diff --git a/.gn b/.gn
index ea7a2f4..a0cba4a 100644
--- a/.gn
+++ b/.gn
@@ -279,6 +279,7 @@
   "//extensions/renderer:unit_tests",
   "//extensions/shell/*",
   "//extensions/strings/*",
+  "//fuchsia/*",
   "//gin/*",
   "//google_apis/*",
   "//google_update/*",
@@ -606,7 +607,6 @@
   "//url/*",
 
   #"//v8/*",  # Errors: https://bugs.chromium.org/p/v8/issues/detail?id=7330
-  "//webrunner/*",
 ]
 
 # These are the list of GN files that run exec_script. This whitelist exists
diff --git a/BUILD.gn b/BUILD.gn
index bb2769f0..b9eb08c 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -764,14 +764,9 @@
 
   if (is_fuchsia) {
     deps += [
+      "//fuchsia:webrunner_unittests",
+      "//fuchsia/net_http:http_service_tests",
       "//headless",
-      "//headless:headless_shell",
-      "//headless:headless_tests",
-      "//webrunner",
-      "//webrunner:archive_sources",
-      "//webrunner:webrunner_unittests",
-      "//webrunner/net_http:http_pkg",
-      "//webrunner/net_http:http_service_tests",
     ]
   }
 
diff --git a/DEPS b/DEPS
index e86cf84..3fc27ec2 100644
--- a/DEPS
+++ b/DEPS
@@ -116,7 +116,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': 'd4fdf78475a195ad1b83a5f51ad8d923f0150a33',
+  'skia_revision': '14235d1ec0955fb615abba8007d067f622599d8d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -128,7 +128,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'b3bdd2acc4f034eb3676df5dc7fd516da1288a6c',
+  'angle_revision': '5fe7c5b926428d0d4dc8fa606afc7a4ab93cc49c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling build tools
   # and whatever else without interference from each other.
@@ -140,7 +140,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'd19dc58f1102216d472edfe2843c85bf2f4a1a7c',
+  'pdfium_revision': 'f801451f55205c1839366b0dbd16c0689fb4275f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -224,7 +224,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': '7577415cc78c225b9ccce8a0771e39d7bbf815cd',
+  'spv_tools_revision': '213e15e100e30c05626f456c3b8115fa3b2375c2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -674,7 +674,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '5787b2a44d4740871eca43a6036c0d9e70d13938',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '5cfe5f2c7b78d0145fb4e8d5e17fe0876a67df88',
       'condition': 'checkout_linux',
   },
 
@@ -840,7 +840,7 @@
     Var('chromium_git') + '/chromium/deps/hunspell_dictionaries.git' + '@' + 'a9bac57ce6c9d390a52ebaad3259f5fdb871210e',
 
   'src/third_party/icu':
-    Var('chromium_git') + '/chromium/deps/icu.git' + '@' + 'd65301491c513d49163ad29c853eb85c02c8d5b4',
+    Var('chromium_git') + '/chromium/deps/icu.git' + '@' + '07e7295d964399ee7bee16a3ac7ca5a053b2cf0a',
 
   'src/third_party/icu4j': {
       'packages': [
@@ -1235,7 +1235,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@902711febd7b3ad6a55ee9a0e4dcd16f3aa2bc22',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@64a7a9267e5fc11b6b959ea2c3062ccb588a3ad3',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 89967c3c..38a306d 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -1643,19 +1643,19 @@
                  r"^courgette[\\/]courgette_minimal_tool\.cc$",
                  r"^courgette[\\/]courgette_tool\.cc$",
                  r"^extensions[\\/]renderer[\\/]logging_native_handler\.cc$",
+                 r"^fuchsia[\\/]browser[\\/]frame_impl.cc$",
+                 r"^headless[\\/]app[\\/]headless_shell\.cc$",
                  r"^ipc[\\/]ipc_logging\.cc$",
                  r"^native_client_sdk[\\/]",
                  r"^remoting[\\/]base[\\/]logging\.h$",
                  r"^remoting[\\/]host[\\/].*",
                  r"^sandbox[\\/]linux[\\/].*",
+                 r"^storage[\\/]browser[\\/]fileapi[\\/]" +
+                     r"dump_file_system.cc$",
                  r"^tools[\\/]",
                  r"^ui[\\/]base[\\/]resource[\\/]data_pack.cc$",
                  r"^ui[\\/]aura[\\/]bench[\\/]bench_main\.cc$",
-                 r"^ui[\\/]ozone[\\/]platform[\\/]cast[\\/]",
-                 r"^webrunner[\\/]browser[\\/]frame_impl.cc$",
-                 r"^storage[\\/]browser[\\/]fileapi[\\/]" +
-                     r"dump_file_system.cc$",
-                 r"^headless[\\/]app[\\/]headless_shell\.cc$"))
+                 r"^ui[\\/]ozone[\\/]platform[\\/]cast[\\/]"))
   source_file_filter = lambda x: input_api.FilterSourceFile(
       x, white_list=file_inclusion_pattern, black_list=black_list)
 
diff --git a/android_webview/browser/aw_proxying_url_loader_factory.cc b/android_webview/browser/aw_proxying_url_loader_factory.cc
index 10476b7..0d7c43b 100644
--- a/android_webview/browser/aw_proxying_url_loader_factory.cc
+++ b/android_webview/browser/aw_proxying_url_loader_factory.cc
@@ -158,7 +158,14 @@
   std::unique_ptr<AwContentsIoThreadClient> io_thread_client =
       GetIoThreadClient();
   DCHECK(io_thread_client);
-  request_.load_flags = GetCacheModeForClient(io_thread_client.get());
+
+  if (ShouldBlockURL(request_.url, io_thread_client.get())) {
+    OnRequestError(network::URLLoaderCompletionStatus(net::ERR_ACCESS_DENIED));
+    return;
+  }
+
+  request_.load_flags =
+      UpdateLoadFlags(request_.load_flags, io_thread_client.get());
 
   // TODO: verify the case when WebContents::RenderFrameDeleted is called
   // before network request is intercepted (i.e. if that's possible and
diff --git a/android_webview/browser/net_helpers.cc b/android_webview/browser/net_helpers.cc
index 7aac1055..473806f 100644
--- a/android_webview/browser/net_helpers.cc
+++ b/android_webview/browser/net_helpers.cc
@@ -5,12 +5,27 @@
 #include "android_webview/browser/net_helpers.h"
 
 #include "android_webview/browser/aw_contents_io_thread_client.h"
+#include "android_webview/common/url_constants.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "net/base/load_flags.h"
+#include "url/gurl.h"
 
 namespace android_webview {
 
+namespace {
+int UpdateCacheControlFlags(int load_flags, int cache_control_flags) {
+  const int all_cache_control_flags =
+      net::LOAD_BYPASS_CACHE | net::LOAD_VALIDATE_CACHE |
+      net::LOAD_SKIP_CACHE_VALIDATION | net::LOAD_ONLY_FROM_CACHE;
+  DCHECK_EQ((cache_control_flags & all_cache_control_flags),
+            cache_control_flags);
+  load_flags &= ~all_cache_control_flags;
+  load_flags |= cache_control_flags;
+  return load_flags;
+}
+
+// Gets the net-layer load_flags which reflect |client|'s cache mode.
 int GetCacheModeForClient(AwContentsIoThreadClient* client) {
   AwContentsIoThreadClient::CacheMode cache_mode = client->GetCacheMode();
   switch (cache_mode) {
@@ -32,6 +47,36 @@
   }
 }
 
+}  // namespace
+
+int UpdateLoadFlags(int load_flags, AwContentsIoThreadClient* client) {
+  if (client->ShouldBlockNetworkLoads()) {
+    return UpdateCacheControlFlags(
+        load_flags,
+        net::LOAD_ONLY_FROM_CACHE | net::LOAD_SKIP_CACHE_VALIDATION);
+  }
+
+  int cache_mode = GetCacheModeForClient(client);
+  if (!cache_mode)
+    return load_flags;
+
+  return UpdateCacheControlFlags(load_flags, cache_mode);
+}
+
+bool ShouldBlockURL(const GURL& url, AwContentsIoThreadClient* client) {
+  // Part of implementation of WebSettings.allowContentAccess.
+  if (url.SchemeIs(url::kContentScheme) && client->ShouldBlockContentUrls())
+    return true;
+
+  // Part of implementation of WebSettings.allowFileAccess.
+  if (url.SchemeIsFile() && client->ShouldBlockFileUrls()) {
+    // Application's assets and resources are always available.
+    return !IsAndroidSpecialFileUrl(url);
+  }
+
+  return client->ShouldBlockNetworkLoads() && url.SchemeIs(url::kFtpScheme);
+}
+
 int GetHttpCacheSize() {
   // This currently returns a constant value, but we may consider deciding cache
   // size dynamically, since Android provides better support on newer versions
diff --git a/android_webview/browser/net_helpers.h b/android_webview/browser/net_helpers.h
index 501d13c..e03bd56 100644
--- a/android_webview/browser/net_helpers.h
+++ b/android_webview/browser/net_helpers.h
@@ -7,12 +7,18 @@
 
 #include <memory>
 
+class GURL;
+
 namespace android_webview {
 
 class AwContentsIoThreadClient;
 
-// Gets the net-layer load_flags which reflect |client|'s cache mode.
-int GetCacheModeForClient(AwContentsIoThreadClient* client);
+// Returns the updated request's |load_flags| based on the settings.
+int UpdateLoadFlags(int load_flags, AwContentsIoThreadClient* client);
+
+// Returns true if the given URL should be aborted with
+// net::ERR_ACCESS_DENIED.
+bool ShouldBlockURL(const GURL& url, AwContentsIoThreadClient* client);
 
 // Determines the desired size for WebView's on-disk HttpCache, measured in
 // Bytes.
diff --git a/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.cc b/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.cc
index ddc67c3..03d3c64 100644
--- a/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.cc
+++ b/android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.cc
@@ -47,18 +47,6 @@
     DestructorAtExit g_webview_resource_dispatcher_host_delegate =
         LAZY_INSTANCE_INITIALIZER;
 
-void SetCacheControlFlag(
-    net::URLRequest* request, int flag) {
-  const int all_cache_control_flags =
-      net::LOAD_BYPASS_CACHE | net::LOAD_VALIDATE_CACHE |
-      net::LOAD_SKIP_CACHE_VALIDATION | net::LOAD_ONLY_FROM_CACHE;
-  DCHECK_EQ((flag & all_cache_control_flags), flag);
-  int load_flags = request->load_flags();
-  load_flags &= ~all_cache_control_flags;
-  load_flags |= flag;
-  request->SetLoadFlags(load_flags);
-}
-
 // Called when ResourceDispathcerHost detects a download request.
 // The download is already cancelled when this is called, since
 // relevant for DownloadListener is already extracted.
@@ -230,31 +218,11 @@
   if (!io_client)
     return false;
 
-  // Part of implementation of WebSettings.allowContentAccess.
-  if (request_->url().SchemeIs(url::kContentScheme) &&
-      io_client->ShouldBlockContentUrls()) {
+  if (ShouldBlockURL(request_->url(), io_client.get()))
     return true;
-  }
 
-  // Part of implementation of WebSettings.allowFileAccess.
-  if (request_->url().SchemeIsFile() &&
-      io_client->ShouldBlockFileUrls()) {
-    // Application's assets and resources are always available.
-    return !IsAndroidSpecialFileUrl(request_->url());
-  }
-
-  if (io_client->ShouldBlockNetworkLoads()) {
-    if (request_->url().SchemeIs(url::kFtpScheme)) {
-      return true;
-    }
-    SetCacheControlFlag(
-        request_, net::LOAD_ONLY_FROM_CACHE | net::LOAD_SKIP_CACHE_VALIDATION);
-  } else {
-    int cache_mode = GetCacheModeForClient(io_client.get());
-    if (cache_mode) {
-      SetCacheControlFlag(request_, cache_mode);
-    }
-  }
+  request_->SetLoadFlags(
+      UpdateLoadFlags(request_->load_flags(), io_client.get()));
   return false;
 }
 
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
index b9d67eac..99e4c95 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
@@ -127,7 +127,7 @@
     private SharedPreferences mWebViewPrefs;
     private WebViewDelegate mWebViewDelegate;
 
-    private boolean mShouldDisableThreadChecking;
+    protected boolean mShouldDisableThreadChecking;
 
     // Initialization guarded by mAwInit.getLock()
     private Statics mStaticsAdapter;
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 3d6d3d9a..e589de94 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -806,8 +806,6 @@
     "system/network/sms_observer.h",
     "system/network/tray_network_state_observer.cc",
     "system/network/tray_network_state_observer.h",
-    "system/network/tray_vpn.cc",
-    "system/network/tray_vpn.h",
     "system/network/unified_network_detailed_view_controller.cc",
     "system/network/unified_network_detailed_view_controller.h",
     "system/network/unified_vpn_detailed_view_controller.cc",
@@ -818,6 +816,8 @@
     "system/network/vpn_list.h",
     "system/network/vpn_list_view.cc",
     "system/network/vpn_list_view.h",
+    "system/network/vpn_util.cc",
+    "system/network/vpn_util.h",
     "system/network/wifi_toggle_notification_controller.cc",
     "system/network/wifi_toggle_notification_controller.h",
     "system/night_light/night_light_controller.cc",
@@ -1526,15 +1526,18 @@
 
   deps = [
     ":ash_shell_lib",
+    ":manifest",
     ":test_support",
     "//ash/components/quick_launch:lib",
+    "//ash/components/quick_launch:manifest",
     "//ash/components/quick_launch/public/mojom",
     "//ash/components/shortcut_viewer:lib",
+    "//ash/components/shortcut_viewer:manifest",
     "//ash/components/shortcut_viewer/public/mojom",
     "//ash/components/tap_visualizer:lib",
+    "//ash/components/tap_visualizer:manifest",
     "//ash/components/tap_visualizer/public/mojom",
     "//ash/public/cpp",
-    "//ash/shell:resources",
     "//base:i18n",
     "//chrome:packed_resources",
     "//chromeos",
@@ -1549,8 +1552,10 @@
     "//content/shell:content_shell_lib",
     "//device/bluetooth",
     "//net",
+    "//services/device/public/mojom",
     "//services/ws:lib",
     "//services/ws/ime/test_ime_driver:lib",
+    "//services/ws/ime/test_ime_driver:manifest",
     "//services/ws/ime/test_ime_driver/public/mojom",
     "//skia",
     "//ui/aura",
diff --git a/ash/resources/BUILD.gn b/ash/resources/BUILD.gn
index 314655e..31dfd84 100644
--- a/ash/resources/BUILD.gn
+++ b/ash/resources/BUILD.gn
@@ -76,11 +76,9 @@
 ash_test_resources("with_content_100_percent") {
   percent = "100"
   sources = [
-    "$root_gen_dir/ash/shell/ash_shell_resources.pak",
     "$root_gen_dir/content/content_resources.pak",
   ]
   deps = [
-    "//ash/shell:resources",
     "//content:resources",
   ]
 }
diff --git a/ash/shelf/shelf_app_button.cc b/ash/shelf/shelf_app_button.cc
index 406bc245..25dded4 100644
--- a/ash/shelf/shelf_app_button.cc
+++ b/ash/shelf/shelf_app_button.cc
@@ -294,7 +294,8 @@
 // static
 const char ShelfAppButton::kViewClassName[] = "ash/ShelfAppButton";
 
-ShelfAppButton::ShelfAppButton(ShelfView* shelf_view)
+ShelfAppButton::ShelfAppButton(ShelfView* shelf_view,
+                               const base::string16& title)
     : ShelfButton(shelf_view),
       icon_view_(new views::ImageView()),
       indicator_(new AppStatusIndicatorView()),
@@ -303,6 +304,7 @@
       destroyed_flag_(nullptr),
       is_notification_indicator_enabled_(
           features::IsNotificationIndicatorEnabled()) {
+  SetTitle(title);
   const gfx::ShadowValue kShadows[] = {
       gfx::ShadowValue(gfx::Vector2d(0, 2), 0, SkColorSetARGB(0x1A, 0, 0, 0)),
       gfx::ShadowValue(gfx::Vector2d(0, 3), 1, SkColorSetARGB(0x1A, 0, 0, 0)),
@@ -343,6 +345,10 @@
       image, icon_shadows_));
 }
 
+void ShelfAppButton::SetTitle(const base::string16 title) {
+  SetAccessibleName(title);
+}
+
 void ShelfAppButton::SetImage(const gfx::ImageSkia& image) {
   if (image.isNull()) {
     // TODO: need an empty image.
diff --git a/ash/shelf/shelf_app_button.h b/ash/shelf/shelf_app_button.h
index 69e1ad8bb..34835b5e 100644
--- a/ash/shelf/shelf_app_button.h
+++ b/ash/shelf/shelf_app_button.h
@@ -45,9 +45,12 @@
     STATE_ACTIVE = 1 << 6,
   };
 
-  ShelfAppButton(ShelfView* shelf_view);
+  ShelfAppButton(ShelfView* shelf_view, const base::string16& title);
   ~ShelfAppButton() override;
 
+  // Sets the textual title for this entry, to be shown in a tooltip.
+  void SetTitle(const base::string16 title);
+
   // Sets the image to display for this entry.
   void SetImage(const gfx::ImageSkia& image);
 
diff --git a/ash/shelf/shelf_button.cc b/ash/shelf/shelf_button.cc
index 5d58a38..991c5d5 100644
--- a/ash/shelf/shelf_button.cc
+++ b/ash/shelf/shelf_button.cc
@@ -54,8 +54,16 @@
 
 void ShelfButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   node_data->role = ax::mojom::Role::kButton;
-  const base::string16 title = shelf_view_->GetTitleForView(this);
-  node_data->SetName(title.empty() ? GetAccessibleName() : title);
+  node_data->SetName(GetAccessibleName());
+}
+
+bool ShelfButton::GetTooltipText(const gfx::Point& p,
+                                 base::string16* tooltip) const {
+  // Copy the proper tooltip text, but return false because we do not want to
+  // show a tooltip with the standard view mechanism and instead use the
+  // custom display logic defined in |ShelfTooltipManager|.
+  *tooltip = GetAccessibleName();
+  return false;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/ash/shelf/shelf_button.h b/ash/shelf/shelf_button.h
index 89aa3f5..fd1963b 100644
--- a/ash/shelf/shelf_button.h
+++ b/ash/shelf/shelf_button.h
@@ -18,15 +18,17 @@
   explicit ShelfButton(ShelfView* shelf_view);
   ~ShelfButton() override;
 
- protected:
-  ShelfView* shelf_view() { return shelf_view_; }
-
   // views::View
   bool OnMousePressed(const ui::MouseEvent& event) override;
   void OnMouseReleased(const ui::MouseEvent& event) override;
   void OnMouseCaptureLost() override;
   bool OnMouseDragged(const ui::MouseEvent& event) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+  bool GetTooltipText(const gfx::Point& p,
+                      base::string16* tooltip) const override;
+
+ protected:
+  ShelfView* shelf_view() { return shelf_view_; }
 
   // views::Button
   void NotifyClick(const ui::Event& event) override;
diff --git a/ash/shelf/shelf_tooltip_manager.cc b/ash/shelf/shelf_tooltip_manager.cc
index 2184d73..72b81d6e 100644
--- a/ash/shelf/shelf_tooltip_manager.cc
+++ b/ash/shelf/shelf_tooltip_manager.cc
@@ -96,8 +96,9 @@
         view, arrow, open_windows, this,
         shelf_view_->shelf_widget()->GetShelfBackgroundColor());
   } else {
-    bubble_ =
-        new ShelfTooltipBubble(view, arrow, shelf_view_->GetTitleForView(view));
+    base::string16 title;
+    view->GetTooltipText(gfx::Point(), &title);
+    bubble_ = new ShelfTooltipBubble(view, arrow, title);
   }
 
   aura::Window* window = bubble_->GetWidget()->GetNativeWindow();
diff --git a/ash/shelf/shelf_view.cc b/ash/shelf/shelf_view.cc
index a743aaa..0ac929173 100644
--- a/ash/shelf/shelf_view.cc
+++ b/ash/shelf/shelf_view.cc
@@ -39,6 +39,7 @@
 #include "ash/wm/root_window_finder.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/auto_reset.h"
+#include "base/containers/adapters.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/timer/timer.h"
@@ -57,7 +58,7 @@
 #include "ui/views/animation/bounds_animator.h"
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/border.h"
-#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/button/button.h"
 #include "ui/views/controls/menu/menu_model_adapter.h"
 #include "ui/views/controls/menu/menu_runner.h"
 #include "ui/views/controls/separator.h"
@@ -465,6 +466,10 @@
   return nullptr;
 }
 
+OverflowButton* ShelfView::GetOverflowButton() const {
+  return overflow_button_;
+}
+
 void ShelfView::UpdateVisibleShelfItemBoundsUnion() {
   visible_shelf_item_bounds_union_.SetRect(0, 0, 0, 0);
   for (int i = first_visible_index_; i <= last_visible_index_; ++i) {
@@ -479,6 +484,11 @@
     if (ShouldShowTooltipForView(child))
       visible_shelf_item_bounds_union_.Union(child->GetMirroredBounds());
   }
+  // Also include the overflow button if it is visible.
+  if (overflow_button_->visible()) {
+    visible_shelf_item_bounds_union_.Union(
+        overflow_button_->GetMirroredBounds());
+  }
 }
 
 bool ShelfView::ShouldHideTooltip(const gfx::Point& cursor_location) const {
@@ -493,11 +503,12 @@
 }
 
 bool ShelfView::ShouldShowTooltipForView(const views::View* view) const {
-  // TODO(msw): Push this app list state into ShelfItem::shows_tooltip.
   // If this is the app list button, only show the tooltip if the app list is
   // not already showing.
   if (view == GetAppListButton())
     return !GetAppListButton()->is_showing_app_list();
+  if (view == overflow_button_)
+    return true;
   // Don't show a tooltip for a view that's currently being dragged.
   if (view == drag_view_)
     return false;
@@ -505,11 +516,6 @@
   return ShelfItemForView(view) && !IsShowingMenuForView(view);
 }
 
-base::string16 ShelfView::GetTitleForView(const views::View* view) const {
-  const ShelfItem* item = ShelfItemForView(view);
-  return item ? item->title : base::string16();
-}
-
 gfx::Rect ShelfView::GetVisibleItemsBoundsInScreen() {
   gfx::Size preferred_size = GetPreferredSize();
   gfx::Point origin(GetMirroredXWithWidthInView(0, preferred_size.width()), 0);
@@ -517,6 +523,88 @@
   return gfx::Rect(origin, preferred_size);
 }
 
+gfx::Size ShelfView::CalculatePreferredSize() const {
+  gfx::Rect overflow_bounds;
+  CalculateIdealBounds(&overflow_bounds);
+
+  int last_button_index = last_visible_index_;
+  if (!is_overflow_mode() && overflow_button_ && overflow_button_->visible())
+    ++last_button_index;
+
+  // When an item is dragged off from the overflow bubble, it is moved to last
+  // position and and changed to invisible. Overflow bubble size should be
+  // shrunk to fit only for visible items.
+  // If |dragged_to_another_shelf_| is set, there will be no
+  // invisible items in the shelf.
+  if (is_overflow_mode() && dragged_off_shelf_ && !dragged_to_another_shelf_ &&
+      RemovableByRipOff(view_model_->GetIndexOfView(drag_view_)) == REMOVABLE)
+    last_button_index--;
+
+  const gfx::Rect last_button_bounds =
+      last_button_index >= first_visible_index_
+          ? view_model_->ideal_bounds(last_button_index)
+          : gfx::Rect(gfx::Size(ShelfConstants::shelf_size(),
+                                ShelfConstants::shelf_size()));
+
+  if (shelf_->IsHorizontalAlignment())
+    return gfx::Size(last_button_bounds.right(), ShelfConstants::shelf_size());
+
+  return gfx::Size(ShelfConstants::shelf_size(), last_button_bounds.bottom());
+}
+
+void ShelfView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
+  // This bounds change is produced by the shelf movement (rotation, alignment
+  // change, etc.) and all content has to follow. Using an animation at that
+  // time would produce a time lag since the animation of the BoundsAnimator has
+  // itself a delay before it arrives at the required location. As such we tell
+  // the animator to go there immediately. We still want to use an animation
+  // when the bounds change is caused by entering or exiting tablet mode.
+  if (shelf_->is_tablet_mode_animation_running()) {
+    AnimateToIdealBounds();
+    if (IsShowingOverflowBubble()) {
+      overflow_bubble_->bubble_view()->shelf_view()->OnBoundsChanged(
+          previous_bounds);
+    }
+    return;
+  }
+
+  BoundsAnimatorDisabler disabler(bounds_animator_.get());
+
+  LayoutToIdealBounds();
+  shelf_->NotifyShelfIconPositionsChanged();
+
+  if (IsShowingOverflowBubble())
+    overflow_bubble_->Hide();
+}
+
+views::FocusTraversable* ShelfView::GetPaneFocusTraversable() {
+  return this;
+}
+
+void ShelfView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
+  node_data->role = ax::mojom::Role::kToolbar;
+  node_data->SetName(l10n_util::GetStringUTF8(IDS_ASH_SHELF_ACCESSIBLE_NAME));
+}
+
+View* ShelfView::GetTooltipHandlerForPoint(const gfx::Point& point) {
+  // Similar implementation as views::View, but without going into each
+  // child's subviews.
+  View::Views children = GetChildrenInZOrder();
+  for (auto* child : base::Reversed(children)) {
+    if (!child->visible())
+      continue;
+
+    gfx::Point point_in_child_coords(point);
+    ConvertPointToTarget(this, child, &point_in_child_coords);
+    if (child->HitTestPoint(point_in_child_coords) &&
+        ShouldShowTooltipForView(child)) {
+      return child;
+    }
+  }
+  // If none of our children qualifies, just return the shelf view itself.
+  return this;
+}
+
 void ShelfView::ButtonPressed(views::Button* sender,
                               const ui::Event& event,
                               views::InkDrop* ink_drop) {
@@ -723,11 +811,17 @@
   std::vector<aura::Window*> window_list =
       Shell::Get()->mru_window_tracker()->BuildWindowForCycleList();
   std::vector<aura::Window*> open_windows;
-  const std::string shelf_item_app_id = ShelfItemForView(view)->id.app_id;
+  const ShelfItem* item = ShelfItemForView(view);
+
+  // The concept of a list of open windows doesn't make sense for something
+  // that isn't an app shortcut: return an empty list.
+  if (!item)
+    return open_windows;
+
   for (auto* window : window_list) {
     const std::string window_app_id =
         ShelfID::Deserialize(window->GetProperty(kShelfIDKey)).app_id;
-    if (window_app_id == shelf_item_app_id) {
+    if (window_app_id == item->id.app_id) {
       // TODO: In the very first version we only show one window. Add the proper
       // UI to show all windows for a given open app.
       open_windows.push_back(window);
@@ -1200,7 +1294,7 @@
     case TYPE_BROWSER_SHORTCUT:
     case TYPE_APP:
     case TYPE_DIALOG: {
-      ShelfAppButton* button = new ShelfAppButton(this);
+      ShelfAppButton* button = new ShelfAppButton(this, item.title);
       button->SetImage(item.image);
       button->ReflectItemStatus(item);
       view = button;
@@ -1802,69 +1896,6 @@
   return modified_view ? view_model_->GetIndexOfView(modified_view) : -1;
 }
 
-gfx::Size ShelfView::CalculatePreferredSize() const {
-  gfx::Rect overflow_bounds;
-  CalculateIdealBounds(&overflow_bounds);
-
-  int last_button_index = last_visible_index_;
-  if (!is_overflow_mode() && overflow_button_ && overflow_button_->visible())
-    ++last_button_index;
-
-  // When an item is dragged off from the overflow bubble, it is moved to last
-  // position and and changed to invisible. Overflow bubble size should be
-  // shrunk to fit only for visible items.
-  // If |dragged_to_another_shelf_| is set, there will be no
-  // invisible items in the shelf.
-  if (is_overflow_mode() && dragged_off_shelf_ && !dragged_to_another_shelf_ &&
-      RemovableByRipOff(view_model_->GetIndexOfView(drag_view_)) == REMOVABLE)
-    last_button_index--;
-
-  const gfx::Rect last_button_bounds =
-      last_button_index >= first_visible_index_
-          ? view_model_->ideal_bounds(last_button_index)
-          : gfx::Rect(gfx::Size(ShelfConstants::shelf_size(),
-                                ShelfConstants::shelf_size()));
-
-  if (shelf_->IsHorizontalAlignment())
-    return gfx::Size(last_button_bounds.right(), ShelfConstants::shelf_size());
-
-  return gfx::Size(ShelfConstants::shelf_size(), last_button_bounds.bottom());
-}
-
-void ShelfView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
-  // This bounds change is produced by the shelf movement (rotation, alignment
-  // change, etc.) and all content has to follow. Using an animation at that
-  // time would produce a time lag since the animation of the BoundsAnimator has
-  // itself a delay before it arrives at the required location. As such we tell
-  // the animator to go there immediately. We still want to use an animation
-  // when the bounds change is caused by entering or exiting tablet mode.
-  if (shelf_->is_tablet_mode_animation_running()) {
-    AnimateToIdealBounds();
-    if (IsShowingOverflowBubble()) {
-      overflow_bubble_->bubble_view()->shelf_view()->OnBoundsChanged(
-          previous_bounds);
-    }
-    return;
-  }
-
-  BoundsAnimatorDisabler disabler(bounds_animator_.get());
-
-  LayoutToIdealBounds();
-  shelf_->NotifyShelfIconPositionsChanged();
-
-  if (IsShowingOverflowBubble())
-    overflow_bubble_->Hide();
-}
-
-views::FocusTraversable* ShelfView::GetPaneFocusTraversable() {
-  return this;
-}
-
-void ShelfView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
-  node_data->role = ax::mojom::Role::kToolbar;
-  node_data->SetName(l10n_util::GetStringUTF8(IDS_ASH_SHELF_ACCESSIBLE_NAME));
-}
-
 void ShelfView::OnGestureEvent(ui::GestureEvent* event) {
   // Convert the event location from current view to screen, since swiping up on
   // the shelf can open the fullscreen app list. Updating the bounds of the app
@@ -2012,6 +2043,7 @@
       CHECK_EQ(ShelfAppButton::kViewClassName, view->GetClassName());
       ShelfAppButton* button = static_cast<ShelfAppButton*>(view);
       button->ReflectItemStatus(item);
+      button->SetTitle(item.title);
       button->SetImage(item.image);
       button->SchedulePaint();
       break;
diff --git a/ash/shelf/shelf_view.h b/ash/shelf/shelf_view.h
index 0ace96f..cbdc9bff 100644
--- a/ash/shelf/shelf_view.h
+++ b/ash/shelf/shelf_view.h
@@ -151,6 +151,7 @@
 
   AppListButton* GetAppListButton() const;
   BackButton* GetBackButton() const;
+  OverflowButton* GetOverflowButton() const;
 
   // Updates the union of all the shelf item bounds shown by this shelf view.
   // This is used to determine the common area where the mouse can hover
@@ -166,13 +167,17 @@
   // Returns true if a tooltip should be shown for the shelf item |view|.
   bool ShouldShowTooltipForView(const views::View* view) const;
 
-  // Returns the title of the shelf item |view|.
-  base::string16 GetTitleForView(const views::View* view) const;
-
   // Returns rectangle bounding all visible launcher items. Used screen
   // coordinate system.
   gfx::Rect GetVisibleItemsBoundsInScreen();
 
+  // Overridden from views::View:
+  gfx::Size CalculatePreferredSize() const override;
+  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
+  FocusTraversable* GetPaneFocusTraversable() override;
+  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+  View* GetTooltipHandlerForPoint(const gfx::Point& point) override;
+
   // InkDropButtonListener:
   void ButtonPressed(views::Button* sender,
                      const ui::Event& event,
@@ -394,12 +399,6 @@
                               const gfx::Point& location,
                               bool context_menu) const;
 
-  // Overridden from views::View:
-  gfx::Size CalculatePreferredSize() const override;
-  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
-  FocusTraversable* GetPaneFocusTraversable() override;
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-
   // Overridden from ui::EventHandler:
   void OnGestureEvent(ui::GestureEvent* event) override;
   bool OnMouseWheel(const ui::MouseWheelEvent& event) override;
diff --git a/ash/shelf/shelf_view_unittest.cc b/ash/shelf/shelf_view_unittest.cc
index d8e718f..e68aaa2 100644
--- a/ash/shelf/shelf_view_unittest.cc
+++ b/ash/shelf/shelf_view_unittest.cc
@@ -35,6 +35,7 @@
 #include "ash/shelf/shelf_widget.h"
 #include "ash/shell.h"
 #include "ash/shell_test_api.h"
+#include "ash/strings/grit/ash_strings.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/test/ash_test_helper.h"
@@ -60,6 +61,7 @@
 #include "ui/aura/test/aura_test_base.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_event_dispatcher.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/compositor/layer.h"
 #include "ui/display/display.h"
@@ -1333,6 +1335,27 @@
   EXPECT_EQ(nullptr, tooltip_manager->GetCurrentAnchorView());
 }
 
+TEST_F(ShelfViewTest, ButtonTitlesTest) {
+  AddButtonsUntilOverflow();
+  EXPECT_EQ(base::UTF8ToUTF16("Launcher"),
+            shelf_view_->GetAppListButton()->GetAccessibleName());
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_SHELF_BACK_BUTTON_TITLE),
+            shelf_view_->GetBackButton()->GetAccessibleName());
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_SHELF_OVERFLOW_NAME),
+            shelf_view_->GetOverflowButton()->GetAccessibleName());
+
+  for (int i = 0; i < test_api_->GetButtonCount(); i++) {
+    ShelfAppButton* button = test_api_->GetButton(i);
+    if (button) {
+      base::string16 tooltip;
+      button->GetTooltipText(gfx::Point(), &tooltip);
+      EXPECT_EQ(tooltip, button->GetAccessibleName())
+          << "Each button's tooltip text should read the same as its "
+          << "accessible name";
+    }
+  }
+}
+
 // Verify a fix for crash caused by a tooltip update for a deleted shelf
 // button, see crbug.com/288838.
 TEST_F(ShelfViewTest, RemovingItemClosesTooltip) {
diff --git a/ash/shell/BUILD.gn b/ash/shell/BUILD.gn
deleted file mode 100644
index a8c43b64..0000000
--- a/ash/shell/BUILD.gn
+++ /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.
-
-import("//services/service_manager/public/service_manifest.gni")
-import("//tools/grit/grit_rule.gni")
-
-assert(is_chromeos)
-
-grit("resources") {
-  source = "ash_shell_resources.grd"
-  outputs = [
-    "grit/ash_shell_resources.h",
-    "ash_shell_resources.pak",
-  ]
-  grit_flags = [
-    "-E",
-    "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir),
-  ]
-
-  deps = [
-    ":ash_content_browser_manifest_overlay",
-    ":ash_content_packaged_services_manifest_overlay",
-  ]
-}
-
-service_manifest("ash_content_browser_manifest_overlay") {
-  source = "//ash/shell/ash_content_browser_manifest_overlay.json"
-}
-
-service_manifest("ash_content_packaged_services_manifest_overlay") {
-  source = "//ash/shell/ash_content_packaged_services_manifest_overlay.json"
-  packaged_services = [
-    "//ash:manifest",
-    "//ash/components/quick_launch:manifest",
-    "//ash/components/shortcut_viewer:manifest",
-    "//ash/components/tap_visualizer:manifest",
-    "//services/ws/ime/test_ime_driver:manifest",
-  ]
-}
diff --git a/ash/shell/OWNERS b/ash/shell/OWNERS
index 9774cf01..c8dc16e 100644
--- a/ash/shell/OWNERS
+++ b/ash/shell/OWNERS
@@ -1,7 +1 @@
 per-file *app_list*=xiyuan@chromium.org
-
-per-file ash_content_packaged_services_manifest_overlay.json=set noparent
-per-file ash_content_packaged_services_manifest_overlay.json=file://ipc/SECURITY_OWNERS
-
-per-file ash_content_browser_manifest_overlay.json=set noparent
-per-file ash_content_browser_manifest_overlay.json=file://ipc/SECURITY_OWNERS
diff --git a/ash/shell/ash_content_browser_manifest_overlay.json b/ash/shell/ash_content_browser_manifest_overlay.json
deleted file mode 100644
index 9f0f4d0..0000000
--- a/ash/shell/ash_content_browser_manifest_overlay.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "name": "content_browser",
-  "display_name": "Ash Content Browser",
-  "interface_provider_specs": {
-    "service_manager:connector": {
-      "requires": {
-        "device": [ "device:fingerprint" ],
-        "shortcut_viewer_app": [ "shortcut_viewer" ],
-        "tap_visualizer_app": [ "tap_visualizer" ]
-      }
-    }
-  }
-}
diff --git a/ash/shell/ash_content_packaged_services_manifest_overlay.json b/ash/shell/ash_content_packaged_services_manifest_overlay.json
deleted file mode 100644
index eaffdf51..0000000
--- a/ash/shell/ash_content_packaged_services_manifest_overlay.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "name": "content_packaged_services",
-  "display_name": "Ash Packaged Services",
-  "interface_provider_specs": {}
-}
diff --git a/ash/shell/ash_shell_resources.grd b/ash/shell/ash_shell_resources.grd
deleted file mode 100644
index d56ed86..0000000
--- a/ash/shell/ash_shell_resources.grd
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
-  <outputs>
-    <output filename="grit/ash_shell_resources.h" type="rc_header">
-      <emit emit_type='prepend'></emit>
-    </output>
-    <output filename="ash_shell_resources.pak" type="data_package" />
-  </outputs>
-  <release seq="1">
-    <includes>
-      <include name="IDR_ASH_SHELL_CONTENT_BROWSER_MANIFEST_OVERLAY" file="${root_gen_dir}\ash\shell\ash_content_browser_manifest_overlay.json" type="BINDATA" use_base_dir="false"/>
-      <include name="IDR_ASH_SHELL_CONTENT_PACKAGED_SERVICES_MANIFEST_OVERLAY" file="${root_gen_dir}\ash\shell\ash_content_packaged_services_manifest_overlay.json" type="BINDATA" use_base_dir="false"/>
-      <include name="IDR_ASH_SHELL_FONT_SERVICE_MANIFEST" file="../../components/services/font/manifest.json" type="BINDATA" use_base_dir="false" />
-      <include name="IDR_ASH_SHELL_QUICK_LAUNCH_MANIFEST" file="../../ash/components/quick_launch/manifest.json" type="BINDATA" use_base_dir="false" />
-      <include name="IDR_ASH_SHELL_SHORTCUT_VIEWER_MANIFEST" file="../../ash/components/shortcut_viewer/manifest.json" type="BINDATA" use_base_dir="false" />
-      <include name="IDR_ASH_SHELL_TAP_VISUALIZER_MANIFEST" file="../../ash/components/tap_visualizer/manifest.json" type="BINDATA" use_base_dir="false" />
-      <include name="IDR_ASH_SHELL_TEST_IME_DRIVER_MANIFEST" file="../../services/ws/ime/test_ime_driver/manifest.json" type="BINDATA" use_base_dir="false" />
-    </includes>
-  </release>
-</grit>
diff --git a/ash/shell/content/client/DEPS b/ash/shell/content/client/DEPS
index 3ff3bd3..b4e4b71 100644
--- a/ash/shell/content/client/DEPS
+++ b/ash/shell/content/client/DEPS
@@ -6,5 +6,6 @@
   "+content/public",
   "+content/shell",
   "+storage/browser/quota",
+  "+services/device/public",
   "+services/ws/ime/test_ime_driver",
 ]
diff --git a/ash/shell/content/client/shell_content_browser_client.cc b/ash/shell/content/client/shell_content_browser_client.cc
index d37d07b..ce8fe44 100644
--- a/ash/shell/content/client/shell_content_browser_client.cc
+++ b/ash/shell/content/client/shell_content_browser_client.cc
@@ -7,18 +7,21 @@
 #include <utility>
 
 #include "ash/ash_service.h"
+#include "ash/components/quick_launch/manifest.h"
 #include "ash/components/quick_launch/public/mojom/constants.mojom.h"
+#include "ash/components/shortcut_viewer/manifest.h"
 #include "ash/components/shortcut_viewer/public/mojom/shortcut_viewer.mojom.h"
+#include "ash/components/tap_visualizer/manifest.h"
 #include "ash/components/tap_visualizer/public/mojom/tap_visualizer.mojom.h"
+#include "ash/manifest.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/shell.h"
 #include "ash/shell/content/client/shell_browser_main_parts.h"
-#include "ash/shell/grit/ash_shell_resources.h"
 #include "base/base_switches.h"
 #include "base/bind.h"
 #include "base/command_line.h"
-#include "base/json/json_reader.h"
+#include "base/no_destructor.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "content/public/browser/browser_context.h"
@@ -26,16 +29,47 @@
 #include "content/public/common/service_manager_connection.h"
 #include "content/public/common/service_names.mojom.h"
 #include "content/public/utility/content_utility_client.h"
+#include "services/device/public/mojom/constants.mojom.h"
+#include "services/service_manager/public/cpp/manifest.h"
+#include "services/service_manager/public/cpp/manifest_builder.h"
+#include "services/ws/ime/test_ime_driver/manifest.h"
 #include "services/ws/ime/test_ime_driver/public/mojom/constants.mojom.h"
 #include "services/ws/public/mojom/constants.mojom.h"
 #include "services/ws/window_service.h"
 #include "storage/browser/quota/quota_settings.h"
 #include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/base/resource/resource_bundle.h"
 
 namespace ash {
 namespace shell {
 
+namespace {
+
+const service_manager::Manifest& GetAshShellBrowserOverlayManifest() {
+  static base::NoDestructor<service_manager::Manifest> manifest{
+      service_manager::ManifestBuilder()
+          .RequireCapability(device::mojom::kServiceName, "device:fingerprint")
+          .RequireCapability(shortcut_viewer::mojom::kServiceName,
+                             "shortcut_viewer")
+          .RequireCapability(tap_visualizer::mojom::kServiceName,
+                             "tap_visualizer")
+          .Build()};
+  return *manifest;
+}
+
+const service_manager::Manifest& GetAshShellPackagedServicesOverlayManifest() {
+  static base::NoDestructor<service_manager::Manifest> manifest{
+      service_manager::ManifestBuilder()
+          .PackageService(ash::GetManifest())
+          .PackageService(quick_launch_app::GetManifest())
+          .PackageService(shortcut_viewer_app::GetManifest())
+          .PackageService(tap_visualizer_app::GetManifest())
+          .PackageService(test_ime_driver::GetManifest())
+          .Build()};
+  return *manifest;
+}
+
+}  // namespace
+
 ShellContentBrowserClient::ShellContentBrowserClient()
     : shell_browser_main_parts_(nullptr) {}
 
@@ -57,38 +91,15 @@
 
 base::Optional<service_manager::Manifest>
 ShellContentBrowserClient::GetServiceManifestOverlay(base::StringPiece name) {
-  if (name == content::mojom::kBrowserServiceName) {
-    // This is necessary for outgoing interface requests (such as the keyboard
-    // shortcut viewer).
-    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
-    base::StringPiece manifest_contents = rb.GetRawDataResourceForScale(
-        IDR_ASH_SHELL_CONTENT_BROWSER_MANIFEST_OVERLAY,
-        ui::ScaleFactor::SCALE_FACTOR_NONE);
-    return service_manager::Manifest::FromValueDeprecated(
-        base::JSONReader::Read(manifest_contents));
-  }
-  if (name == content::mojom::kPackagedServicesServiceName) {
-    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
-    base::StringPiece manifest_contents = rb.GetRawDataResourceForScale(
-        IDR_ASH_SHELL_CONTENT_PACKAGED_SERVICES_MANIFEST_OVERLAY,
-        ui::ScaleFactor::SCALE_FACTOR_NONE);
-    return service_manager::Manifest::FromValueDeprecated(
-        base::JSONReader::Read(manifest_contents));
-  }
-  return base::nullopt;
-}
+  // This is necessary for outgoing interface requests (such as the keyboard
+  // shortcut viewer).
+  if (name == content::mojom::kBrowserServiceName)
+    return GetAshShellBrowserOverlayManifest();
 
-std::vector<content::ContentBrowserClient::ServiceManifestInfo>
-ShellContentBrowserClient::GetExtraServiceManifests() {
-  return {
-      {quick_launch::mojom::kServiceName, IDR_ASH_SHELL_QUICK_LAUNCH_MANIFEST},
-      {shortcut_viewer::mojom::kServiceName,
-       IDR_ASH_SHELL_SHORTCUT_VIEWER_MANIFEST},
-      {tap_visualizer::mojom::kServiceName,
-       IDR_ASH_SHELL_TAP_VISUALIZER_MANIFEST},
-      {test_ime_driver::mojom::kServiceName,
-       IDR_ASH_SHELL_TEST_IME_DRIVER_MANIFEST},
-  };
+  if (name == content::mojom::kPackagedServicesServiceName)
+    return GetAshShellPackagedServicesOverlayManifest();
+
+  return base::nullopt;
 }
 
 void ShellContentBrowserClient::RegisterOutOfProcessServices(
diff --git a/ash/shell/content/client/shell_content_browser_client.h b/ash/shell/content/client/shell_content_browser_client.h
index 8162e1f..54c934a 100644
--- a/ash/shell/content/client/shell_content_browser_client.h
+++ b/ash/shell/content/client/shell_content_browser_client.h
@@ -34,7 +34,6 @@
       storage::OptionalQuotaSettingsCallback callback) override;
   base::Optional<service_manager::Manifest> GetServiceManifestOverlay(
       base::StringPiece name) override;
-  std::vector<ServiceManifestInfo> GetExtraServiceManifests() override;
   void RegisterOutOfProcessServices(OutOfProcessServiceMap* services) override;
   void HandleServiceRequest(
       const std::string& service_name,
diff --git a/ash/system/date/date_view.cc b/ash/system/date/date_view.cc
index 5f488c4..589bd322 100644
--- a/ash/system/date/date_view.cc
+++ b/ash/system/date/date_view.cc
@@ -56,147 +56,19 @@
 
 }  // namespace
 
-BaseDateTimeView::~BaseDateTimeView() {
-  model_->RemoveObserver(this);
-  timer_.Stop();
-}
-
-void BaseDateTimeView::UpdateText() {
-  base::Time now = base::Time::Now();
-  UpdateTextInternal(now);
-  SchedulePaint();
-  SetTimer(now);
-}
-
-void BaseDateTimeView::UpdateTimeFormat() {
-  UpdateText();
-}
-
-void BaseDateTimeView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
-  ActionableView::GetAccessibleNodeData(node_data);
-  node_data->role = ax::mojom::Role::kTime;
-}
-
-void BaseDateTimeView::OnDateFormatChanged() {
-  UpdateTimeFormat();
-}
-
-void BaseDateTimeView::OnSystemClockTimeUpdated() {
-  UpdateTimeFormat();
-}
-
-void BaseDateTimeView::OnSystemClockCanSetTimeChanged(bool can_set_time) {}
-
-void BaseDateTimeView::Refresh() {}
-
-base::HourClockType BaseDateTimeView::GetHourTypeForTesting() const {
-  return model_->hour_clock_type();
-}
-
-BaseDateTimeView::BaseDateTimeView(ClockModel* model)
+TimeView::TimeView(ClockLayout clock_layout, ClockModel* model)
     : ActionableView(TrayPopupInkDropStyle::INSET_BOUNDS), model_(model) {
   SetTimer(base::Time::Now());
   SetFocusBehavior(FocusBehavior::NEVER);
   model_->AddObserver(this);
-}
-
-void BaseDateTimeView::SetTimer(const base::Time& now) {
-  // Try to set the timer to go off at the next change of the minute. We don't
-  // want to have the timer go off more than necessary since that will cause
-  // the CPU to wake up and consume power.
-  base::Time::Exploded exploded;
-  now.LocalExplode(&exploded);
-
-  // Often this will be called at minute boundaries, and we'll actually want
-  // 60 seconds from now.
-  int seconds_left = 60 - exploded.second;
-  if (seconds_left == 0)
-    seconds_left = 60;
-
-  // Make sure that the timer fires on the next minute. Without this, if it is
-  // called just a teeny bit early, then it will skip the next minute.
-  seconds_left += kTimerSlopSeconds;
-
-  timer_.Stop();
-  timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(seconds_left), this,
-               &BaseDateTimeView::UpdateText);
-}
-
-void BaseDateTimeView::UpdateTextInternal(const base::Time& now) {
-  SetAccessibleName(base::TimeFormatTimeOfDayWithHourClockType(
-                        now, model_->hour_clock_type(), base::kKeepAmPm) +
-                    base::ASCIIToUTF16(", ") +
-                    base::TimeFormatFriendlyDate(now));
-
-  NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, true);
-}
-
-void BaseDateTimeView::ChildPreferredSizeChanged(views::View* child) {
-  PreferredSizeChanged();
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-TimeView::TimeView(ClockLayout clock_layout, ClockModel* model)
-    : BaseDateTimeView(model) {
   SetupLabels();
   UpdateTextInternal(base::Time::Now());
   UpdateClockLayout(clock_layout);
 }
 
-TimeView::~TimeView() = default;
-
-void TimeView::UpdateTextInternal(const base::Time& now) {
-  // Just in case |now| is null, do NOT update time; otherwise, it will
-  // crash icu code by calling into base::TimeFormatTimeOfDayWithHourClockType,
-  // see details in crbug.com/147570.
-  if (now.is_null()) {
-    LOG(ERROR) << "Received null value from base::Time |now| in argument";
-    return;
-  }
-
-  BaseDateTimeView::UpdateTextInternal(now);
-  base::string16 current_time = base::TimeFormatTimeOfDayWithHourClockType(
-      now, model_->hour_clock_type(), base::kDropAmPm);
-  horizontal_label_->SetText(current_time);
-  horizontal_label_->SetTooltipText(base::TimeFormatFriendlyDate(now));
-  horizontal_label_->NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged,
-                                              true);
-
-  // Calculate vertical clock layout labels.
-  size_t colon_pos = current_time.find(base::ASCIIToUTF16(":"));
-  base::string16 hour = current_time.substr(0, colon_pos);
-  base::string16 minute = current_time.substr(colon_pos + 1);
-
-  // Sometimes pad single-digit hours with a zero for aesthetic reasons.
-  if (hour.length() == 1 && model_->hour_clock_type() == base::k24HourClock &&
-      !base::i18n::IsRTL())
-    hour = base::ASCIIToUTF16("0") + hour;
-
-  vertical_label_hours_->SetText(hour);
-  vertical_label_minutes_->SetText(minute);
-  vertical_label_hours_->NotifyAccessibilityEvent(
-      ax::mojom::Event::kTextChanged, true);
-  vertical_label_minutes_->NotifyAccessibilityEvent(
-      ax::mojom::Event::kTextChanged, true);
-  Layout();
-}
-
-bool TimeView::PerformAction(const ui::Event& event) {
-  return false;
-}
-
-bool TimeView::OnMousePressed(const ui::MouseEvent& event) {
-  // Let the event fall through.
-  return false;
-}
-
-void TimeView::OnGestureEvent(ui::GestureEvent* event) {
-  // Skip gesture handling happening in Button so that the container views
-  // receive and handle them properly.
-  // TODO(mohsen): Refactor TimeView/DateView classes so that they are not
-  // ActionableView anymore. Create an ActionableView as a container for when
-  // needed.
+TimeView::~TimeView() {
+  model_->RemoveObserver(this);
+  timer_.Stop();
 }
 
 void TimeView::UpdateClockLayout(ClockLayout clock_layout) {
@@ -236,10 +108,103 @@
   set_color(vertical_label_minutes_);
 }
 
+void TimeView::OnDateFormatChanged() {
+  UpdateTimeFormat();
+}
+
+void TimeView::OnSystemClockTimeUpdated() {
+  UpdateTimeFormat();
+}
+
+void TimeView::OnSystemClockCanSetTimeChanged(bool can_set_time) {}
+
 void TimeView::Refresh() {
   UpdateText();
 }
 
+base::HourClockType TimeView::GetHourTypeForTesting() const {
+  return model_->hour_clock_type();
+}
+
+bool TimeView::PerformAction(const ui::Event& event) {
+  return false;
+}
+
+void TimeView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
+  ActionableView::GetAccessibleNodeData(node_data);
+  node_data->role = ax::mojom::Role::kTime;
+}
+
+void TimeView::ChildPreferredSizeChanged(views::View* child) {
+  PreferredSizeChanged();
+}
+
+bool TimeView::OnMousePressed(const ui::MouseEvent& event) {
+  // Let the event fall through.
+  return false;
+}
+
+void TimeView::OnGestureEvent(ui::GestureEvent* event) {
+  // Skip gesture handling happening in Button so that the container views
+  // receive and handle them properly.
+  // TODO(mohsen): Refactor TimeView/DateView classes so that they are not
+  // ActionableView anymore. Create an ActionableView as a container for when
+  // needed.
+}
+
+void TimeView::UpdateText() {
+  base::Time now = base::Time::Now();
+  UpdateTextInternal(now);
+  SchedulePaint();
+  SetTimer(now);
+}
+
+void TimeView::UpdateTimeFormat() {
+  UpdateText();
+}
+
+void TimeView::UpdateTextInternal(const base::Time& now) {
+  // Just in case |now| is null, do NOT update time; otherwise, it will
+  // crash icu code by calling into base::TimeFormatTimeOfDayWithHourClockType,
+  // see details in crbug.com/147570.
+  if (now.is_null()) {
+    LOG(ERROR) << "Received null value from base::Time |now| in argument";
+    return;
+  }
+
+  SetAccessibleName(base::TimeFormatTimeOfDayWithHourClockType(
+                        now, model_->hour_clock_type(), base::kKeepAmPm) +
+                    base::ASCIIToUTF16(", ") +
+                    base::TimeFormatFriendlyDate(now));
+
+  NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, true);
+
+  base::string16 current_time = base::TimeFormatTimeOfDayWithHourClockType(
+      now, model_->hour_clock_type(), base::kDropAmPm);
+  horizontal_label_->SetText(current_time);
+  horizontal_label_->SetTooltipText(base::TimeFormatFriendlyDate(now));
+  horizontal_label_->NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged,
+                                              true);
+
+  // Calculate vertical clock layout labels.
+  size_t colon_pos = current_time.find(base::ASCIIToUTF16(":"));
+  base::string16 hour = current_time.substr(0, colon_pos);
+  base::string16 minute = current_time.substr(colon_pos + 1);
+
+  // Sometimes pad single-digit hours with a zero for aesthetic reasons.
+  if (hour.length() == 1 && model_->hour_clock_type() == base::k24HourClock &&
+      !base::i18n::IsRTL())
+    hour = base::ASCIIToUTF16("0") + hour;
+
+  vertical_label_hours_->SetText(hour);
+  vertical_label_minutes_->SetText(minute);
+  vertical_label_hours_->NotifyAccessibilityEvent(
+      ax::mojom::Event::kTextChanged, true);
+  vertical_label_minutes_->NotifyAccessibilityEvent(
+      ax::mojom::Event::kTextChanged, true);
+  Layout();
+}
+
 void TimeView::SetupLabels() {
   horizontal_label_.reset(new views::Label());
   SetupLabel(horizontal_label_.get());
@@ -261,5 +226,27 @@
   label->SetElideBehavior(gfx::NO_ELIDE);
 }
 
+void TimeView::SetTimer(const base::Time& now) {
+  // Try to set the timer to go off at the next change of the minute. We don't
+  // want to have the timer go off more than necessary since that will cause
+  // the CPU to wake up and consume power.
+  base::Time::Exploded exploded;
+  now.LocalExplode(&exploded);
+
+  // Often this will be called at minute boundaries, and we'll actually want
+  // 60 seconds from now.
+  int seconds_left = 60 - exploded.second;
+  if (seconds_left == 0)
+    seconds_left = 60;
+
+  // Make sure that the timer fires on the next minute. Without this, if it is
+  // called just a teeny bit early, then it will skip the next minute.
+  seconds_left += kTimerSlopSeconds;
+
+  timer_.Stop();
+  timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(seconds_left), this,
+               &TimeView::UpdateText);
+}
+
 }  // namespace tray
 }  // namespace ash
diff --git a/ash/system/date/date_view.h b/ash/system/date/date_view.h
index a5ba987..bfbf61b9 100644
--- a/ash/system/date/date_view.h
+++ b/ash/system/date/date_view.h
@@ -30,55 +30,9 @@
 
 namespace tray {
 
-// Abstract base class containing common updating and layout code for the
-// DateView popup and the TimeView tray icon. Exported for tests.
-class ASH_EXPORT BaseDateTimeView : public ActionableView,
-                                    public ClockObserver {
- public:
-  ~BaseDateTimeView() override;
-
-  // Updates the displayed text for the current time and calls SetTimer().
-  void UpdateText();
-
-  // Updates the format of the displayed time.
-  void UpdateTimeFormat();
-
-  // views::View:
-  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-
-  // ClockObserver:
-  void OnDateFormatChanged() override;
-  void OnSystemClockTimeUpdated() override;
-  void OnSystemClockCanSetTimeChanged(bool can_set_time) override;
-  void Refresh() override;
-
-  base::HourClockType GetHourTypeForTesting() const;
-
- protected:
-  explicit BaseDateTimeView(ClockModel* model);
-
-  // Updates labels to display the current time.
-  virtual void UpdateTextInternal(const base::Time& now);
-
-  ClockModel* const model_;
-
- private:
-  // Starts |timer_| to schedule the next update.
-  void SetTimer(const base::Time& now);
-
-  // views::View:
-  void ChildPreferredSizeChanged(views::View* child) override;
-
-  // Invokes UpdateText() when the displayed time should change.
-  base::OneShotTimer timer_;
-
-  DISALLOW_COPY_AND_ASSIGN(BaseDateTimeView);
-};
-
 // Tray view used to display the current time.
 // Exported for tests.
-// TODO(tetsui): Combine with BaseDateTimeView.  https://crbug.com/901712
-class ASH_EXPORT TimeView : public BaseDateTimeView {
+class ASH_EXPORT TimeView : public ActionableView, public ClockObserver {
  public:
   enum class ClockLayout {
     HORIZONTAL_CLOCK,
@@ -95,24 +49,40 @@
   void SetTextColorBasedOnSession(session_manager::SessionState session_state);
 
   // ClockObserver:
+  void OnDateFormatChanged() override;
+  void OnSystemClockTimeUpdated() override;
+  void OnSystemClockCanSetTimeChanged(bool can_set_time) override;
   void Refresh() override;
 
+  base::HourClockType GetHourTypeForTesting() const;
+
  private:
   friend class TimeViewTest;
 
-  // BaseDateTimeView:
-  void UpdateTextInternal(const base::Time& now) override;
-
   // ActionableView:
   bool PerformAction(const ui::Event& event) override;
 
   // views::View:
+  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+  void ChildPreferredSizeChanged(views::View* child) override;
   bool OnMousePressed(const ui::MouseEvent& event) override;
   void OnGestureEvent(ui::GestureEvent* event) override;
 
+  // Updates the displayed text for the current time and calls SetTimer().
+  void UpdateText();
+
+  // Updates the format of the displayed time.
+  void UpdateTimeFormat();
+
+  // Updates labels to display the current time.
+  void UpdateTextInternal(const base::Time& now);
+
   void SetupLabels();
   void SetupLabel(views::Label* label);
 
+  // Starts |timer_| to schedule the next update.
+  void SetTimer(const base::Time& now);
+
   // Label text used for the normal horizontal shelf.
   std::unique_ptr<views::Label> horizontal_label_;
 
@@ -120,6 +90,11 @@
   std::unique_ptr<views::Label> vertical_label_hours_;
   std::unique_ptr<views::Label> vertical_label_minutes_;
 
+  // Invokes UpdateText() when the displayed time should change.
+  base::OneShotTimer timer_;
+
+  ClockModel* const model_;
+
   DISALLOW_COPY_AND_ASSIGN(TimeView);
 };
 
diff --git a/ash/system/network/tray_vpn.h b/ash/system/network/tray_vpn.h
deleted file mode 100644
index 445570d5f..0000000
--- a/ash/system/network/tray_vpn.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_SYSTEM_NETWORK_TRAY_VPN_H_
-#define ASH_SYSTEM_NETWORK_TRAY_VPN_H_
-
-namespace ash {
-namespace tray {
-
-// TODO(tetsui): Move them to VpnList.  https://crbug.com/901714
-extern bool IsVPNVisibleInSystemTray();
-extern bool IsVPNEnabled();
-extern bool IsVPNConnected();
-
-}  // namespace tray
-}  // namespace ash
-
-#endif  // ASH_SYSTEM_NETWORK_TRAY_VPN_H_
diff --git a/ash/system/network/vpn_feature_pod_controller.cc b/ash/system/network/vpn_feature_pod_controller.cc
index 20c9983c..fd44d4f 100644
--- a/ash/system/network/vpn_feature_pod_controller.cc
+++ b/ash/system/network/vpn_feature_pod_controller.cc
@@ -9,8 +9,8 @@
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/network/network_icon.h"
-#include "ash/system/network/tray_vpn.h"
 #include "ash/system/network/vpn_list.h"
+#include "ash/system/network/vpn_util.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/unified/feature_pod_button.h"
 #include "ash/system/unified/unified_system_tray_controller.h"
@@ -63,14 +63,14 @@
   if (!chromeos::NetworkHandler::IsInitialized())
     return;
 
-  button_->SetVisible(tray::IsVPNVisibleInSystemTray());
+  button_->SetVisible(vpn_util::IsVPNVisibleInSystemTray());
   if (!button_->visible())
     return;
 
   button_->SetSubLabel(l10n_util::GetStringUTF16(
-      tray::IsVPNConnected() ? IDS_ASH_STATUS_TRAY_VPN_CONNECTED_SHORT
-                             : IDS_ASH_STATUS_TRAY_VPN_DISCONNECTED_SHORT));
-  button_->SetToggled(tray::IsVPNEnabled() && tray::IsVPNConnected());
+      vpn_util::IsVPNConnected() ? IDS_ASH_STATUS_TRAY_VPN_CONNECTED_SHORT
+                                 : IDS_ASH_STATUS_TRAY_VPN_DISCONNECTED_SHORT));
+  button_->SetToggled(vpn_util::IsVPNEnabled() && vpn_util::IsVPNConnected());
 }
 
 }  // namespace ash
diff --git a/ash/system/network/tray_vpn.cc b/ash/system/network/vpn_util.cc
similarity index 94%
rename from ash/system/network/tray_vpn.cc
rename to ash/system/network/vpn_util.cc
index 15ab4b1..2d80e46 100644
--- a/ash/system/network/tray_vpn.cc
+++ b/ash/system/network/vpn_util.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/system/network/tray_vpn.h"
+#include "ash/system/network/vpn_util.h"
 
 #include "ash/session/session_controller.h"
 #include "ash/shell.h"
@@ -16,7 +16,7 @@
 using chromeos::NetworkTypePattern;
 
 namespace ash {
-namespace tray {
+namespace vpn_util {
 
 bool IsVPNVisibleInSystemTray() {
   LoginStatus login_status = Shell::Get()->session_controller()->login_status();
@@ -49,5 +49,5 @@
          (vpn->IsConnectedState() || vpn->IsConnectingState());
 }
 
-}  // namespace tray
+}  // namespace vpn_util
 }  // namespace ash
diff --git a/ash/system/network/vpn_util.h b/ash/system/network/vpn_util.h
new file mode 100644
index 0000000..d2fdc03
--- /dev/null
+++ b/ash/system/network/vpn_util.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_SYSTEM_NETWORK_VPN_UTIL_H_
+#define ASH_SYSTEM_NETWORK_VPN_UTIL_H_
+
+namespace ash {
+namespace vpn_util {
+
+extern bool IsVPNVisibleInSystemTray();
+extern bool IsVPNEnabled();
+extern bool IsVPNConnected();
+
+}  // namespace vpn_util
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_NETWORK_VPN_UTIL_H_
diff --git a/ash/wm/overview/window_selector.cc b/ash/wm/overview/window_selector.cc
index 90832b2d..bac7095 100644
--- a/ash/wm/overview/window_selector.cc
+++ b/ash/wm/overview/window_selector.cc
@@ -449,7 +449,8 @@
 
 void WindowSelector::InitiateDrag(WindowSelectorItem* item,
                                   const gfx::Point& location_in_screen) {
-  window_drag_controller_.reset(new OverviewWindowDragController(this));
+  window_drag_controller_ =
+      std::make_unique<OverviewWindowDragController>(this);
   window_drag_controller_->InitiateDrag(item, location_in_screen);
 
   for (std::unique_ptr<WindowGrid>& grid : grid_list_)
@@ -813,7 +814,10 @@
 void WindowSelector::ResetFocusRestoreWindow(bool focus) {
   if (!restore_focus_window_)
     return;
-  if (focus) {
+
+  // Ensure the window is still in the window hierarchy and not in the middle
+  // of teardown.
+  if (focus && restore_focus_window_->GetRootWindow()) {
     base::AutoReset<bool> restoring_focus(&ignore_activations_, true);
     wm::ActivateWindow(restore_focus_window_);
   }
diff --git a/ash/wm/overview/window_selector_controller_unittest.cc b/ash/wm/overview/window_selector_controller_unittest.cc
index 8a4398ee..0f3dab6 100644
--- a/ash/wm/overview/window_selector_controller_unittest.cc
+++ b/ash/wm/overview/window_selector_controller_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "ash/wm/overview/window_selector_controller.h"
 
+#include "ash/app_list/test/app_list_test_helper.h"
 #include "ash/shell.h"
 #include "ash/shell_observer.h"
 #include "ash/test/ash_test_base.h"
@@ -333,6 +334,18 @@
   EXPECT_EQ(OcclusionState::OCCLUDED, window2->occlusion_state());
 }
 
+// Tests that beginning window selection hides the app list.
+TEST_F(WindowSelectorControllerTest, SelectingHidesAppList) {
+  std::unique_ptr<aura::Window> window(CreateTestWindow());
+
+  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplay().id());
+  GetAppListTestHelper()->CheckVisibility(true);
+
+  Shell::Get()->window_selector_controller()->ToggleOverview();
+  GetAppListTestHelper()->WaitUntilIdle();
+  GetAppListTestHelper()->CheckVisibility(false);
+}
+
 class OverviewVirtualKeyboardTest : public WindowSelectorControllerTest {
  protected:
   void SetUp() override {
diff --git a/ash/wm/overview/window_selector_unittest.cc b/ash/wm/overview/window_selector_unittest.cc
index 6b8d991..303e13e 100644
--- a/ash/wm/overview/window_selector_unittest.cc
+++ b/ash/wm/overview/window_selector_unittest.cc
@@ -4,30 +4,22 @@
 
 #include <algorithm>
 #include <memory>
-#include <string>
 #include <vector>
 
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/accessibility/test_accessibility_controller_client.h"
 #include "ash/app_list/app_list_controller_impl.h"
-#include "ash/app_list/home_launcher_gesture_handler.h"
-#include "ash/app_list/test/app_list_test_helper.h"
 #include "ash/display/screen_orientation_controller.h"
 #include "ash/display/screen_orientation_controller_test_api.h"
 #include "ash/drag_drop/drag_drop_controller.h"
-#include "ash/public/cpp/app_list/app_list_constants.h"
-#include "ash/public/cpp/ash_features.h"
-#include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/screen_util.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_constants.h"
 #include "ash/shelf/shelf_view_test_api.h"
 #include "ash/shell.h"
-#include "ash/system/unified/unified_system_tray.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/overview/caption_container_view.h"
-#include "ash/wm/overview/cleanup_animation_observer.h"
 #include "ash/wm/overview/overview_constants.h"
 #include "ash/wm/overview/overview_utils.h"
 #include "ash/wm/overview/overview_window_drag_controller.h"
@@ -35,27 +27,25 @@
 #include "ash/wm/overview/window_selector.h"
 #include "ash/wm/overview/window_selector_controller.h"
 #include "ash/wm/overview/window_selector_item.h"
-#include "ash/wm/splitview/split_view_constants.h"
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/splitview/split_view_divider.h"
-#include "ash/wm/splitview/split_view_utils.h"
 #include "ash/wm/tablet_mode/tablet_mode_app_window_drag_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/wm_event.h"
 #include "ash/wm/workspace/workspace_window_resizer.h"
-#include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
 #include "base/stl_util.h"
-#include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/user_action_tester.h"
+#include "services/ws/public/mojom/window_tree_constants.mojom.h"
 #include "ui/aura/client/aura_constants.h"
-#include "ui/aura/client/focus_client.h"
 #include "ui/aura/client/window_types.h"
-#include "ui/aura/test/test_windows.h"
+#include "ui/aura/test/test_window_delegate.h"
 #include "ui/aura/window.h"
+#include "ui/aura/window_event_dispatcher.h"
+#include "ui/aura/window_tree_host.h"
 #include "ui/base/hit_test.h"
 #include "ui/compositor/layer_animation_sequence.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
@@ -69,6 +59,7 @@
 #include "ui/gfx/transform_util.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/label.h"
+#include "ui/views/widget/widget.h"
 #include "ui/wm/core/coordinate_conversion.h"
 #include "ui/wm/core/shadow_controller.h"
 #include "ui/wm/core/window_util.h"
@@ -79,20 +70,6 @@
 constexpr const char kActiveWindowChangedFromOverview[] =
     "WindowSelector_ActiveWindowChanged";
 
-// A simple window delegate that returns the specified hit-test code when
-// requested and applies a minimum size constraint if there is one.
-class TestDragWindowDelegate : public aura::test::TestWindowDelegate {
- public:
-  TestDragWindowDelegate() { set_window_component(HTCAPTION); }
-  ~TestDragWindowDelegate() override = default;
-
- private:
-  // Overridden from aura::Test::TestWindowDelegate:
-  void OnWindowDestroyed(aura::Window* window) override { delete this; }
-
-  DISALLOW_COPY_AND_ASSIGN(TestDragWindowDelegate);
-};
-
 // Helper function to get the index of |child|, given its parent window
 // |parent|.
 int IndexOf(aura::Window* child, aura::Window* parent) {
@@ -156,41 +133,18 @@
     WindowSelectorController::SetDoNotChangeWallpaperBlurForTests();
   }
 
-  aura::Window* CreateWindow(const gfx::Rect& bounds) {
-    aura::Window* window =
-        CreateTestWindowInShellWithDelegate(&delegate_, -1, bounds);
-    window->SetProperty(aura::client::kTopViewInset, kHeaderHeightDp);
-    return window;
-  }
-
-  aura::Window* CreateWindowWithId(const gfx::Rect& bounds, int id) {
-    aura::Window* window =
-        CreateTestWindowInShellWithDelegate(&delegate_, id, bounds);
-    window->SetProperty(aura::client::kTopViewInset, kHeaderHeightDp);
-    return window;
-  }
-
-  // Creates a Widget containing a Window with the given |bounds|. This should
-  // be used when the test requires a Widget. For example any test that will
-  // cause a window to be closed via
-  // views::Widget::GetWidgetForNativeView(window)->Close().
-  std::unique_ptr<views::Widget> CreateWindowWidget(const gfx::Rect& bounds) {
-    std::unique_ptr<views::Widget> widget(new views::Widget);
-    views::Widget::InitParams params;
-    params.bounds = bounds;
-    params.type = views::Widget::InitParams::TYPE_WINDOW;
-    params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
-    params.context = CurrentContext();
-    widget->Init(params);
-    widget->Show();
-    aura::Window* window = widget->GetNativeWindow();
-    window->SetProperty(aura::client::kTopViewInset, kHeaderHeightDp);
-    return widget;
+  // Enters tablet mode. Needed by tests that test dragging and or splitview,
+  // which are tablet mode only.
+  void EnterTabletMode() {
+    // Ensure calls to EnableTabletModeWindowManager complete.
+    base::RunLoop().RunUntilIdle();
+    Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
+    base::RunLoop().RunUntilIdle();
   }
 
   bool WindowsOverlapping(aura::Window* window1, aura::Window* window2) {
-    gfx::Rect window1_bounds = GetTransformedTargetBounds(window1);
-    gfx::Rect window2_bounds = GetTransformedTargetBounds(window2);
+    const gfx::Rect window1_bounds = GetTransformedTargetBounds(window1);
+    const gfx::Rect window2_bounds = GetTransformedTargetBounds(window2);
     return window1_bounds.Intersects(window2_bounds);
   }
 
@@ -209,8 +163,8 @@
 
   aura::Window* GetOverviewWindowForMinimizedState(int index,
                                                    aura::Window* window) {
-    WindowSelectorItem* selector = GetWindowItemForWindow(index, window);
-    return selector->GetOverviewWindowForMinimizedStateForTest();
+    WindowSelectorItem* item = GetWindowItemForWindow(index, window);
+    return item->GetOverviewWindowForMinimizedStateForTest();
   }
 
   gfx::Rect GetTransformedBounds(aura::Window* window) {
@@ -255,8 +209,8 @@
 
   void SendKey(ui::KeyboardCode key, int flags = ui::EF_NONE) {
     ui::test::EventGenerator event_generator(Shell::GetPrimaryRootWindow());
-    event_generator.PressKey(key, flags);
-    event_generator.ReleaseKey(key, flags);
+    GetEventGenerator()->PressKey(key, flags);
+    GetEventGenerator()->ReleaseKey(key, flags);
   }
 
   bool IsSelecting() { return window_selector_controller()->IsSelecting(); }
@@ -310,8 +264,8 @@
     return ws->grid_list_[ws->selected_grid_index_]->is_selecting();
   }
 
-  views::Widget* GetCloseButton(WindowSelectorItem* window) {
-    return window->caption_container_view_->GetCloseButton()->GetWidget();
+  views::ImageButton* GetCloseButton(WindowSelectorItem* window) {
+    return window->caption_container_view_->GetCloseButton();
   }
 
   views::Label* GetLabelView(WindowSelectorItem* window) {
@@ -325,15 +279,14 @@
   // Tests that a window is contained within a given WindowSelectorItem, and
   // that both the window and its matching close button are within the same
   // screen.
-  void IsWindowAndCloseButtonInScreen(aura::Window* window,
-                                      WindowSelectorItem* window_item) {
-    aura::Window* root_window = window_item->root_window();
+  void CheckWindowAndCloseButtonInScreen(aura::Window* window,
+                                         WindowSelectorItem* window_item) {
+    const gfx::Rect screen_bounds =
+        window_item->root_window()->GetBoundsInScreen();
     EXPECT_TRUE(window_item->Contains(window));
-    EXPECT_TRUE(root_window->GetBoundsInScreen().Contains(
-        GetTransformedTargetBounds(window)));
-    EXPECT_TRUE(
-        root_window->GetBoundsInScreen().Contains(GetTransformedTargetBounds(
-            GetCloseButton(window_item)->GetNativeView())));
+    EXPECT_TRUE(screen_bounds.Contains(GetTransformedTargetBounds(window)));
+    EXPECT_TRUE(screen_bounds.Contains(
+        GetCloseButton(window_item)->GetBoundsInScreen()));
   }
 
   void SetGridBounds(WindowGrid* grid, const gfx::Rect& bounds) {
@@ -364,7 +317,6 @@
   }
 
  private:
-  aura::test::TestWindowDelegate delegate_;
   std::unique_ptr<ShelfViewTestAPI> shelf_view_test_api_;
 
   DISALLOW_COPY_AND_ASSIGN(WindowSelectorTest);
@@ -372,12 +324,11 @@
 
 // Tests that an a11y alert is sent on entering overview mode.
 TEST_F(WindowSelectorTest, A11yAlertOnOverviewMode) {
-  const gfx::Rect bounds(400, 400);
   TestAccessibilityControllerClient client;
   AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->SetClient(client.CreateInterfacePtrAndBind());
-  std::unique_ptr<aura::Window> window(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window(CreateTestWindow());
   EXPECT_NE(mojom::AccessibilityAlert::WINDOW_OVERVIEW_MODE_ENTERED,
             client.last_a11y_alert());
   ToggleOverview();
@@ -391,10 +342,10 @@
 TEST_F(WindowSelectorTest, SmallDisplay) {
   UpdateDisplay("3x1");
   gfx::Rect bounds(0, 0, 1, 1);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window4(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds));
+  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds));
+  std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds));
+  std::unique_ptr<aura::Window> window4(CreateTestWindow(bounds));
   window1->SetProperty(aura::client::kTopViewInset, 0);
   window2->SetProperty(aura::client::kTopViewInset, 0);
   window3->SetProperty(aura::client::kTopViewInset, 0);
@@ -404,16 +355,19 @@
 
 // Tests entering overview mode with two windows and selecting one by clicking.
 TEST_F(WindowSelectorTest, Basic) {
-  const gfx::Rect bounds(400, 400);
+  // Overview disabled by default.
+  EXPECT_FALSE(IsSelecting());
+
   aura::Window* root_window = Shell::GetPrimaryRootWindow();
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
 
   EXPECT_TRUE(WindowsOverlapping(window1.get(), window2.get()));
-  wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window2.get());
   EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
   EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
   EXPECT_EQ(window2.get(), wm::GetFocusedWindow());
+
   // Hide the cursor before entering overview to test that it will be shown.
   aura::client::GetCursorClient(root_window)->HideCursor();
 
@@ -436,8 +390,7 @@
 
 // Tests activating minimized window.
 TEST_F(WindowSelectorTest, ActivateMinimized) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window(CreateTestWindow());
 
   wm::WindowState* window_state = wm::GetWindowState(window.get());
   wm::WMEvent minimize_event(wm::WM_EVENT_MINIMIZE);
@@ -459,9 +412,8 @@
   const gfx::Point point =
       GetTransformedBoundsInRootWindow(window_for_minimized_window)
           .CenterPoint();
-  ui::test::EventGenerator event_generator(
-      window_for_minimized_window->GetRootWindow(), point);
-  event_generator.ClickLeftButton();
+  GetEventGenerator()->set_current_screen_location(point);
+  GetEventGenerator()->ClickLeftButton();
 
   EXPECT_FALSE(IsSelecting());
 
@@ -473,10 +425,9 @@
 // Tests that the ordering of windows is stable across different overview
 // sessions even when the windows have the same bounds.
 TEST_F(WindowSelectorTest, WindowsOrder) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindowWithId(bounds, 1));
-  std::unique_ptr<aura::Window> window2(CreateWindowWithId(bounds, 2));
-  std::unique_ptr<aura::Window> window3(CreateWindowWithId(bounds, 3));
+  std::unique_ptr<aura::Window> window1(CreateTestWindowInShellWithId(1));
+  std::unique_ptr<aura::Window> window2(CreateTestWindowInShellWithId(2));
+  std::unique_ptr<aura::Window> window3(CreateTestWindowInShellWithId(3));
 
   // The order of windows in overview mode is MRU.
   wm::GetWindowState(window1.get())->Activate();
@@ -503,17 +454,14 @@
 
 // Tests selecting a window by tapping on it.
 TEST_F(WindowSelectorTest, BasicGesture) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  wm::ActivateWindow(window1.get());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  ::wm::ActivateWindow(window1.get());
   EXPECT_EQ(window1.get(), wm::GetFocusedWindow());
   ToggleOverview();
   EXPECT_EQ(window_selector()->GetOverviewFocusWindow(),
             wm::GetFocusedWindow());
-  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
-                                     window2.get());
-  generator.GestureTapAt(
+  GetEventGenerator()->GestureTapAt(
       GetTransformedTargetBounds(window2.get()).CenterPoint());
   EXPECT_EQ(window2.get(), wm::GetFocusedWindow());
 }
@@ -523,22 +471,19 @@
 // in overview mode which is different from the previously-active window.
 TEST_F(WindowSelectorTest, ActiveWindowChangedUserActionRecorded) {
   base::UserActionTester user_action_tester;
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  ::wm::ActivateWindow(window1.get());
+  ToggleOverview();
 
   // Tap on |window2| to activate it and exit overview.
-  wm::ActivateWindow(window1.get());
-  ToggleOverview();
-  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
-                                     window2.get());
-  generator.GestureTapAt(
+  GetEventGenerator()->GestureTapAt(
       GetTransformedTargetBounds(window2.get()).CenterPoint());
   EXPECT_EQ(
       1, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview));
 
   // Click on |window2| to activate it and exit overview.
-  wm::ActivateWindow(window1.get());
+  ::wm::ActivateWindow(window1.get());
   ToggleOverview();
   ClickWindow(window2.get());
   EXPECT_EQ(
@@ -546,7 +491,7 @@
 
   // Select |window2| using the arrow keys. Activate it (and exit overview) by
   // pressing the return key.
-  wm::ActivateWindow(window1.get());
+  ::wm::ActivateWindow(window1.get());
   ToggleOverview();
   ASSERT_TRUE(SelectWindow(window2.get()));
   SendKey(ui::VKEY_RETURN);
@@ -560,18 +505,13 @@
 // exiting overview without selecting a window does not record the action.
 TEST_F(WindowSelectorTest, ActiveWindowChangedUserActionNotRecorded) {
   base::UserActionTester user_action_tester;
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-
-  // Set |window1| to be initially active.
-  wm::ActivateWindow(window1.get());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  ::wm::ActivateWindow(window1.get());
   ToggleOverview();
 
   // Tap on |window1| to exit overview.
-  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
-                                     window1.get());
-  generator.GestureTapAt(
+  GetEventGenerator()->GestureTapAt(
       GetTransformedTargetBounds(window1.get()).CenterPoint());
   EXPECT_EQ(
       0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview));
@@ -603,18 +543,17 @@
 // recorded when overview mode exits as a result of closing its only window.
 TEST_F(WindowSelectorTest, ActiveWindowChangedUserActionWindowClose) {
   base::UserActionTester user_action_tester;
-  std::unique_ptr<views::Widget> widget =
-      CreateWindowWidget(gfx::Rect(400, 400));
+  std::unique_ptr<views::Widget> widget(CreateTestWidget(
+      nullptr, kShellWindowId_DefaultContainer, gfx::Rect(400, 400)));
 
   ToggleOverview();
-
   aura::Window* window = widget->GetNativeWindow();
-  gfx::Rect bounds = GetTransformedBoundsInRootWindow(window);
-  gfx::Point point(bounds.right() - 5, bounds.y() + 5);
-  ui::test::EventGenerator event_generator(window->GetRootWindow(), point);
-
+  const gfx::Point point = GetCloseButton(GetWindowItemForWindow(0, window))
+                               ->GetBoundsInScreen()
+                               .CenterPoint();
   ASSERT_FALSE(widget->IsClosed());
-  event_generator.ClickLeftButton();
+  GetEventGenerator()->set_current_screen_location(point);
+  GetEventGenerator()->ClickLeftButton();
   ASSERT_TRUE(widget->IsClosed());
   EXPECT_EQ(
       0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview));
@@ -624,73 +563,68 @@
 // is tapped while a finger is already down over a window.
 TEST_F(WindowSelectorTest, NoCrashWithDesktopTap) {
   std::unique_ptr<aura::Window> window(
-      CreateWindow(gfx::Rect(200, 300, 250, 450)));
+      CreateTestWindow(gfx::Rect(200, 300, 250, 450)));
 
   ToggleOverview();
 
-  gfx::Rect bounds = GetTransformedBoundsInRootWindow(window.get());
-  ui::test::EventGenerator event_generator(window->GetRootWindow(),
-                                           bounds.CenterPoint());
+  const gfx::Rect bounds = GetTransformedBoundsInRootWindow(window.get());
+  GetEventGenerator()->set_current_screen_location(bounds.CenterPoint());
 
   // Press down on the window.
   const int kTouchId = 19;
-  event_generator.PressTouchId(kTouchId);
+  GetEventGenerator()->PressTouchId(kTouchId);
 
   // Tap on the desktop, which should not cause a crash. Overview mode should
   // be disengaged.
-  event_generator.GestureTapAt(gfx::Point(0, 0));
+  GetEventGenerator()->GestureTapAt(gfx::Point(0, 0));
   EXPECT_FALSE(IsSelecting());
 
-  event_generator.ReleaseTouchId(kTouchId);
+  GetEventGenerator()->ReleaseTouchId(kTouchId);
 }
 
 // Tests that we do not crash and a window is selected when appropriate when
 // we click on a window during touch.
 TEST_F(WindowSelectorTest, ClickOnWindowDuringTouch) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  wm::ActivateWindow(window2.get());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  ::wm::ActivateWindow(window2.get());
   EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
   EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
 
   ToggleOverview();
 
   gfx::Rect window1_bounds = GetTransformedBoundsInRootWindow(window1.get());
-  ui::test::EventGenerator event_generator(window1->GetRootWindow(),
-                                           window1_bounds.CenterPoint());
+  GetEventGenerator()->set_current_screen_location(
+      window1_bounds.CenterPoint());
 
   // Clicking on |window2| while touching on |window1| should not cause a
   // crash, it should do nothing since overview only handles one click or touch
   // at a time.
   const int kTouchId = 19;
-  event_generator.PressTouchId(kTouchId);
-  event_generator.MoveMouseToCenterOf(window2.get());
-  event_generator.ClickLeftButton();
+  GetEventGenerator()->PressTouchId(kTouchId);
+  GetEventGenerator()->MoveMouseToCenterOf(window2.get());
+  GetEventGenerator()->ClickLeftButton();
   EXPECT_TRUE(IsSelecting());
   EXPECT_FALSE(wm::IsActiveWindow(window2.get()));
 
   // Clicking on |window1| while touching on |window1| should not cause
   // a crash, overview mode should be disengaged, and |window1| should
   // be active.
-  event_generator.MoveMouseToCenterOf(window1.get());
-  event_generator.ClickLeftButton();
+  GetEventGenerator()->MoveMouseToCenterOf(window1.get());
+  GetEventGenerator()->ClickLeftButton();
   EXPECT_FALSE(IsSelecting());
   EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
-  event_generator.ReleaseTouchId(kTouchId);
+  GetEventGenerator()->ReleaseTouchId(kTouchId);
 }
 
 // Tests that a window does not receive located events when in overview mode.
 TEST_F(WindowSelectorTest, WindowDoesNotReceiveEvents) {
-  gfx::Rect window_bounds(20, 10, 200, 300);
-  aura::Window* root_window = Shell::GetPrimaryRootWindow();
-  std::unique_ptr<aura::Window> window(CreateWindow(window_bounds));
-
-  gfx::Point point1(window_bounds.x() + 10, window_bounds.y() + 10);
-
+  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(400, 400)));
+  const gfx::Point point1 = window->bounds().CenterPoint();
   ui::MouseEvent event1(ui::ET_MOUSE_PRESSED, point1, point1,
                         ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
 
+  aura::Window* root_window = Shell::GetPrimaryRootWindow();
   ui::EventTarget* root_target = root_window;
   ui::EventTargeter* targeter =
       root_window->GetHost()->dispatcher()->GetDefaultEventTargeter();
@@ -702,8 +636,8 @@
   ToggleOverview();
 
   // The bounds have changed, take that into account.
-  gfx::Rect bounds = GetTransformedBoundsInRootWindow(window.get());
-  gfx::Point point2(bounds.x() + 10, bounds.y() + 10);
+  const gfx::Point point2 =
+      GetTransformedBoundsInRootWindow(window.get()).CenterPoint();
   ui::MouseEvent event2(ui::ET_MOUSE_PRESSED, point2, point2,
                         ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
 
@@ -713,37 +647,34 @@
 
 // Tests that clicking on the close button effectively closes the window.
 TEST_F(WindowSelectorTest, CloseButton) {
-  std::unique_ptr<views::Widget> widget =
-      CreateWindowWidget(gfx::Rect(0, 0, 400, 400));
-
-  std::unique_ptr<views::Widget> minimized_widget =
-      CreateWindowWidget(gfx::Rect(400, 0, 400, 400));
+  std::unique_ptr<views::Widget> widget(CreateTestWidget());
+  std::unique_ptr<views::Widget> minimized_widget(CreateTestWidget());
   minimized_widget->Minimize();
 
   ToggleOverview();
-
   aura::Window* window = widget->GetNativeWindow();
-  gfx::Rect bounds = GetTransformedBoundsInRootWindow(window);
-  gfx::Point point(bounds.right() - 5, bounds.y() + 5);
-  ui::test::EventGenerator event_generator(window->GetRootWindow(), point);
+  const gfx::Point point = GetCloseButton(GetWindowItemForWindow(0, window))
+                               ->GetBoundsInScreen()
+                               .CenterPoint();
+  GetEventGenerator()->set_current_screen_location(point);
 
   EXPECT_FALSE(widget->IsClosed());
-  event_generator.ClickLeftButton();
+  GetEventGenerator()->ClickLeftButton();
   EXPECT_TRUE(widget->IsClosed());
-
-  EXPECT_TRUE(IsSelecting());
+  ASSERT_TRUE(IsSelecting());
 
   aura::Window* window_for_minimized_window =
       GetOverviewWindowForMinimizedState(0,
                                          minimized_widget->GetNativeWindow());
   ASSERT_TRUE(window_for_minimized_window);
-  const gfx::Rect rect =
-      GetTransformedBoundsInRootWindow(window_for_minimized_window);
-
-  event_generator.MoveMouseTo(gfx::Point(rect.right() - 10, rect.y() - 10));
-
+  const gfx::Point point2 =
+      GetCloseButton(GetWindowItemForWindow(0, window_for_minimized_window))
+          ->GetBoundsInScreen()
+          .CenterPoint();
+  GetEventGenerator()->MoveMouseTo(point2);
   EXPECT_FALSE(minimized_widget->IsClosed());
-  event_generator.ClickLeftButton();
+
+  GetEventGenerator()->ClickLeftButton();
   EXPECT_TRUE(minimized_widget->IsClosed());
 
   // All minimized windows are closed, so it should exit overview mode.
@@ -753,22 +684,19 @@
 
 // Tests minimizing/unminimizing in overview mode.
 TEST_F(WindowSelectorTest, MinimizeUnminimize) {
-  std::unique_ptr<views::Widget> widget =
-      CreateWindowWidget(gfx::Rect(400, 400));
+  std::unique_ptr<views::Widget> widget(CreateTestWidget());
   aura::Window* window = widget->GetNativeWindow();
 
   ToggleOverview();
-
   EXPECT_FALSE(GetOverviewWindowForMinimizedState(0, window));
+
   widget->Minimize();
   EXPECT_TRUE(widget->IsMinimized());
   EXPECT_TRUE(IsSelecting());
-
   EXPECT_TRUE(GetOverviewWindowForMinimizedState(0, window));
 
   widget->Restore();
   EXPECT_FALSE(widget->IsMinimized());
-
   EXPECT_FALSE(GetOverviewWindowForMinimizedState(0, window));
   EXPECT_TRUE(IsSelecting());
 }
@@ -777,30 +705,20 @@
 // closes the window.
 TEST_F(WindowSelectorTest, CloseButtonOnMultipleDisplay) {
   UpdateDisplay("600x400,600x400");
-  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
-
-  std::unique_ptr<aura::Window> window1(
-      CreateWindow(gfx::Rect(650, 300, 250, 450)));
 
   // We need a widget for the close button to work because windows are closed
   // via the widget. We also use the widget to determine if the window has been
-  // closed or not. We explicity create the widget so that the window can be
-  // parented to a non-primary root window.
-  std::unique_ptr<views::Widget> widget(new views::Widget);
-  views::Widget::InitParams params;
-  params.bounds = gfx::Rect(650, 0, 400, 400);
-  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
-  params.parent = window1->parent();
-  widget->Init(params);
-  widget->Show();
-  aura::Window* window = widget->GetNativeWindow();
-  window->SetProperty(aura::client::kTopViewInset, kHeaderHeightDp);
-
-  ASSERT_EQ(root_windows[1], window1->GetRootWindow());
+  // closed or not. Parent the window to a window in a non-primary root window.
+  std::unique_ptr<aura::Window> window(
+      CreateTestWindow(gfx::Rect(650, 300, 250, 450)));
+  std::unique_ptr<views::Widget> widget(CreateTestWidget());
+  widget->SetBounds(gfx::Rect(650, 0, 400, 400));
+  aura::Window* window2 = widget->GetNativeWindow();
+  window2->SetProperty(aura::client::kTopViewInset, kHeaderHeightDp);
+  views::Widget::ReparentNativeView(window2, window->parent());
+  ASSERT_EQ(Shell::GetAllRootWindows()[1], window2->GetRootWindow());
 
   ToggleOverview();
-
-  aura::Window* window2 = widget->GetNativeWindow();
   gfx::Rect bounds = GetTransformedBoundsInRootWindow(window2);
   gfx::Point point(bounds.right() - 5, bounds.y() + 5);
   ui::test::EventGenerator event_generator(window2->GetRootWindow(), point);
@@ -812,10 +730,9 @@
 
 // Tests entering overview mode with two windows and selecting one.
 TEST_F(WindowSelectorTest, FullscreenWindow) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  wm::ActivateWindow(window1.get());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  ::wm::ActivateWindow(window1.get());
 
   const wm::WMEvent toggle_fullscreen_event(wm::WM_EVENT_TOGGLE_FULLSCREEN);
   wm::GetWindowState(window1.get())->OnWMEvent(&toggle_fullscreen_event);
@@ -823,8 +740,7 @@
 
   // Enter overview and select the fullscreen window.
   ToggleOverview();
-
-  // The window is still fullscreen as it was selected.
+  ClickWindow(window1.get());
   EXPECT_TRUE(wm::GetWindowState(window1.get())->IsFullscreen());
 
   // Entering overview and selecting another window, the previous window remains
@@ -834,37 +750,18 @@
   EXPECT_TRUE(wm::GetWindowState(window1.get())->IsFullscreen());
 }
 
-TEST_F(WindowSelectorTest, SkipOverviewWindow) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-
-  window2->SetProperty(ash::kHideInOverviewKey, true);
-
-  // Enter overview.
-  ToggleOverview();
-  EXPECT_TRUE(window1->IsVisible());
-  EXPECT_FALSE(window2->IsVisible());
-
-  // Exit overview.
-  ToggleOverview();
-  base::RunLoop().RunUntilIdle();
-
-  EXPECT_TRUE(window1->IsVisible());
-  EXPECT_TRUE(window2->IsVisible());
-}
-
 // Tests that entering overview when a fullscreen window is active in maximized
 // mode correctly applies the transformations to the window and correctly
 // updates the window bounds on exiting overview mode: http://crbug.com/401664.
 TEST_F(WindowSelectorTest, FullscreenWindowTabletMode) {
   UpdateDisplay("800x600");
   const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
-  wm::ActivateWindow(window2.get());
-  wm::ActivateWindow(window1.get());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds));
+  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds));
+  ::wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window1.get());
+
+  EnterTabletMode();
   gfx::Rect normal_window_bounds(window1->bounds());
   const wm::WMEvent toggle_fullscreen_event(wm::WM_EVENT_TOGGLE_FULLSCREEN);
   wm::GetWindowState(window1.get())->OnWMEvent(&toggle_fullscreen_event);
@@ -913,28 +810,28 @@
             screen->GetDisplayNearestWindow(window1.get()).work_area());
 }
 
-// Tests that beginning window selection hides the app list.
-TEST_F(WindowSelectorTest, SelectingHidesAppList) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
+TEST_F(WindowSelectorTest, SkipOverviewWindow) {
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  window2->SetProperty(ash::kHideInOverviewKey, true);
 
-  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplay().id());
-  GetAppListTestHelper()->CheckVisibility(true);
+  // Enter overview.
+  ToggleOverview();
+  EXPECT_TRUE(window1->IsVisible());
+  EXPECT_FALSE(window2->IsVisible());
 
+  // Exit overview.
   ToggleOverview();
-  GetAppListTestHelper()->WaitUntilIdle();
-  GetAppListTestHelper()->CheckVisibility(false);
-  ToggleOverview();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(window1->IsVisible());
+  EXPECT_TRUE(window2->IsVisible());
 }
 
 // Tests that a minimized window's visibility and layer visibility
 // stay invisible (A minimized window is cloned during overview).
 TEST_F(WindowSelectorTest, MinimizedWindowState) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  wm::WindowState* window_state = wm::GetWindowState(window1.get());
-  window_state->Minimize();
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  wm::GetWindowState(window1.get())->Minimize();
   EXPECT_FALSE(window1->IsVisible());
   EXPECT_FALSE(window1->layer()->GetTargetVisibility());
 
@@ -949,38 +846,33 @@
 
 // Tests that a bounds change during overview is corrected for.
 TEST_F(WindowSelectorTest, BoundsChangeDuringOverview) {
-  std::unique_ptr<aura::Window> window(CreateWindow(gfx::Rect(0, 0, 400, 400)));
+  std::unique_ptr<aura::Window> window(
+      CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect(400, 400)));
   // Use overview headers above the window in this test.
   window->SetProperty(aura::client::kTopViewInset, 0);
   ToggleOverview();
   gfx::Rect overview_bounds = GetTransformedTargetBounds(window.get());
   window->SetBounds(gfx::Rect(200, 0, 200, 200));
   gfx::Rect new_overview_bounds = GetTransformedTargetBounds(window.get());
-  EXPECT_EQ(overview_bounds.x(), new_overview_bounds.x());
-  EXPECT_EQ(overview_bounds.y(), new_overview_bounds.y());
-  EXPECT_EQ(overview_bounds.width(), new_overview_bounds.width());
-  EXPECT_EQ(overview_bounds.height(), new_overview_bounds.height());
+  EXPECT_EQ(overview_bounds, new_overview_bounds);
   ToggleOverview();
 }
 
 // Tests that a newly created window aborts overview.
 TEST_F(WindowSelectorTest, NewWindowCancelsOverview) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
   ToggleOverview();
   EXPECT_TRUE(IsSelecting());
 
   // A window being created should exit overview mode.
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
   EXPECT_FALSE(IsSelecting());
 }
 
 // Tests that a window activation exits overview mode.
 TEST_F(WindowSelectorTest, ActivationCancelsOverview) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
   window2->Focus();
   ToggleOverview();
   EXPECT_TRUE(IsSelecting());
@@ -997,9 +889,8 @@
 // Tests that exiting overview mode without selecting a window restores focus
 // to the previously focused window.
 TEST_F(WindowSelectorTest, CancelRestoresFocus) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window(CreateWindow(bounds));
-  wm::ActivateWindow(window.get());
+  std::unique_ptr<aura::Window> window(CreateTestWindow());
+  ::wm::ActivateWindow(window.get());
   EXPECT_EQ(window.get(), wm::GetFocusedWindow());
 
   // In overview mode, the overview focus window should be focused.
@@ -1014,9 +905,8 @@
 
 // Tests that overview mode is exited if the last remaining window is destroyed.
 TEST_F(WindowSelectorTest, LastWindowDestroyed) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
   ToggleOverview();
 
   window1.reset();
@@ -1024,11 +914,33 @@
   EXPECT_FALSE(IsSelecting());
 }
 
+// Regression test for crash when closing the last overview mode window under
+// SingleProcessMash. https://crbug.com/922293.
+TEST_F(WindowSelectorTest, DontRestoreFocusToUnparentedWindow) {
+  const gfx::Rect bounds(400, 400);
+  std::unique_ptr<aura::Window> parent = CreateTestWindow(bounds);
+  std::unique_ptr<aura::Window> child = CreateChildWindow(parent.get(), bounds);
+
+  // Enter overview with a focused child window. This simulates an app window
+  // web contents RenderWidgetHostViewAura.
+  child->Focus();
+  ToggleOverview();
+
+  // Simulate the asynchronous window teardown for used by client widgets.
+  // Hierarchy changes are processed first, so the child is removed from its
+  // parent, then the windows are destroyed.
+  parent->RemoveChild(child.get());
+  parent.reset();
+  child.reset();
+
+  // Overview mode exits without crashing.
+  EXPECT_FALSE(IsSelecting());
+}
+
 // Tests that entering overview mode restores a window to its original
 // target location.
 TEST_F(WindowSelectorTest, QuickReentryRestoresInitialTransform) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(400, 400)));
   gfx::Rect initial_bounds = GetTransformedBounds(window.get());
   ToggleOverview();
   // Quickly exit and reenter overview mode. The window should still be
@@ -1050,39 +962,38 @@
 // child even though not activatable themselves.
 TEST_F(WindowSelectorTest, ModalChild) {
   const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> child1(CreateWindow(bounds));
-  child1->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
-  ::wm::AddTransientChild(window1.get(), child1.get());
-  EXPECT_EQ(window1->parent(), child1->parent());
+  std::unique_ptr<aura::Window> window(CreateTestWindow(bounds));
+  std::unique_ptr<aura::Window> child(CreateTestWindow(bounds));
+  child->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
+  ::wm::AddTransientChild(window.get(), child.get());
+  EXPECT_EQ(window->parent(), child->parent());
   ToggleOverview();
-  EXPECT_TRUE(window1->IsVisible());
-  EXPECT_TRUE(child1->IsVisible());
-  EXPECT_EQ(GetTransformedTargetBounds(child1.get()),
-            GetTransformedTargetBounds(window1.get()));
+  EXPECT_TRUE(window->IsVisible());
+  EXPECT_TRUE(child->IsVisible());
+  EXPECT_EQ(GetTransformedTargetBounds(child.get()),
+            GetTransformedTargetBounds(window.get()));
   ToggleOverview();
 }
 
 // Tests that clicking a modal window's parent activates the modal window in
 // overview.
 TEST_F(WindowSelectorTest, ClickModalWindowParent) {
-  std::unique_ptr<aura::Window> window1(
-      CreateWindow(gfx::Rect(0, 0, 180, 180)));
-  std::unique_ptr<aura::Window> child1(
-      CreateWindow(gfx::Rect(200, 0, 180, 180)));
-  child1->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
-  ::wm::AddTransientChild(window1.get(), child1.get());
-  EXPECT_FALSE(WindowsOverlapping(window1.get(), child1.get()));
-  EXPECT_EQ(window1->parent(), child1->parent());
+  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(180, 180)));
+  std::unique_ptr<aura::Window> child(
+      CreateTestWindow(gfx::Rect(200, 0, 180, 180)));
+  child->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
+  ::wm::AddTransientChild(window.get(), child.get());
+  EXPECT_FALSE(WindowsOverlapping(window.get(), child.get()));
+  EXPECT_EQ(window->parent(), child->parent());
   ToggleOverview();
   // Given that their relative positions are preserved, the windows should still
   // not overlap.
-  EXPECT_FALSE(WindowsOverlapping(window1.get(), child1.get()));
-  ClickWindow(window1.get());
+  EXPECT_FALSE(WindowsOverlapping(window.get(), child.get()));
+  ClickWindow(window.get());
   EXPECT_FALSE(IsSelecting());
 
   // Clicking on window1 should activate child1.
-  EXPECT_TRUE(wm::IsActiveWindow(child1.get()));
+  EXPECT_TRUE(wm::IsActiveWindow(child.get()));
 }
 
 // Tests that windows remain on the display they are currently on in overview
@@ -1093,10 +1004,10 @@
   gfx::Rect bounds1(0, 0, 400, 400);
   gfx::Rect bounds2(650, 0, 400, 400);
 
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds1));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds1));
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds2));
-  std::unique_ptr<aura::Window> window4(CreateWindow(bounds2));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds1));
+  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds1));
+  std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds2));
+  std::unique_ptr<aura::Window> window4(CreateTestWindow(bounds2));
   EXPECT_EQ(root_windows[0], window1->GetRootWindow());
   EXPECT_EQ(root_windows[0], window2->GetRootWindow());
   EXPECT_EQ(root_windows[1], window3->GetRootWindow());
@@ -1110,26 +1021,25 @@
   EXPECT_EQ(root_windows[1], window4->GetRootWindow());
 
   // Window indices are based on top-down order. The reverse of our creation.
-  IsWindowAndCloseButtonInScreen(window1.get(),
-                                 GetWindowItemForWindow(0, window1.get()));
-  IsWindowAndCloseButtonInScreen(window2.get(),
-                                 GetWindowItemForWindow(0, window2.get()));
-  IsWindowAndCloseButtonInScreen(window3.get(),
-                                 GetWindowItemForWindow(1, window3.get()));
-  IsWindowAndCloseButtonInScreen(window4.get(),
-                                 GetWindowItemForWindow(1, window4.get()));
+  CheckWindowAndCloseButtonInScreen(window1.get(),
+                                    GetWindowItemForWindow(0, window1.get()));
+  CheckWindowAndCloseButtonInScreen(window2.get(),
+                                    GetWindowItemForWindow(0, window2.get()));
+  CheckWindowAndCloseButtonInScreen(window3.get(),
+                                    GetWindowItemForWindow(1, window3.get()));
+  CheckWindowAndCloseButtonInScreen(window4.get(),
+                                    GetWindowItemForWindow(1, window4.get()));
 }
 
 // Tests shutting down during overview.
 TEST_F(WindowSelectorTest, Shutdown) {
-  const gfx::Rect bounds(400, 400);
   // These windows will be deleted when the test exits and the Shell instance
   // is shut down.
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
 
-  wm::ActivateWindow(window2.get());
-  wm::ActivateWindow(window1.get());
+  ::wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window1.get());
 
   ToggleOverview();
 }
@@ -1137,17 +1047,16 @@
 // Tests removing a display during overview.
 TEST_F(WindowSelectorTest, RemoveDisplay) {
   UpdateDisplay("400x400,400x400");
-  gfx::Rect bounds1(0, 0, 100, 100);
-  gfx::Rect bounds2(450, 0, 100, 100);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds1));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds2));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow(gfx::Rect(100, 100)));
+  std::unique_ptr<aura::Window> window2(
+      CreateTestWindow(gfx::Rect(450, 0, 100, 100)));
 
   aura::Window::Windows root_windows = Shell::GetAllRootWindows();
   EXPECT_EQ(root_windows[0], window1->GetRootWindow());
   EXPECT_EQ(root_windows[1], window2->GetRootWindow());
 
-  wm::ActivateWindow(window2.get());
-  wm::ActivateWindow(window1.get());
+  ::wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window1.get());
 
   ToggleOverview();
   EXPECT_TRUE(IsSelecting());
@@ -1158,17 +1067,16 @@
 // Tests removing a display during overview with NON_ZERO_DURATION animation.
 TEST_F(WindowSelectorTest, RemoveDisplayWithAnimation) {
   UpdateDisplay("400x400,400x400");
-  gfx::Rect bounds1(0, 0, 100, 100);
-  gfx::Rect bounds2(450, 0, 100, 100);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds1));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds2));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow(gfx::Rect(100, 100)));
+  std::unique_ptr<aura::Window> window2(
+      CreateTestWindow(gfx::Rect(450, 0, 100, 100)));
 
   aura::Window::Windows root_windows = Shell::GetAllRootWindows();
   EXPECT_EQ(root_windows[0], window1->GetRootWindow());
   EXPECT_EQ(root_windows[1], window2->GetRootWindow());
 
-  wm::ActivateWindow(window2.get());
-  wm::ActivateWindow(window1.get());
+  ::wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window1.get());
 
   ToggleOverview();
   EXPECT_TRUE(IsSelecting());
@@ -1179,35 +1087,52 @@
   EXPECT_FALSE(IsSelecting());
 }
 
+namespace {
+
+// A simple window delegate that returns the specified hit-test code when
+// requested and applies a minimum size constraint if there is one.
+class TestDragWindowDelegate : public aura::test::TestWindowDelegate {
+ public:
+  TestDragWindowDelegate() { set_window_component(HTCAPTION); }
+  ~TestDragWindowDelegate() override = default;
+
+ private:
+  // aura::Test::TestWindowDelegate:
+  void OnWindowDestroyed(aura::Window* window) override { delete this; }
+
+  DISALLOW_COPY_AND_ASSIGN(TestDragWindowDelegate);
+};
+
+}  // namespace
+
 // Tests that toggling overview on and off does not cancel drag.
 TEST_F(WindowSelectorTest, DragDropInProgress) {
-  gfx::Rect bounds(0, 0, 100, 100);
   std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
-      new TestDragWindowDelegate(), -1, bounds));
+      new TestDragWindowDelegate(), -1, gfx::Rect(100, 100)));
 
-  ui::test::EventGenerator event_generator(window->GetRootWindow(),
-                                           window.get());
-  event_generator.PressLeftButton();
-  event_generator.MoveMouseBy(10, 10);
+  GetEventGenerator()->set_current_screen_location(
+      window->GetBoundsInScreen().CenterPoint());
+  GetEventGenerator()->PressLeftButton();
+  GetEventGenerator()->MoveMouseBy(10, 10);
   EXPECT_EQ(gfx::Rect(10, 10, 100, 100), window->bounds());
 
   ToggleOverview();
   ASSERT_TRUE(IsSelecting());
 
-  event_generator.MoveMouseBy(10, 10);
+  GetEventGenerator()->MoveMouseBy(10, 10);
 
   ToggleOverview();
   ASSERT_FALSE(IsSelecting());
 
-  event_generator.MoveMouseBy(10, 10);
-  event_generator.ReleaseLeftButton();
+  GetEventGenerator()->MoveMouseBy(10, 10);
+  GetEventGenerator()->ReleaseLeftButton();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(gfx::Rect(30, 30, 100, 100), window->bounds());
 }
 
 // Test that a label is created under the window on entering overview mode.
 TEST_F(WindowSelectorTest, CreateLabelUnderWindow) {
-  std::unique_ptr<aura::Window> window(CreateWindow(gfx::Rect(0, 0, 300, 500)));
+  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(300, 500)));
   const base::string16 window_title = base::UTF8ToUTF16("My window");
   window->SetTitle(window_title);
   ToggleOverview();
@@ -1235,11 +1160,12 @@
 TEST_F(WindowSelectorTest, DisplayOrientationChanged) {
   aura::Window* root_window = Shell::Get()->GetPrimaryRootWindow();
   UpdateDisplay("600x200");
-  EXPECT_EQ("0,0 600x200", root_window->bounds().ToString());
-  gfx::Rect window_bounds(0, 0, 150, 150);
+  EXPECT_EQ(gfx::Rect(600, 200), root_window->bounds());
   std::vector<std::unique_ptr<aura::Window>> windows;
-  for (int i = 0; i < 3; i++)
-    windows.push_back(base::WrapUnique(CreateWindow(window_bounds)));
+  for (int i = 0; i < 3; i++) {
+    windows.push_back(
+        std::unique_ptr<aura::Window>(CreateTestWindow(gfx::Rect(150, 150))));
+  }
 
   ToggleOverview();
   for (const auto& window : windows) {
@@ -1250,7 +1176,7 @@
   // Rotate the display, windows should be repositioned to be within the screen
   // bounds.
   UpdateDisplay("600x200/r");
-  EXPECT_EQ("0,0 200x600", root_window->bounds().ToString());
+  EXPECT_EQ(gfx::Rect(200, 600), root_window->bounds());
   for (const auto& window : windows) {
     EXPECT_TRUE(root_window->bounds().Contains(
         GetTransformedTargetBounds(window.get())));
@@ -1259,9 +1185,8 @@
 
 // Tests traversing some windows in overview mode with the tab key.
 TEST_F(WindowSelectorTest, BasicTabKeyNavigation) {
-  gfx::Rect bounds(0, 0, 100, 100);
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
   ToggleOverview();
 
   const std::vector<std::unique_ptr<WindowSelectorItem>>& overview_windows =
@@ -1276,15 +1201,11 @@
 
 // Tests that pressing Ctrl+W while a window is selected in overview closes it.
 TEST_F(WindowSelectorTest, CloseWindowWithKey) {
-  gfx::Rect bounds(0, 0, 100, 100);
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  std::unique_ptr<views::Widget> widget =
-      CreateWindowWidget(gfx::Rect(0, 0, 400, 400));
-  aura::Window* window1 = widget->GetNativeWindow();
+  std::unique_ptr<views::Widget> widget(CreateTestWidget());
   ToggleOverview();
 
   SendKey(ui::VKEY_RIGHT);
-  EXPECT_EQ(window1, GetSelectedWindow());
+  EXPECT_EQ(widget->GetNativeWindow(), GetSelectedWindow());
   SendKey(ui::VKEY_W, ui::EF_CONTROL_DOWN);
   EXPECT_TRUE(widget->IsClosed());
 }
@@ -1297,7 +1218,7 @@
   std::vector<std::unique_ptr<aura::Window>> windows;
   for (size_t i = test_windows; i > 0; i--) {
     windows.push_back(
-        base::WrapUnique(CreateWindowWithId(gfx::Rect(0, 0, 100, 100), i)));
+        std::unique_ptr<aura::Window>(CreateTestWindowInShellWithId(i)));
   }
 
   ui::KeyboardCode arrow_keys[] = {ui::VKEY_RIGHT, ui::VKEY_DOWN, ui::VKEY_LEFT,
@@ -1327,7 +1248,7 @@
   }
 }
 
-// Verifies hitting the escape and back keys exit overview mode.
+// Tests hitting the escape and back keys exit overview mode.
 TEST_F(WindowSelectorTest, ExitOverviewWithKey) {
   std::unique_ptr<aura::Window> window1(CreateTestWindow());
   std::unique_ptr<aura::Window> window2(CreateTestWindow());
@@ -1362,12 +1283,12 @@
 // Tests basic selection across multiple monitors.
 TEST_F(WindowSelectorTest, BasicMultiMonitorArrowKeyNavigation) {
   UpdateDisplay("400x400,400x400");
-  gfx::Rect bounds1(0, 0, 100, 100);
-  gfx::Rect bounds2(450, 0, 100, 100);
-  std::unique_ptr<aura::Window> window4(CreateWindow(bounds2));
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds2));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds1));
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds1));
+  const gfx::Rect bounds1(100, 100);
+  const gfx::Rect bounds2(450, 0, 100, 100);
+  std::unique_ptr<aura::Window> window4(CreateTestWindow(bounds2));
+  std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds2));
+  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds1));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds1));
 
   ToggleOverview();
 
@@ -1393,10 +1314,9 @@
       display::test::CreateDisplayLayout(display_manager(),
                                          display::DisplayPlacement::LEFT, 0));
   aura::Window::Windows root_windows = Shell::GetAllRootWindows();
-  gfx::Rect bounds1(-350, 0, 100, 100);
-  gfx::Rect bounds2(0, 0, 100, 100);
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds2));
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds1));
+  std::unique_ptr<aura::Window> window2(CreateTestWindow(gfx::Rect(100, 100)));
+  std::unique_ptr<aura::Window> window1(
+      CreateTestWindow(gfx::Rect(-350, 0, 100, 100)));
   EXPECT_EQ(root_windows[1], window1->GetRootWindow());
   EXPECT_EQ(root_windows[0], window2->GetRootWindow());
 
@@ -1407,6 +1327,7 @@
   SendKey(ui::VKEY_RIGHT);
   EXPECT_EQ(GetSelectedWindow(), window1.get());
 
+  // Exit and reenter overview.
   ToggleOverview();
   ToggleOverview();
 
@@ -1420,12 +1341,11 @@
 TEST_F(WindowSelectorTest, ThreeMonitor) {
   UpdateDisplay("400x400,400x400,400x400");
   aura::Window::Windows root_windows = Shell::GetAllRootWindows();
-  const gfx::Rect bounds1(0, 0, 100, 100);
-  const gfx::Rect bounds2(400, 0, 100, 100);
-  const gfx::Rect bounds3(800, 0, 100, 100);
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds3));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds2));
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds1));
+  std::unique_ptr<aura::Window> window3(
+      CreateTestWindow(gfx::Rect(800, 0, 100, 100)));
+  std::unique_ptr<aura::Window> window2(
+      CreateTestWindow(gfx::Rect(400, 0, 100, 100)));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow(gfx::Rect(100, 100)));
   EXPECT_EQ(root_windows[0], window1->GetRootWindow());
   EXPECT_EQ(root_windows[1], window2->GetRootWindow());
   EXPECT_EQ(root_windows[2], window3->GetRootWindow());
@@ -1443,7 +1363,7 @@
   EXPECT_EQ(GetSelectedWindow(), window2.get());
   ToggleOverview();
 
-  window3.reset(CreateWindow(bounds3));
+  window3 = CreateTestWindow(gfx::Rect(800, 0, 100, 100));
   ToggleOverview();
   SendKey(ui::VKEY_RIGHT);
   SendKey(ui::VKEY_RIGHT);
@@ -1458,9 +1378,8 @@
 
 // Tests selecting a window in overview mode with the return key.
 TEST_F(WindowSelectorTest, SelectWindowWithReturnKey) {
-  gfx::Rect bounds(0, 0, 100, 100);
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
   ToggleOverview();
 
   // Pressing the return key without a selection widget should not do anything.
@@ -1481,68 +1400,54 @@
   EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
 }
 
-// Tests clicking on the desktop itself to cancel overview mode.
 TEST_F(WindowSelectorTest, CancelOverviewOnMouseClick) {
-  // Overview disabled by default.
-  EXPECT_FALSE(IsSelecting());
-
   // Point and bounds selected so that they don't intersect. This causes
   // events located at the point to be passed to WallpaperController,
   // and not the window.
-  gfx::Point point_in_background_page(0, 0);
-  gfx::Rect bounds(10, 10, 100, 100);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  ui::test::EventGenerator* generator = GetEventGenerator();
+  const gfx::Point point_in_background_page(0, 0);
+  std::unique_ptr<aura::Window> window(
+      CreateTestWindow(gfx::Rect(10, 10, 100, 100)));
   // Move mouse to point in the background page. Sending an event here will pass
   // it to the WallpaperController in both regular and overview mode.
-  generator->MoveMouseTo(point_in_background_page);
+  GetEventGenerator()->MoveMouseTo(point_in_background_page);
 
   // Clicking on the background page while not in overview should not toggle
   // overview.
-  generator->ClickLeftButton();
+  GetEventGenerator()->ClickLeftButton();
   EXPECT_FALSE(IsSelecting());
 
-  // Switch to overview mode.
+  // Switch to overview mode. Clicking should now exit overview mode.
   ToggleOverview();
   ASSERT_TRUE(IsSelecting());
-
-  // Click should now exit overview mode.
-  generator->ClickLeftButton();
+  GetEventGenerator()->ClickLeftButton();
   EXPECT_FALSE(IsSelecting());
 }
 
 // Tests tapping on the desktop itself to cancel overview mode.
 TEST_F(WindowSelectorTest, CancelOverviewOnTap) {
-  // Overview disabled by default.
-  EXPECT_FALSE(IsSelecting());
-
   // Point and bounds selected so that they don't intersect. This causes
   // events located at the point to be passed to WallpaperController,
   // and not the window.
   gfx::Point point_in_background_page(0, 0);
-  gfx::Rect bounds(10, 10, 100, 100);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  ui::test::EventGenerator* generator = GetEventGenerator();
+  std::unique_ptr<aura::Window> window(
+      CreateTestWindow(gfx::Rect(10, 10, 100, 100)));
 
   // Tapping on the background page while not in overview should not toggle
   // overview.
-  generator->GestureTapAt(point_in_background_page);
+  GetEventGenerator()->GestureTapAt(point_in_background_page);
   EXPECT_FALSE(IsSelecting());
 
-  // Switch to overview mode.
+  // Switch to overview mode. Tapping should now exit overview mode.
   ToggleOverview();
   ASSERT_TRUE(IsSelecting());
-
-  // Tap should now exit overview mode.
-  generator->GestureTapAt(point_in_background_page);
+  GetEventGenerator()->GestureTapAt(point_in_background_page);
   EXPECT_FALSE(IsSelecting());
 }
 
 // Start dragging a window and activate overview mode. This test should not
 // crash or DCHECK inside aura::Window::StackChildRelativeTo().
 TEST_F(WindowSelectorTest, OverviewWhileDragging) {
-  const gfx::Rect bounds(10, 10, 100, 100);
-  std::unique_ptr<aura::Window> window(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window(CreateTestWindow());
   std::unique_ptr<WindowResizer> resizer(CreateWindowResizer(
       window.get(), gfx::Point(), HTCAPTION, ::wm::WINDOW_MOVE_SOURCE_MOUSE));
   ASSERT_TRUE(resizer.get());
@@ -1639,9 +1544,8 @@
   // Create two windows with widgets (widgets are needed to close the windows
   // later in the test), one each on the first two monitors.
   aura::Window::Windows root_windows = Shell::GetAllRootWindows();
-  const gfx::Rect bounds(100, 100);
-  std::unique_ptr<views::Widget> widget1(CreateWindowWidget(bounds));
-  std::unique_ptr<views::Widget> widget2(CreateWindowWidget(bounds));
+  std::unique_ptr<views::Widget> widget1(CreateTestWidget());
+  std::unique_ptr<views::Widget> widget2(CreateTestWidget());
   aura::Window* window1 = widget1->GetNativeWindow();
   aura::Window* window2 = widget2->GetNativeWindow();
   ASSERT_TRUE(wm::MoveWindowToDisplay(window2, GetSecondaryDisplay().id()));
@@ -1687,13 +1591,12 @@
 
 // Tests window list animation states are correctly updated.
 TEST_F(WindowSelectorTest, SetWindowListAnimationStates) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
-  wm::ActivateWindow(window3.get());
-  wm::ActivateWindow(window2.get());
-  wm::ActivateWindow(window1.get());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  std::unique_ptr<aura::Window> window3(CreateTestWindow());
+  ::wm::ActivateWindow(window3.get());
+  ::wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window1.get());
 
   EXPECT_FALSE(wm::GetWindowState(window1.get())->IsFullscreen());
   EXPECT_FALSE(wm::GetWindowState(window2.get())->IsFullscreen());
@@ -1720,13 +1623,12 @@
 // Tests window list animation states are correctly updated with selected
 // window.
 TEST_F(WindowSelectorTest, SetWindowListAnimationStatesWithSelectedWindow) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
-  wm::ActivateWindow(window3.get());
-  wm::ActivateWindow(window2.get());
-  wm::ActivateWindow(window1.get());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  std::unique_ptr<aura::Window> window3(CreateTestWindow());
+  ::wm::ActivateWindow(window3.get());
+  ::wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window1.get());
 
   EXPECT_FALSE(wm::GetWindowState(window1.get())->IsFullscreen());
   EXPECT_FALSE(wm::GetWindowState(window2.get())->IsFullscreen());
@@ -1759,13 +1661,12 @@
 // Tests OverviewWindowAnimationObserver can handle deleted window.
 TEST_F(WindowSelectorTest,
        OverviewWindowAnimationObserverCanHandleDeletedWindow) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
-  wm::ActivateWindow(window3.get());
-  wm::ActivateWindow(window2.get());
-  wm::ActivateWindow(window1.get());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  std::unique_ptr<aura::Window> window3(CreateTestWindow());
+  ::wm::ActivateWindow(window3.get());
+  ::wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window1.get());
 
   EXPECT_FALSE(wm::GetWindowState(window1.get())->IsFullscreen());
   EXPECT_FALSE(wm::GetWindowState(window2.get())->IsFullscreen());
@@ -1803,13 +1704,12 @@
 
 // Tests can handle OverviewWindowAnimationObserver was deleted.
 TEST_F(WindowSelectorTest, HandleOverviewWindowAnimationObserverWasDeleted) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
-  wm::ActivateWindow(window3.get());
-  wm::ActivateWindow(window2.get());
-  wm::ActivateWindow(window1.get());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  std::unique_ptr<aura::Window> window3(CreateTestWindow());
+  ::wm::ActivateWindow(window3.get());
+  ::wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window1.get());
 
   EXPECT_FALSE(wm::GetWindowState(window1.get())->IsFullscreen());
   EXPECT_FALSE(wm::GetWindowState(window2.get())->IsFullscreen());
@@ -1825,12 +1725,11 @@
   // Enter overview.
   ToggleOverview();
 
-  // Click on |window2| to activate it and exit overview.
-  // Should only set |should_animate_when_exiting_| and
-  // |should_be_observed_when_exiting_| on window 2.
-  // Because the animation duration is zero in test, the
-  // OverviewWindowAnimationObserver will delete itself immediatelly before
-  // |window3|'s is added to it.
+  // Click on |window2| to activate it and exit overview. Should only set
+  // |should_animate_when_exiting_| and |should_be_observed_when_exiting_| on
+  // window 2. Because the animation duration is zero in test, the
+  // OverviewWindowAnimationObserver will delete itself immediately before
+  // |window3| is added to it.
   ClickWindow(window2.get());
   EXPECT_FALSE(window1->layer()->GetAnimator()->is_animating());
   EXPECT_FALSE(window2->layer()->GetAnimator()->is_animating());
@@ -1840,13 +1739,12 @@
 // Tests can handle |gained_active| window is not in the |window_grid| when
 // OnWindowActivated.
 TEST_F(WindowSelectorTest, HandleActiveWindowNotInWindowGrid) {
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
-  wm::ActivateWindow(window3.get());
-  wm::ActivateWindow(window2.get());
-  wm::ActivateWindow(window1.get());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  std::unique_ptr<aura::Window> window3(CreateTestWindow());
+  ::wm::ActivateWindow(window3.get());
+  ::wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window1.get());
 
   EXPECT_FALSE(wm::GetWindowState(window1.get())->IsFullscreen());
   EXPECT_FALSE(wm::GetWindowState(window2.get())->IsFullscreen());
@@ -1865,8 +1763,7 @@
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
   // Create and active a new window should exit overview without error.
-  auto widget =
-      CreateTestWidget(nullptr, kShellWindowId_StatusContainer, bounds);
+  auto widget = CreateTestWidget();
 
   TweenTester tester1(window1.get());
   TweenTester tester2(window2.get());
@@ -1885,26 +1782,26 @@
 // Fails consistently; see https://crbug.com/812497.
 TEST_F(WindowSelectorTest, DISABLED_HandleAlwaysOnTopWindow) {
   const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window4(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window5(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window6(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window7(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window8(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds));
+  std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds));
+  std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds));
+  std::unique_ptr<aura::Window> window4(CreateTestWindow(bounds));
+  std::unique_ptr<aura::Window> window5(CreateTestWindow(bounds));
+  std::unique_ptr<aura::Window> window6(CreateTestWindow(bounds));
+  std::unique_ptr<aura::Window> window7(CreateTestWindow(bounds));
+  std::unique_ptr<aura::Window> window8(CreateTestWindow(bounds));
   window3->SetProperty(aura::client::kAlwaysOnTopKey, true);
   window5->SetProperty(aura::client::kAlwaysOnTopKey, true);
 
   // Control z order and MRU order.
-  wm::ActivateWindow(window8.get());
-  wm::ActivateWindow(window7.get());  // Will be fullscreen.
-  wm::ActivateWindow(window6.get());  // Will be maximized.
-  wm::ActivateWindow(window5.get());  // AlwaysOnTop window.
-  wm::ActivateWindow(window4.get());
-  wm::ActivateWindow(window3.get());  // AlwaysOnTop window.
-  wm::ActivateWindow(window2.get());  // Will be fullscreen.
-  wm::ActivateWindow(window1.get());
+  ::wm::ActivateWindow(window8.get());
+  ::wm::ActivateWindow(window7.get());  // Will be fullscreen.
+  ::wm::ActivateWindow(window6.get());  // Will be maximized.
+  ::wm::ActivateWindow(window5.get());  // AlwaysOnTop window.
+  ::wm::ActivateWindow(window4.get());
+  ::wm::ActivateWindow(window3.get());  // AlwaysOnTop window.
+  ::wm::ActivateWindow(window2.get());  // Will be fullscreen.
+  ::wm::ActivateWindow(window1.get());
 
   EXPECT_FALSE(wm::GetWindowState(window2.get())->IsFullscreen());
   EXPECT_FALSE(wm::GetWindowState(window6.get())->IsFullscreen());
@@ -1958,14 +1855,14 @@
   // https://crbug.com/816224.
   wm::GetWindowState(window2.get())->OnWMEvent(&toggle_fullscreen_event);
   wm::GetWindowState(window7.get())->OnWMEvent(&toggle_fullscreen_event);
-  wm::ActivateWindow(window8.get());
-  wm::ActivateWindow(window7.get());  // Will be fullscreen.
-  wm::ActivateWindow(window6.get());  // Maximized.
-  wm::ActivateWindow(window5.get());  // AlwaysOnTop window.
-  wm::ActivateWindow(window4.get());
-  wm::ActivateWindow(window3.get());  // AlwaysOnTop window.
-  wm::ActivateWindow(window2.get());  // Will be fullscreen.
-  wm::ActivateWindow(window1.get());
+  ::wm::ActivateWindow(window8.get());
+  ::wm::ActivateWindow(window7.get());  // Will be fullscreen.
+  ::wm::ActivateWindow(window6.get());  // Maximized.
+  ::wm::ActivateWindow(window5.get());  // AlwaysOnTop window.
+  ::wm::ActivateWindow(window4.get());
+  ::wm::ActivateWindow(window3.get());  // AlwaysOnTop window.
+  ::wm::ActivateWindow(window2.get());  // Will be fullscreen.
+  ::wm::ActivateWindow(window1.get());
   wm::GetWindowState(window2.get())->OnWMEvent(&toggle_fullscreen_event);
   wm::GetWindowState(window7.get())->OnWMEvent(&toggle_fullscreen_event);
   // Enter overview.
@@ -1994,14 +1891,14 @@
   // https://crbug.com/816224.
   wm::GetWindowState(window2.get())->OnWMEvent(&toggle_fullscreen_event);
   wm::GetWindowState(window7.get())->OnWMEvent(&toggle_fullscreen_event);
-  wm::ActivateWindow(window8.get());
-  wm::ActivateWindow(window7.get());  // Will be fullscreen.
-  wm::ActivateWindow(window6.get());  // Maximized.
-  wm::ActivateWindow(window5.get());  // AlwaysOnTop window.
-  wm::ActivateWindow(window4.get());
-  wm::ActivateWindow(window3.get());  // AlwaysOnTop window.
-  wm::ActivateWindow(window2.get());  // Will be fullscreen.
-  wm::ActivateWindow(window1.get());
+  ::wm::ActivateWindow(window8.get());
+  ::wm::ActivateWindow(window7.get());  // Will be fullscreen.
+  ::wm::ActivateWindow(window6.get());  // Maximized.
+  ::wm::ActivateWindow(window5.get());  // AlwaysOnTop window.
+  ::wm::ActivateWindow(window4.get());
+  ::wm::ActivateWindow(window3.get());  // AlwaysOnTop window.
+  ::wm::ActivateWindow(window2.get());  // Will be fullscreen.
+  ::wm::ActivateWindow(window1.get());
   wm::GetWindowState(window2.get())->OnWMEvent(&toggle_fullscreen_event);
   wm::GetWindowState(window7.get())->OnWMEvent(&toggle_fullscreen_event);
   // Enter overview.
@@ -2031,14 +1928,14 @@
   // https://crbug.com/816224.
   wm::GetWindowState(window2.get())->OnWMEvent(&toggle_fullscreen_event);
   wm::GetWindowState(window7.get())->OnWMEvent(&toggle_fullscreen_event);
-  wm::ActivateWindow(window8.get());
-  wm::ActivateWindow(window7.get());  // Will be fullscreen.
-  wm::ActivateWindow(window6.get());  // Maximized.
-  wm::ActivateWindow(window5.get());  // AlwaysOnTop window.
-  wm::ActivateWindow(window4.get());
-  wm::ActivateWindow(window3.get());  // AlwaysOnTop window.
-  wm::ActivateWindow(window2.get());  // Will be fullscreen.
-  wm::ActivateWindow(window1.get());
+  ::wm::ActivateWindow(window8.get());
+  ::wm::ActivateWindow(window7.get());  // Will be fullscreen.
+  ::wm::ActivateWindow(window6.get());  // Maximized.
+  ::wm::ActivateWindow(window5.get());  // AlwaysOnTop window.
+  ::wm::ActivateWindow(window4.get());
+  ::wm::ActivateWindow(window3.get());  // AlwaysOnTop window.
+  ::wm::ActivateWindow(window2.get());  // Will be fullscreen.
+  ::wm::ActivateWindow(window1.get());
   wm::GetWindowState(window2.get())->OnWMEvent(&toggle_fullscreen_event);
   wm::GetWindowState(window7.get())->OnWMEvent(&toggle_fullscreen_event);
   // Enter overview.
@@ -2065,16 +1962,12 @@
 // released.
 TEST_F(WindowSelectorTest, WindowItemCanAnimateOnDragRelease) {
   UpdateDisplay("400x400");
-  const gfx::Rect bounds(10, 10, 200, 200);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  wm::ActivateWindow(window2.get());
-  wm::ActivateWindow(window1.get());
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  ::wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window1.get());
 
-  // The item dragging is only allowed in tablet mode.
-  base::RunLoop().RunUntilIdle();
-  Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
-
+  EnterTabletMode();
   ToggleOverview();
   WindowSelectorItem* item2 = GetWindowItemForWindow(0, window2.get());
   // Drag |item2| in a way so that |window2| does not get activated.
@@ -2096,14 +1989,10 @@
 // visibility when a item is being dragged.
 TEST_F(WindowSelectorTest, WindowItemTitleCloseVisibilityOnDrag) {
   UpdateDisplay("400x400");
-  const gfx::Rect bounds(10, 10, 200, 200);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
 
-  // Dragging is only allowed in tablet mode.
-  base::RunLoop().RunUntilIdle();
-  Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
-
+  EnterTabletMode();
   ToggleOverview();
   WindowSelectorItem* item1 = GetWindowItemForWindow(0, window1.get());
   WindowSelectorItem* item2 = GetWindowItemForWindow(0, window2.get());
@@ -2135,19 +2024,15 @@
 // Tests that overview widgets are stacked in the correct order.
 TEST_F(WindowSelectorTest, OverviewWidgetStackingOrder) {
   // Create three windows, including one minimized.
-  const gfx::Rect bounds(10, 10, 200, 200);
-  std::unique_ptr<aura::Window> window(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> minimized(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window(CreateTestWindow());
+  std::unique_ptr<aura::Window> minimized(CreateTestWindow());
   wm::GetWindowState(minimized.get())->Minimize();
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window3(CreateTestWindow());
 
   aura::Window* parent = window->parent();
   DCHECK_EQ(parent, minimized->parent());
 
-  // Dragging is only allowed in tablet mode.
-  base::RunLoop().RunUntilIdle();
-  Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
-
+  EnterTabletMode();
   ToggleOverview();
   WindowSelectorItem* item1 = GetWindowItemForWindow(0, window.get());
   WindowSelectorItem* item2 = GetWindowItemForWindow(0, minimized.get());
@@ -2219,15 +2104,11 @@
 
 // Tests that overview widgets are stacked in the correct order.
 TEST_F(WindowSelectorTest, OverviewWidgetStackingOrderWithDragging) {
-  const gfx::Rect bounds(10, 10, 200, 200);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  std::unique_ptr<aura::Window> window3(CreateTestWindow());
 
-  // Dragging is only allowed in tablet mode.
-  base::RunLoop().RunUntilIdle();
-  Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
-
+  EnterTabletMode();
   ToggleOverview();
   WindowSelectorItem* item1 = GetWindowItemForWindow(0, window1.get());
   WindowSelectorItem* item2 = GetWindowItemForWindow(0, window2.get());
@@ -2274,10 +2155,9 @@
   // when the 400x200 is rotated to 200x400, and should be considered a normal
   // overview window after display change.
   UpdateDisplay("400x200");
-  std::unique_ptr<aura::Window> wide(CreateWindow(gfx::Rect(10, 10, 400, 160)));
-  std::unique_ptr<aura::Window> tall(CreateWindow(gfx::Rect(10, 10, 50, 200)));
-  std::unique_ptr<aura::Window> normal(
-      CreateWindow(gfx::Rect(10, 10, 200, 200)));
+  std::unique_ptr<aura::Window> wide(CreateTestWindow(gfx::Rect(400, 160)));
+  std::unique_ptr<aura::Window> tall(CreateTestWindow(gfx::Rect(50, 200)));
+  std::unique_ptr<aura::Window> normal(CreateTestWindow(gfx::Rect(200, 200)));
 
   ToggleOverview();
   base::RunLoop().RunUntilIdle();
@@ -2309,18 +2189,13 @@
 // Verify that the mask that is applied to add rounded corners in overview mode
 // is removed during animations and drags.
 TEST_F(WindowSelectorTest, RoundedEdgeMaskVisibility) {
-  UpdateDisplay("400x400");
-  const gfx::Rect bounds(0, 0, 200, 200);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
 
-  wm::ActivateWindow(window2.get());
-  wm::ActivateWindow(window1.get());
+  ::wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window1.get());
 
-  // Dragging is only allowed in tablet mode.
-  base::RunLoop().RunUntilIdle();
-  Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
-
+  EnterTabletMode();
   ToggleOverview();
   base::RunLoop().RunUntilIdle();
   WindowSelectorItem* item1 = GetWindowItemForWindow(0, window1.get());
@@ -2382,13 +2257,15 @@
   };
 
   // Add three windows which in overview mode will be considered wide, tall and
-  // normal. Set top view insets to 0, so it is easy to check the ratios of
-  // the shadows match the ratios of the untransformed windows.
+  // normal. Set top view insets to 0 so it is easy to check the ratios of the
+  // shadows match the ratios of the untransformed windows.
   UpdateDisplay("400x400");
-  std::unique_ptr<aura::Window> wide(CreateWindow(gfx::Rect(10, 10, 400, 100)));
-  std::unique_ptr<aura::Window> tall(CreateWindow(gfx::Rect(10, 10, 100, 400)));
+  std::unique_ptr<aura::Window> wide(
+      CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect(400, 100)));
+  std::unique_ptr<aura::Window> tall(
+      CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect(100, 400)));
   std::unique_ptr<aura::Window> normal(
-      CreateWindow(gfx::Rect(10, 10, 200, 200)));
+      CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect(200, 200)));
   wide->SetProperty(aura::client::kTopViewInset, 0);
   tall->SetProperty(aura::client::kTopViewInset, 0);
   normal->SetProperty(aura::client::kTopViewInset, 0);
@@ -2438,14 +2315,10 @@
 // Verify that attempting to drag with a secondary finger works as expected.
 // Disabled due to flakiness: crbug.com/834708
 TEST_F(WindowSelectorTest, DISABLED_DraggingWithTwoFingers) {
-  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
-  std::unique_ptr<aura::Window> window2 = CreateTestWindow();
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
 
-  // Dragging is only allowed in tablet mode.
-  base::RunLoop().RunUntilIdle();
-  Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
-  base::RunLoop().RunUntilIdle();
-
+  EnterTabletMode();
   ToggleOverview();
   base::RunLoop().RunUntilIdle();
   WindowSelectorItem* item1 = GetWindowItemForWindow(0, window1.get());
@@ -2506,8 +2379,7 @@
 
 // Verify that shadows on windows disappear for the duration of overview mode.
 TEST_F(WindowSelectorTest, ShadowDisappearsInOverview) {
-  const gfx::Rect bounds(200, 200);
-  std::unique_ptr<aura::Window> window(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window(CreateTestWindow());
 
   // Verify that the shadow is initially visible.
   ::wm::ShadowController* shadow_controller = Shell::Get()->shadow_controller();
@@ -2524,8 +2396,8 @@
 
 // Verify that PIP windows will be excluded from the overview, but not hidden.
 TEST_F(WindowSelectorTest, PipWindowShownButExcludedFromOverview) {
-  std::unique_ptr<aura::Window> pip_window(CreateWindow(gfx::Rect(200, 200)));
-
+  std::unique_ptr<aura::Window> pip_window(
+      CreateTestWindow(gfx::Rect(200, 200)));
   wm::WindowState* window_state = wm::GetWindowState(pip_window.get());
   const wm::WMEvent enter_pip(wm::WM_EVENT_PIP);
   window_state->OnWMEvent(&enter_pip);
@@ -2540,10 +2412,9 @@
 
 // Tests the PositionWindows function works as expected.
 TEST_F(WindowSelectorTest, PositionWindows) {
-  const gfx::Rect bounds(200, 200);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  std::unique_ptr<aura::Window> window3(CreateTestWindow());
 
   ToggleOverview();
   WindowSelectorItem* item1 = GetWindowItemForWindow(0, window1.get());
@@ -2582,14 +2453,10 @@
 // Tests that overview mode is entered with kWindowDragged mode when an app is
 // dragged from the top of the screen.
 TEST_F(WindowSelectorTest, DraggingFromTopAnimation) {
-  // Ensure calls to EnableTabletModeWindowManager complete.
-  base::RunLoop().RunUntilIdle();
-  Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
-  base::RunLoop().RunUntilIdle();
-
-  const gfx::Rect bounds(200, 200);
-  std::unique_ptr<aura::Window> window(CreateWindow(bounds));
-  std::unique_ptr<views::Widget> widget(CreateWindowWidget(bounds));
+  EnterTabletMode();
+  std::unique_ptr<views::Widget> widget(CreateTestWidget(
+      nullptr, kShellWindowId_DefaultContainer, gfx::Rect(200, 200)));
+  widget->GetNativeWindow()->SetProperty(aura::client::kTopViewInset, 20);
 
   // Drag from the the top of the app to enter overview.
   auto drag_controller = std::make_unique<TabletModeAppWindowDragController>();
@@ -2608,9 +2475,7 @@
 // behaviors and alignments.
 TEST_F(WindowSelectorTest, GridBounds) {
   UpdateDisplay("600x600");
-
-  const gfx::Rect bounds(200, 200);
-  std::unique_ptr<aura::Window> window(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(200, 200)));
 
   Shelf* shelf = GetPrimaryShelf();
   shelf->SetAlignment(SHELF_ALIGNMENT_BOTTOM);
@@ -2656,10 +2521,7 @@
 
   void SetUp() override {
     WindowSelectorTest::SetUp();
-    // Ensure calls to EnableTabletModeWindowManager complete.
-    base::RunLoop().RunUntilIdle();
-    Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
-    base::RunLoop().RunUntilIdle();
+    EnterTabletMode();
   }
 
   SplitViewController* split_view_controller() {
@@ -2718,7 +2580,6 @@
                     const gfx::Point& end_location,
                     SelectorItemLocation location,
                     bool long_press = true) {
-    // Start drag in the middle of the seletor item.
     gfx::Point start_location;
     switch (location) {
       case SelectorItemLocation::CENTER:
@@ -2756,12 +2617,7 @@
   // Creates a window which cannot be snapped by splitview.
   std::unique_ptr<aura::Window> CreateUnsnappableWindow(
       const gfx::Rect& bounds = gfx::Rect()) {
-    std::unique_ptr<aura::Window> window;
-    if (bounds.IsEmpty())
-      window = CreateTestWindow();
-    else
-      window = base::WrapUnique<aura::Window>(CreateWindow(bounds));
-
+    std::unique_ptr<aura::Window> window = CreateTestWindow();
     window->SetProperty(aura::client::kResizeBehaviorKey,
                         ws::mojom::kResizeBehaviorNone);
     return window;
@@ -2879,14 +2735,13 @@
 // will be closed.
 TEST_F(SplitViewWindowSelectorTest, DragToClose) {
   // This test requires a widget.
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<views::Widget> widget1(CreateWindowWidget(bounds));
+  std::unique_ptr<views::Widget> widget(CreateTestWidget());
 
   ToggleOverview();
   ASSERT_TRUE(window_selector_controller()->IsSelecting());
 
   WindowSelectorItem* item =
-      GetWindowItemForWindow(0, widget1->GetNativeWindow());
+      GetWindowItemForWindow(0, widget->GetNativeWindow());
   const gfx::Point start = item->target_bounds().CenterPoint();
   ASSERT_TRUE(item);
 
@@ -2904,21 +2759,21 @@
   window_selector()->CompleteDrag(item, start + gfx::Vector2d(0, 180));
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(window_selector());
+  EXPECT_TRUE(widget->IsClosed());
 }
 
 // Verify that if the window item has been flung enough vertically, the window
 // will be closed.
 TEST_F(SplitViewWindowSelectorTest, FlingToClose) {
   // This test requires a widget.
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<views::Widget> widget1(CreateWindowWidget(bounds));
+  std::unique_ptr<views::Widget> widget(CreateTestWidget());
 
   ToggleOverview();
   ASSERT_TRUE(window_selector_controller()->IsSelecting());
   EXPECT_EQ(1u, window_selector()->grid_list_for_testing()[0]->size());
 
   WindowSelectorItem* item =
-      GetWindowItemForWindow(0, widget1->GetNativeWindow());
+      GetWindowItemForWindow(0, widget->GetNativeWindow());
   const gfx::Point start = item->target_bounds().CenterPoint();
   ASSERT_TRUE(item);
 
@@ -2928,7 +2783,7 @@
   window_selector()->Fling(item, start, 2500, 0);
   ASSERT_TRUE(window_selector());
 
-  // Verify that items flung vertically, but without enough velocity do not
+  // Verify that items flung vertically but without enough velocity do not
   // close the item.
   window_selector()->InitiateDrag(item, start);
   window_selector()->Drag(item, start + gfx::Vector2d(0, 50));
@@ -2942,6 +2797,7 @@
   window_selector()->Fling(item, start, 0, 2500);
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(window_selector());
+  EXPECT_TRUE(widget->IsClosed());
 }
 
 // Tests that nudging occurs in the most basic case, which is we have one row
@@ -3082,10 +2938,9 @@
 // overview mode when split view is enabled.
 TEST_F(SplitViewWindowSelectorTest, WindowGridSizeWhileDraggingWithSplitView) {
   // Add three windows and enter overview mode.
-  const gfx::Rect bounds(400, 400);
-  std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  std::unique_ptr<aura::Window> window3(CreateTestWindow());
 
   ToggleOverview();
   ASSERT_TRUE(window_selector_controller()->IsSelecting());
@@ -3200,7 +3055,7 @@
 
   // Create a new window should exit the overview mode.
   std::unique_ptr<aura::Window> window2(CreateWindow(bounds));
-  wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window2.get());
   EXPECT_FALSE(window_selector_controller()->IsSelecting());
   EXPECT_EQ(split_view_controller()->state(),
             SplitViewController::BOTH_SNAPPED);
@@ -3212,8 +3067,8 @@
   // If there are more than 2 windows in overview
   std::unique_ptr<aura::Window> window3(CreateWindow(bounds));
   std::unique_ptr<aura::Window> window4(CreateWindow(bounds));
-  wm::ActivateWindow(window3.get());
-  wm::ActivateWindow(window4.get());
+  ::wm::ActivateWindow(window3.get());
+  ::wm::ActivateWindow(window4.get());
   EXPECT_FALSE(window_selector_controller()->IsSelecting());
   EXPECT_EQ(split_view_controller()->state(),
             SplitViewController::BOTH_SNAPPED);
@@ -3537,9 +3392,9 @@
   // Exiting the splitview will hide the unsnappable label.
   const gfx::Rect divider_bounds =
       GetSplitViewDividerBounds(/*is_dragging=*/false);
-  ui::test::EventGenerator* generator = GetEventGenerator();
-  generator->set_current_screen_location(divider_bounds.CenterPoint());
-  generator->DragMouseTo(0, 0);
+  GetEventGenerator()->set_current_screen_location(
+      divider_bounds.CenterPoint());
+  GetEventGenerator()->DragMouseTo(0, 0);
 
   EXPECT_FALSE(split_view_controller()->IsSplitViewModeActive());
   EXPECT_EQ(0.f, unsnappable_layer->opacity());
@@ -3721,9 +3576,9 @@
   // Drag the divider to the left edge.
   const gfx::Rect divider_bounds =
       GetSplitViewDividerBounds(/*is_dragging=*/false);
-  ui::test::EventGenerator* generator = GetEventGenerator();
-  generator->set_current_screen_location(divider_bounds.CenterPoint());
-  generator->DragMouseTo(0, 0);
+  GetEventGenerator()->set_current_screen_location(
+      divider_bounds.CenterPoint());
+  GetEventGenerator()->DragMouseTo(0, 0);
 
   // Verify that it is still in overview mode and that |window1| is returned to
   // the overview list.
@@ -3820,7 +3675,7 @@
   // Now snap both |window1| and |window2|.
   selector_item1 = GetWindowItemForWindow(grid_index, window1.get());
   DragWindowTo(selector_item1, gfx::Point(0, 0));
-  wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window2.get());
   EXPECT_FALSE(IsSelecting());
   EXPECT_EQ(split_view_controller()->state(),
             SplitViewController::BOTH_SNAPPED);
@@ -3908,7 +3763,7 @@
   EXPECT_FALSE(window3->layer()->GetTargetTransform().IsIdentity());
   EXPECT_EQ(SplitViewController::LEFT_SNAPPED,
             split_view_controller()->state());
-  wm::ActivateWindow(window2.get());
+  ::wm::ActivateWindow(window2.get());
   EXPECT_EQ(SplitViewController::BOTH_SNAPPED,
             split_view_controller()->state());
   EXPECT_FALSE(window_selector_controller()->IsSelecting());
@@ -3924,7 +3779,7 @@
   EXPECT_EQ(SplitViewController::LEFT_SNAPPED,
             split_view_controller()->state());
   std::unique_ptr<aura::Window> window4(CreateWindow(bounds));
-  wm::ActivateWindow(window4.get());
+  ::wm::ActivateWindow(window4.get());
   EXPECT_EQ(SplitViewController::BOTH_SNAPPED,
             split_view_controller()->state());
   EXPECT_FALSE(window_selector_controller()->IsSelecting());
@@ -3969,8 +3824,7 @@
 // Verify the behavior when trying to exit overview with one snapped window
 // is as expected.
 TEST_F(SplitViewWindowSelectorTest, ExitOverviewWithOneSnapped) {
-  const gfx::Rect bounds(0, 0, 400, 400);
-  std::unique_ptr<aura::Window> window(CreateWindow(bounds));
+  std::unique_ptr<aura::Window> window(CreateWindow(gfx::Rect(400, 400)));
 
   // Tests that we cannot exit overview when there is one snapped window and no
   // windows in overview normally.
diff --git a/base/android/junit/src/org/chromium/base/GcStateAssertTest.java b/base/android/junit/src/org/chromium/base/GcStateAssertTest.java
index 6460441..1548070 100644
--- a/base/android/junit/src/org/chromium/base/GcStateAssertTest.java
+++ b/base/android/junit/src/org/chromium/base/GcStateAssertTest.java
@@ -32,6 +32,9 @@
 
     @Before
     public void setUp() {
+        if (!BuildConfig.DCHECK_IS_ON) {
+            return;
+        }
         mTestClass = new TestClass();
         mTargetRef = mTestClass.mGcStateAssert.mWrapper;
         mFound = false;
@@ -49,6 +52,9 @@
 
     @After
     public void tearDown() {
+        if (!BuildConfig.DCHECK_IS_ON) {
+            return;
+        }
         GcStateAssert.sTestHook = null;
     }
 
diff --git a/base/cpu.cc b/base/cpu.cc
index aac2e957..3a0f6fa0 100644
--- a/base/cpu.cc
+++ b/base/cpu.cc
@@ -152,6 +152,7 @@
   memcpy(cpu_string, &cpu_info[1], kVendorNameSize);
   cpu_string[kVendorNameSize] = '\0';
   cpu_vendor_ = cpu_string;
+  bool hypervisor = false;
 
   // Interpret CPU feature information.
   if (num_ids > 0) {
@@ -176,6 +177,13 @@
     has_sse42_ = (cpu_info[2] & 0x00100000) != 0;
     has_popcnt_ = (cpu_info[2] & 0x00800000) != 0;
 
+    // "Hypervisor Present Bit: Bit 31 of ECX of CPUID leaf 0x1."
+    // See https://lwn.net/Articles/301888/
+    // This is checking for any hypervisor. Hypervisors may choose not to
+    // announce themselves. Hypervisors trap CPUID and sometimes return
+    // different results to underlying hardware.
+    hypervisor = (cpu_info[2] & 0x80000000) != 0;
+
     // AVX instructions will generate an illegal instruction exception unless
     //   a) they are supported by the CPU,
     //   b) XSAVE is supported by the CPU and
@@ -222,6 +230,23 @@
     __cpuid(cpu_info, kParameterContainingNonStopTimeStampCounter);
     has_non_stop_time_stamp_counter_ = (cpu_info[3] & (1 << 8)) != 0;
   }
+
+  if (!has_non_stop_time_stamp_counter_ && hypervisor) {
+    int cpu_info_hv[4] = {};
+    __cpuid(cpu_info_hv, 0x40000000);
+    if (cpu_info_hv[1] == 0x7263694D &&  // Micr
+        cpu_info_hv[2] == 0x666F736F &&  // osof
+        cpu_info_hv[3] == 0x76482074) {  // t Hv
+      // If CPUID says we have a variant TSC and a hypervisor has identified
+      // itself and the hypervisor says it is Microsoft Hyper-V, then treat
+      // TSC as invariant.
+      //
+      // Microsoft Hyper-V hypervisor reports variant TSC as there are some
+      // scenarios (eg. VM live migration) where the TSC is variant, but for
+      // our purposes we can treat it as invariant.
+      has_non_stop_time_stamp_counter_ = true;
+    }
+  }
 #elif defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) || defined(OS_LINUX))
   cpu_brand_ = *CpuInfoBrand();
 #endif
diff --git a/build/android/gyp/javac.py b/build/android/gyp/javac.py
index 1377681..8ad9bb7 100755
--- a/build/android/gyp/javac.py
+++ b/build/android/gyp/javac.py
@@ -317,7 +317,7 @@
 
   # Compiles with Error Prone take twice as long to run as pure javac. Thus GN
   # rules run both in parallel, with Error Prone only used for checks.
-  save_outputs = not options.use_errorprone_path
+  save_outputs = not options.enable_errorprone
 
   with build_utils.TempDir() as temp_dir:
     srcjars = options.java_srcjars
@@ -546,8 +546,11 @@
       help='Whether code being compiled should be built with stricter '
       'warnings for chromium code.')
   parser.add_option(
-      '--use-errorprone-path',
-      help='Use the Errorprone compiler at this path.')
+      '--errorprone-path', help='Use the Errorprone compiler at this path.')
+  parser.add_option(
+      '--enable-errorprone',
+      action='store_true',
+      help='Enable errorprone checks')
   parser.add_option('--jar-path', help='Jar output path.')
   parser.add_option(
       '--javac-arg',
@@ -603,27 +606,38 @@
   argv = build_utils.ExpandFileArgs(argv)
   options, java_files = _ParseOptions(argv)
 
-  if options.use_errorprone_path:
-    javac_path = options.use_errorprone_path
+  # Until we add a version of javac via DEPS, use errorprone with all checks
+  # disabled rather than javac. This ensures builds are reproducible.
+  # https://crbug.com/693079
+  # As of Jan 2019, on a z920, compiling chrome_java times:
+  # * With javac: 17 seconds
+  # * With errorprone (checks disabled): 20 seconds
+  # * With errorprone (checks enabled): 30 seconds
+  if options.errorprone_path:
+    javac_path = options.errorprone_path
   else:
     javac_path = distutils.spawn.find_executable('javac')
-  javac_cmd = [javac_path]
 
-  javac_cmd.extend((
-    '-g',
-    # Chromium only allows UTF8 source files.  Being explicit avoids
-    # javac pulling a default encoding from the user's environment.
-    '-encoding', 'UTF-8',
-    # Prevent compiler from compiling .java files not listed as inputs.
-    # See: http://blog.ltgt.net/most-build-tools-misuse-javac/
-    '-sourcepath', ':',
-  ))
+  javac_cmd = [
+      javac_path,
+      '-g',
+      # Chromium only allows UTF8 source files.  Being explicit avoids
+      # javac pulling a default encoding from the user's environment.
+      '-encoding',
+      'UTF-8',
+      # Prevent compiler from compiling .java files not listed as inputs.
+      # See: http://blog.ltgt.net/most-build-tools-misuse-javac/
+      '-sourcepath',
+      ':',
+  ]
 
-  if options.use_errorprone_path:
+  if options.enable_errorprone:
     for warning in ERRORPRONE_WARNINGS_TO_TURN_OFF:
       javac_cmd.append('-Xep:{}:OFF'.format(warning))
     for warning in ERRORPRONE_WARNINGS_TO_ERROR:
       javac_cmd.append('-Xep:{}:ERROR'.format(warning))
+  elif options.errorprone_path:
+    javac_cmd.append('-XepDisableAllChecks')
 
   if options.java_version:
     javac_cmd.extend([
@@ -665,7 +679,7 @@
                       options.processorpath)
   # GN already knows of java_files, so listing them just make things worse when
   # they change.
-  depfile_deps = ([javac_path] + classpath_inputs + options.java_srcjars)
+  depfile_deps = [javac_path] + classpath_inputs + options.java_srcjars
   input_paths = depfile_deps + java_files
 
   output_paths = [
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index fdaaca3..4658cefc 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -2798,16 +2798,23 @@
       if (_chromium_code) {
         args += [ "--chromium-code=1" ]
       }
-      if (invoker.enable_errorprone) {
+
+      # Use system javac only for debug builds. It's faster, but non-hermetic.
+      if (!is_java_debug || invoker.enable_errorprone) {
         deps += [ "//third_party/errorprone:errorprone($default_toolchain)" ]
+        args += [
+          "--errorprone-path",
+          "bin/errorprone",
+        ]
+      }
+      if (invoker.enable_errorprone) {
         deps += [ "//tools/android/errorprone_plugin:errorprone_plugin_java($default_toolchain)" ]
         _rebased_errorprone_processorpath = [
           "lib.java/tools/android/errorprone_plugin/errorprone_plugin_java.jar",
         ]
         args += [
-          "--use-errorprone-path",
-          "bin/errorprone",
           "--processorpath=$_rebased_errorprone_processorpath",
+          "--enable-errorprone",
         ]
       }
       foreach(e, _provider_configurations) {
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 9c8e867..9b5b3b1 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -3861,6 +3861,9 @@
 
     if (defined(invoker.extra_modules)) {
       _module_count = 0
+
+      # Mark as used in case extra_modules is an empty list.
+      assert(_module_count == 0)
       foreach(_module, invoker.extra_modules) {
         _module_count += 1
         assert(defined(_module.name),
diff --git a/build/config/fuchsia/build_manifest.py b/build/config/fuchsia/build_manifest.py
index 72dd8ac..0fac7f88 100644
--- a/build/config/fuchsia/build_manifest.py
+++ b/build/config/fuchsia/build_manifest.py
@@ -2,20 +2,9 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-"""Creates a archive manifest used for Fuchsia package generation.
+"""Creates a archive manifest used for Fuchsia package generation."""
 
-Arguments:
-  root_dir: The absolute path to the Chromium source tree root.
-
-  out_dir: The absolute path to the Chromium build directory.
-
-  app_name: The filename of the package's executable target.
-
-  runtime_deps: The path to the GN runtime deps file.
-
-  output_path: The path of the manifest file which will be written.
-"""
-
+import argparse
 import json
 import os
 import re
@@ -148,10 +137,9 @@
   return file_tag == '\x7fELF'
 
 
-def BuildManifest(root_dir, out_dir, app_name, app_filename,
-                  sandbox_policy_path, runtime_deps_file, depfile_path,
-                  dynlib_paths, output_path):
-  with open(output_path, 'w') as manifest, open(depfile_path, 'w') as depfile:
+def BuildManifest(args):
+  with open(args.output_path, 'w') as manifest, \
+       open(args.depfile_path, 'w') as depfile:
     # Process the runtime deps file for file paths, recursively walking
     # directories as needed. File paths are stored in absolute form,
     # so that MakePackagePath() may relativize to either the source root or
@@ -159,7 +147,7 @@
     # runtime_deps may contain duplicate paths, so use a set for
     # de-duplication.
     expanded_files = set()
-    for next_path in open(runtime_deps_file, 'r'):
+    for next_path in open(args.runtime_deps_file, 'r'):
       next_path = next_path.strip()
       if os.path.isdir(next_path):
         for root, _, files in os.walk(next_path):
@@ -173,7 +161,7 @@
 
     # Get set of dist libraries available for dynamic linking.
     dist_libs = set()
-    for next_dir in dynlib_paths.split(','):
+    for next_dir in args.dynlib_path:
       dist_libs = dist_libs.union(EnumerateDirectoryFiles(next_dir))
 
     # Compute the set of dynamic libraries used by the application or its
@@ -184,55 +172,90 @@
     # (binaries and libraries) are included as well.
     expanded_files = expanded_files.union(
        ComputeTransitiveLibDeps(
-           app_filename,
+           args.app_filename,
            {os.path.basename(f): f for f in expanded_files.union(dist_libs)}))
 
     # Format and write out the manifest contents.
-    gen_dir = os.path.join(out_dir, "gen")
+    gen_dir = os.path.join(args.out_dir, "gen")
     app_found = False
+    excluded_files_set = set(args.exclude_file)
     for current_file in expanded_files:
       if _IsBinary(current_file):
         current_file = _GetStrippedPath(current_file)
 
-      in_package_path = MakePackagePath(os.path.join(out_dir, current_file),
-                                        [gen_dir, root_dir, out_dir])
-      if in_package_path == app_filename:
+      absolute_file_path = os.path.join(args.out_dir, current_file)
+      in_package_path = MakePackagePath(absolute_file_path,
+                                        [gen_dir, args.root_dir, args.out_dir])
+      if in_package_path == args.app_filename:
         app_found = True
 
+      if in_package_path in excluded_files_set:
+        excluded_files_set.remove(in_package_path)
+        continue
+
       # The source path is relativized so that it can be used on multiple
       # environments with differing parent directory structures,
       # e.g. builder bots and swarming clients.
       manifest.write('%s=%s\n' % (in_package_path,
-                                  os.path.relpath(current_file, out_dir)))
+                                  os.path.relpath(current_file, args.out_dir)))
+
+    if len(excluded_files_set) > 0:
+      raise Exception('Some files were excluded with --exclude-file, but '
+                      'not found in the deps list: %s' %
+                          ', '.join(excluded_files_set));
 
     if not app_found:
       raise Exception('Could not locate executable inside runtime_deps.')
 
     # Write meta/package manifest file.
-    with open(os.path.join(os.path.dirname(output_path), 'package'), 'w') \
+    with open(os.path.join(os.path.dirname(args.output_path), 'package'), 'w') \
         as package_json:
-      json.dump({'version': '0', 'name': app_name}, package_json)
+      json.dump({'version': '0', 'name': args.app_name}, package_json)
       manifest.write('meta/package=%s\n' %
-                   os.path.relpath(package_json.name, out_dir))
+                   os.path.relpath(package_json.name, args.out_dir))
 
     # Write component manifest file.
-    with open(os.path.join(os.path.dirname(output_path),
-                           app_name + '.cmx'), 'w') as component_manifest_file:
+    cmx_file_path = os.path.join(os.path.dirname(args.output_path),
+                                 args.app_name + '.cmx')
+    with open(cmx_file_path, 'w') as component_manifest_file:
       component_manifest = {
-          'program': { 'binary': app_filename },
-          'sandbox': json.load(open(sandbox_policy_path, 'r')),
+          'program': { 'binary': args.app_filename },
+          'sandbox': json.load(open(args.sandbox_policy_path, 'r')),
       }
       json.dump(component_manifest, component_manifest_file)
+
       manifest.write('meta/%s=%s\n' %
                      (os.path.basename(component_manifest_file.name),
-                      os.path.relpath(component_manifest_file.name, out_dir)))
+                      os.path.relpath(cmx_file_path, args.out_dir)))
 
     depfile.write(
-        "%s: %s" % (os.path.relpath(output_path, out_dir),
-                    " ".join([os.path.relpath(f, out_dir)
+        "%s: %s" % (os.path.relpath(args.output_path, args.out_dir),
+                    " ".join([os.path.relpath(f, args.out_dir)
                               for f in expanded_files])))
   return 0
 
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--root-dir', required=True, help='Build root directory')
+  parser.add_argument('--out-dir', required=True, help='Build output directory')
+  parser.add_argument('--app-name', required=True, help='Package name')
+  parser.add_argument('--app-filename', required=True,
+      help='Path to the main application binary relative to the output dir.')
+  parser.add_argument('--sandbox-policy-path', required=True,
+      help='Path to the sandbox policy file relative to the output dir.')
+  parser.add_argument('--runtime-deps-file', required=True,
+      help='File with the list of runtime dependencies.')
+  parser.add_argument('--depfile-path', required=True,
+      help='Path to write GN deps file.')
+  parser.add_argument('--exclude-file', action='append', default=[],
+      help='Package-relative file path to exclude from the package.')
+  parser.add_argument('--dynlib-path', action='append', default=[],
+      help='Paths for the dynamic libraries relative to the output dir.')
+  parser.add_argument('--output-path', required=True, help='Output file path.')
+
+  args = parser.parse_args()
+
+  return BuildManifest(args)
 
 if __name__ == '__main__':
-  sys.exit(BuildManifest(*sys.argv[1:]))
+  sys.exit(main())
diff --git a/build/config/fuchsia/package.gni b/build/config/fuchsia/package.gni
index 83a5dae0..2e71029 100644
--- a/build/config/fuchsia/package.gni
+++ b/build/config/fuchsia/package.gni
@@ -84,17 +84,37 @@
     depfile = _depfile
 
     args = [
+      "--root-dir",
       rebase_path("//"),
+      "--out-dir",
       rebase_path(root_out_dir),
+      "--app-name",
       pkg.package_name,
+      "--app-filename",
       get_label_info(pkg.binary, "name"),
+      "--sandbox-policy-path",
       rebase_path(pkg.sandbox_policy),
+      "--runtime-deps-file",
       rebase_path(_runtime_deps_file),
+      "--depfile-path",
       rebase_path(_depfile),
-      rebase_path(dist_libroot) + "," + rebase_path("${sysroot}/dist"),
+      "--dynlib-path",
+      rebase_path(dist_libroot),
+      "--dynlib-path",
+      rebase_path("${sysroot}/dist"),
+      "--output-path",
       rebase_path(_archive_manifest),
     ]
 
+    if (defined(pkg.excluded_files)) {
+      foreach(filename, pkg.excluded_files) {
+        args += [
+          "--exclude-file",
+          filename,
+        ]
+      }
+    }
+
     write_runtime_deps = _runtime_deps_file
   }
 
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 109f90b..0e56d05 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8239079b3b80414f4ad7566335007b4e8a82192a
\ No newline at end of file
+06501f59b8b19215e4235b97f3f0b006ed059d68
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 983be8f..f091d05 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-73b95194dc363d5c7391e4e6220b24e1424dd71c
\ No newline at end of file
+59ee2ad79697b65682a86c4fd07fa7ba95b69c3e
\ No newline at end of file
diff --git a/cc/scheduler/scheduler_state_machine.cc b/cc/scheduler/scheduler_state_machine.cc
index eec833c..57bd80e 100644
--- a/cc/scheduler/scheduler_state_machine.cc
+++ b/cc/scheduler/scheduler_state_machine.cc
@@ -714,8 +714,12 @@
   if (begin_frame_source_paused_)
     return false;
 
-  // Don't create a pending tree till a frame sink is initialized.
-  if (!HasInitializedLayerTreeFrameSink())
+  // Don't create a pending tree till a frame sink is fully initialized.  Check
+  // for the ACTIVE state explicitly instead of calling
+  // HasInitializedLayerTreeFrameSink() because that only checks if frame sink
+  // has been recreated, but doesn't check if we're waiting for first commit or
+  // activation.
+  if (layer_tree_frame_sink_state_ != LayerTreeFrameSinkState::ACTIVE)
     return false;
 
   return true;
diff --git a/cc/scheduler/scheduler_state_machine_unittest.cc b/cc/scheduler/scheduler_state_machine_unittest.cc
index 931c38bd..ca5ad89 100644
--- a/cc/scheduler/scheduler_state_machine_unittest.cc
+++ b/cc/scheduler/scheduler_state_machine_unittest.cc
@@ -2310,24 +2310,63 @@
       SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION);
 }
 
-TEST(SchedulerStateMachineTest,
-     NoImplSideInvalidationWithoutLayerTreeFrameSink) {
+TEST(SchedulerStateMachineTest, NoImplSideInvalidationUntilFrameSinkActive) {
   SchedulerSettings settings;
   StateMachine state(settings);
-  SET_UP_STATE(state);
+  SET_UP_STATE(state)
 
-  // Impl-side invalidations should not be triggered till the frame sink is
-  // initialized.
+  // Prefer impl side invalidation over begin main frame.
+  state.set_should_defer_invalidation_for_fast_main_frame(false);
+
   state.DidLoseLayerTreeFrameSink();
+
+  // Create new frame sink but don't commit or activate yet.
   EXPECT_ACTION_UPDATE_STATE(
       SchedulerStateMachine::Action::BEGIN_LAYER_TREE_FRAME_SINK_CREATION);
-  EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE);
 
-  // No impl-side invalidations should be performed during frame sink creation.
+  state.DidCreateAndInitializeLayerTreeFrameSink();
+  state.SetNeedsBeginMainFrame();
+
   bool needs_first_draw_on_activation = true;
   state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation);
+
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME);
+  // No impl side invalidation because we're still waiting for first commit.
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE);
+
+  state.NotifyBeginMainFrameStarted();
+  state.NotifyReadyToCommit();
+  EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::COMMIT);
+
+  state.OnBeginImplFrameDeadline();
+  state.OnBeginImplFrameIdle();
+  EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE);
+
+  state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation);
+
+  state.IssueNextBeginImplFrame();
+  // No impl side invalidation because we're still waiting for first activation.
+  EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::NONE);
+
+  state.NotifyReadyToActivate();
+  EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE);
+
+  state.OnBeginImplFrameDeadline();
+  EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::Action::DRAW_IF_POSSIBLE);
+  state.OnBeginImplFrameIdle();
+
+  state.SetNeedsBeginMainFrame();
+  state.SetNeedsImplSideInvalidation(needs_first_draw_on_activation);
+
+  state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME);
+  // Impl side invalidation only after receiving first commit and activation for
+  // new frame sink.
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION);
 }
 
 TEST(SchedulerStateMachineTest, ImplSideInvalidationWhenPendingTreeExists) {
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 3dfe2add..6d6fb03 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -1836,6 +1836,16 @@
     base_module_target = ":chrome_modern_public_base_bundle_module"
     version_code = chrome_modern_version_code
     version_name = chrome_version_name
+    uncompress_shared_libraries = chromium_linker_supported
+  }
+
+  vr_module_tmpl("vr_monochrome_public_bundle_module") {
+    manifest_package = manifest_package
+    module_name = "VrMonochromePublic"
+    base_module_target = ":monochrome_public_base_bundle_module"
+    version_code = monochrome_version_code
+    version_name = chrome_version_name
+    uncompress_shared_libraries = true
   }
 }
 
@@ -1878,12 +1888,21 @@
     proguard_android_sdk_dep = webview_framework_dep
   }
   enable_language_splits = enable_chrome_language_splits
+  extra_modules = []
   if (modularize_ar) {
-    extra_modules = [
+    extra_modules += [
       {
         name = "ar"
         module_target = ":ar_public_bundle_module"
       },
     ]
   }
+  if (modularize_vr) {
+    extra_modules += [
+      {
+        name = "vr"
+        module_target = ":vr_monochrome_public_bundle_module"
+      },
+    ]
+  }
 }
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index 4a7ceea9..a2eaf1b2 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -370,12 +370,6 @@
       png_to_webp = true
     }
 
-    # TODO(crbug.com/862719): Remove Java VR code from the monochrome base
-    # module once we can conditionally install the VR DFM.
-    if (enable_vr) {
-      deps += [ "//chrome/browser/android/vr:java" ]
-    }
-
     if ((invoker.target_type == "android_apk" || !modularize_ar) &&
         package_arcore) {
       deps += [ "//third_party/android_deps:com_google_ar_core_java" ]
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedNewTabPage.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedNewTabPage.java
index f90a139..4103136 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedNewTabPage.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedNewTabPage.java
@@ -14,7 +14,6 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.widget.FrameLayout;
 import android.widget.ScrollView;
 
 import com.google.android.libraries.feed.api.scope.FeedProcessScope;
@@ -32,6 +31,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.feed.action.FeedActionHandler;
+import org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.native_page.NativePageHost;
 import org.chromium.chrome.browser.ntp.NewTabPage;
@@ -63,7 +63,7 @@
     private final int mWideMargin;
 
     private UiConfig mUiConfig;
-    private FrameLayout mRootView;
+    private HistoryNavigationLayout mRootView;
     private ContextMenuManager mContextMenuManager;
 
     // Used when Feed is enabled.
@@ -183,7 +183,7 @@
     /**
      * Provides the additional capabilities needed for the {@link FeedNewTabPage} container view.
      */
-    private class RootView extends FrameLayout {
+    private class RootView extends HistoryNavigationLayout {
         public RootView(Context context) {
             super(context);
         }
@@ -202,6 +202,12 @@
             return !(mTab != null && DeviceFormFactor.isWindowOnTablet(mTab.getWindowAndroid()))
                     && (mFakeboxDelegate != null && mFakeboxDelegate.isUrlBarFocused());
         }
+
+        @Override
+        public boolean wasLastSideSwipeGestureConsumed() {
+            // TODO(jinsukkim): Get the correct info from mStream.
+            return true;
+        }
     }
 
     /**
diff --git a/chrome/android/java/res/layout/recent_tabs_page.xml b/chrome/android/java/res/layout/recent_tabs_page.xml
index bdff54c..e7ac82a 100644
--- a/chrome/android/java/res/layout/recent_tabs_page.xml
+++ b/chrome/android/java/res/layout/recent_tabs_page.xml
@@ -4,7 +4,7 @@
      found in the LICENSE file. -->
 
 <!-- This single-child FrameLayout is needed for its top padding. -->
-<FrameLayout
+<org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
@@ -32,4 +32,4 @@
             android:scrollbarStyle="outsideOverlay" />
 
     </org.chromium.chrome.browser.ntp.NativePageRootFrameLayout>
-</FrameLayout>
+</org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ActivityTabProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/ActivityTabProvider.java
index 25d9a96..f555c649 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ActivityTabProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ActivityTabProvider.java
@@ -213,7 +213,9 @@
      */
     private void triggerActivityTabChangeEvent(Tab tab) {
         // Allow the event to trigger before native is ready (before the layout manager is set).
-        if (mLayoutManager != null && !(mLayoutManager.getActiveLayout() instanceof StaticLayout)
+        if (mLayoutManager != null
+                && !(mLayoutManager.getActiveLayout() instanceof StaticLayout
+                        || mLayoutManager.getActiveLayout() instanceof SimpleAnimationLayout)
                 && tab != null) {
             return;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index 9b6a667..3e557fa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -316,6 +316,9 @@
     /** A means of providing the foreground tab of the activity to different features. */
     private ActivityTabProvider mActivityTabProvider = new ActivityTabProvider();
 
+    /** A means of providing the theme color to different features. */
+    private final TabThemeColorProvider mTabThemeColorProvider = new TabThemeColorProvider();
+
     /** Whether or not the activity is in started state. */
     private boolean mStarted;
 
@@ -627,7 +630,7 @@
             Callback<Boolean> urlFocusChangedCallback = hasFocus -> onOmniboxFocusChanged(hasFocus);
             mToolbarManager = new ToolbarManager(this, toolbarContainer, mAppMenuHandler,
                     mAppMenuPropertiesDelegate, getCompositorViewHolder().getInvalidator(),
-                    urlFocusChangedCallback);
+                    urlFocusChangedCallback, mTabThemeColorProvider);
             mFindToolbarManager =
                     new FindToolbarManager(this, mToolbarManager.getActionModeControllerCallback());
             mAppMenuHandler.addObserver(new AppMenuObserver() {
@@ -666,6 +669,7 @@
 
         mTabModelSelector = createTabModelSelector();
         mActivityTabProvider.setTabModelSelector(mTabModelSelector);
+        mTabThemeColorProvider.setActivityTabProvider(mActivityTabProvider);
 
         if (mTabModelSelector == null) {
             assert isFinishing();
@@ -727,12 +731,6 @@
             @Override
             public void onDidChangeThemeColor(Tab tab, int color) {
                 setStatusBarColor(tab, color);
-
-                if (getToolbarManager() == null) return;
-                getToolbarManager().updatePrimaryColor(color, true);
-
-                ControlContainer controlContainer = findViewById(R.id.control_container);
-                controlContainer.getToolbarResourceAdapter().invalidate(null);
             }
 
             @Override
@@ -1367,6 +1365,7 @@
             manager.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
         }
 
+        mTabThemeColorProvider.destroy();
         mActivityTabProvider.destroy();
 
         mComponent = null;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java
index 83c2cbd9..a56a642 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java
@@ -18,7 +18,6 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
 import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
-import org.chromium.content_public.browser.ChildProcessImportance;
 import org.chromium.content_public.browser.LoadUrlParams;
 
 /**
@@ -67,7 +66,6 @@
         Tab tab = createTab();
         getTabModelSelector().setTab(tab);
         tab.show(TabSelectionType.FROM_NEW);
-        tab.setImportance(ChildProcessImportance.MODERATE);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java
index cd4950073..dfd96e3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java
@@ -5,14 +5,17 @@
 package org.chromium.chrome.browser;
 
 import android.content.Context;
+import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 
+import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
+import org.chromium.chrome.browser.gesturenav.SideSlideLayout;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabWebContentsUserData;
@@ -22,12 +25,28 @@
 import org.chromium.third_party.android.swiperefresh.SwipeRefreshLayout;
 import org.chromium.ui.OverscrollRefreshHandler;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * An overscroll handler implemented in terms a modified version of the Android
  * compat library's SwipeRefreshLayout effect.
  */
 public class SwipeRefreshHandler
         extends TabWebContentsUserData implements OverscrollRefreshHandler {
+    /**
+     * The targets that can handle MotionEvents.
+     */
+    @IntDef({SwipeType.NONE, SwipeType.PULL_TO_REFRESH, SwipeType.HISTORY_NAV})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface SwipeType {
+        int NONE = 0;
+        int PULL_TO_REFRESH = 1;
+        int HISTORY_NAV = 2;
+    }
+
+    private @SwipeType int mSwipeType;
+
     private static final Class<SwipeRefreshHandler> USER_DATA_KEY = SwipeRefreshHandler.class;
 
     // Synthetic delay between the {@link #didStopRefreshing()} signal and the
@@ -38,9 +57,11 @@
     // guarding against cases where the page reload fails or takes too long.
     private static final int MAX_REFRESH_ANIMATION_DURATION_MS = 7500;
 
+    private final boolean mNavigationEnabled;
+
     // The modified AppCompat version of the refresh effect, handling all core
     // logic, rendering and animation.
-    private final SwipeRefreshLayout mSwipeRefreshLayout;
+    private SwipeRefreshLayout mSwipeRefreshLayout;
 
     // The Tab where the swipe occurs.
     private Tab mTab;
@@ -55,11 +76,22 @@
 
     // Handles removing the layout from the view hierarchy.  This is posted to ensure it does not
     // conflict with pending Android draws.
-    private Runnable mDetachLayoutRunnable;
+    private Runnable mDetachRefreshLayoutRunnable;
 
     // Accessibility utterance used to indicate refresh activation.
     private String mAccessibilityRefreshString;
 
+    // History navigation layout and the main logic turning the gesture into corresponding UI.
+    private SideSlideLayout mSideSlideLayout;
+
+    // Async runnable for ending the refresh animation after the page first
+    // loads a frame. This is used to provide a reasonable minimum animation time.
+    private Runnable mStopNavigatingRunnable;
+
+    // Handles removing the layout from the view hierarchy.  This is posted to ensure it does not
+    // conflict with pending Android draws.
+    private Runnable mDetachSideSlideLayoutRunnable;
+
     public static SwipeRefreshHandler from(Tab tab) {
         SwipeRefreshHandler handler = get(tab);
         if (handler == null) {
@@ -82,19 +114,20 @@
     private SwipeRefreshHandler(Tab tab) {
         super(tab);
         mTab = tab;
+        mNavigationEnabled = ChromeFeatureList.isEnabled(ChromeFeatureList.GESTURE_NAVIGATION);
+    }
 
-        final Context context = tab.getThemedApplicationContext();
+    private void initSwipeRefreshLayout() {
+        final Context context = mTab.getThemedApplicationContext();
         mSwipeRefreshLayout = new SwipeRefreshLayout(context);
         mSwipeRefreshLayout.setLayoutParams(
                 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
         mSwipeRefreshLayout.setColorSchemeResources(R.color.light_active_color);
-        // SwipeRefreshLayout.LARGE layouts appear broken on JellyBean.
-        mSwipeRefreshLayout.setSize(SwipeRefreshLayout.DEFAULT);
-        mSwipeRefreshLayout.setEnabled(false);
+        if (mContainerView != null) mSwipeRefreshLayout.setEnabled(true);
 
         mSwipeRefreshLayout.setOnRefreshListener(() -> {
             cancelStopRefreshingRunnable();
-            mSwipeRefreshLayout.postDelayed(
+            ThreadUtils.postOnUiThreadDelayed(
                     getStopRefreshingRunnable(), MAX_REFRESH_ANIMATION_DURATION_MS);
             if (mAccessibilityRefreshString == null) {
                 int resId = R.string.accessibility_swipe_refresh;
@@ -105,12 +138,38 @@
             RecordUserAction.record("MobilePullGestureReload");
         });
         mSwipeRefreshLayout.setOnResetListener(() -> {
-            if (mDetachLayoutRunnable != null) return;
-            mDetachLayoutRunnable = () -> {
-                mDetachLayoutRunnable = null;
+            if (mDetachRefreshLayoutRunnable != null) return;
+            mDetachRefreshLayoutRunnable = () -> {
+                mDetachRefreshLayoutRunnable = null;
                 detachSwipeRefreshLayoutIfNecessary();
             };
-            mSwipeRefreshLayout.post(mDetachLayoutRunnable);
+            ThreadUtils.postOnUiThread(mDetachRefreshLayoutRunnable);
+        });
+    }
+
+    private void initSideSlideLayout() {
+        mSideSlideLayout = new SideSlideLayout(mTab.getThemedApplicationContext());
+        mSideSlideLayout.setLayoutParams(
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        if (mContainerView != null) mSideSlideLayout.setEnabled(true);
+
+        mSideSlideLayout.setOnNavigationListener((isForward) -> {
+            if (isForward) {
+                mTab.goForward();
+            } else {
+                mTab.goBack();
+            }
+            cancelStopNavigatingRunnable();
+            mSideSlideLayout.post(getStopNavigatingRunnable());
+        });
+
+        mSideSlideLayout.setOnResetListener(() -> {
+            if (mDetachSideSlideLayoutRunnable != null) return;
+            mDetachSideSlideLayoutRunnable = () -> {
+                mDetachSideSlideLayoutRunnable = null;
+                detachSideSlideLayoutIfNecessary();
+            };
+            mSideSlideLayout.post(mDetachSideSlideLayoutRunnable);
         });
     }
 
@@ -123,16 +182,22 @@
 
     @Override
     public void cleanupWebContents(WebContents webContents) {
-        detachSwipeRefreshLayoutIfNecessary();
-        cancelStopRefreshingRunnable();
+        if (mSwipeRefreshLayout != null) detachSwipeRefreshLayoutIfNecessary();
+        if (mSideSlideLayout != null) detachSideSlideLayoutIfNecessary();
         mContainerView = null;
         setEnabled(false);
     }
 
     @Override
     public void destroyInternal() {
-        mSwipeRefreshLayout.setOnRefreshListener(null);
-        mSwipeRefreshLayout.setOnResetListener(null);
+        if (mSwipeRefreshLayout != null) {
+            mSwipeRefreshLayout.setOnRefreshListener(null);
+            mSwipeRefreshLayout.setOnResetListener(null);
+        }
+        if (mSideSlideLayout != null) {
+            mSideSlideLayout.setOnNavigationListener(null);
+            mSideSlideLayout.setOnResetListener(null);
+        }
     }
 
     /**
@@ -141,59 +206,85 @@
      * visiblity of the animation.
      */
     public void didStopRefreshing() {
-        if (!mSwipeRefreshLayout.isRefreshing()) return;
+        if (mSwipeRefreshLayout == null || !mSwipeRefreshLayout.isRefreshing()) return;
         cancelStopRefreshingRunnable();
         mSwipeRefreshLayout.postDelayed(
                 getStopRefreshingRunnable(), STOP_REFRESH_ANIMATION_DELAY_MS);
     }
 
     @Override
-    public boolean start() {
+    public boolean start(float xDelta, float yDelta) {
         if (mTab.getActivity() != null && mTab.getActivity().getBottomSheet() != null) {
             Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedProfile());
             tracker.notifyEvent(EventConstants.PULL_TO_REFRESH);
         }
 
-        attachSwipeRefreshLayoutIfNecessary();
-        return mSwipeRefreshLayout.start();
+        if (yDelta > 0 && yDelta > Math.abs(xDelta)) {
+            if (mSwipeRefreshLayout == null) initSwipeRefreshLayout();
+            mSwipeType = SwipeType.PULL_TO_REFRESH;
+            attachSwipeRefreshLayoutIfNecessary();
+            return mSwipeRefreshLayout.start();
+        } else if (Math.abs(xDelta) > Math.abs(yDelta) && mNavigationEnabled) {
+            if (mSideSlideLayout == null) initSideSlideLayout();
+            mSwipeType = SwipeType.HISTORY_NAV;
+            boolean isForward = xDelta <= 0;
+            boolean shouldStart = isForward ? mTab.canGoForward() : mTab.canGoBack();
+            if (shouldStart) {
+                mSideSlideLayout.setDirection(isForward);
+                attachSideSlideLayoutIfNecessary();
+                mSideSlideLayout.start();
+            }
+            return shouldStart;
+        }
+        mSwipeType = SwipeType.NONE;
+        return false;
     }
 
     @Override
-    public void pull(float delta) {
+    public void pull(float xDelta, float yDelta) {
         TraceEvent.begin("SwipeRefreshHandler.pull");
-        mSwipeRefreshLayout.pull(delta);
+        if (mSwipeType == SwipeType.PULL_TO_REFRESH) {
+            mSwipeRefreshLayout.pull(yDelta);
+        } else if (mSwipeType == SwipeType.HISTORY_NAV) {
+            mSideSlideLayout.pull(xDelta);
+        }
         TraceEvent.end("SwipeRefreshHandler.pull");
     }
 
     @Override
     public void release(boolean allowRefresh) {
         TraceEvent.begin("SwipeRefreshHandler.release");
-        mSwipeRefreshLayout.release(allowRefresh);
+        if (mSwipeType == SwipeType.PULL_TO_REFRESH) {
+            mSwipeRefreshLayout.release(allowRefresh);
+        } else if (mSwipeType == SwipeType.HISTORY_NAV) {
+            mSideSlideLayout.release(allowRefresh);
+        }
         TraceEvent.end("SwipeRefreshHandler.release");
     }
 
     @Override
     public void reset() {
         cancelStopRefreshingRunnable();
-        mSwipeRefreshLayout.reset();
+        if (mSwipeRefreshLayout != null) mSwipeRefreshLayout.reset();
+        cancelStopNavigatingRunnable();
+        if (mSideSlideLayout != null) mSideSlideLayout.reset();
     }
 
     @Override
     public void setEnabled(boolean enabled) {
-        mSwipeRefreshLayout.setEnabled(enabled);
         if (!enabled) reset();
     }
 
     private void cancelStopRefreshingRunnable() {
         if (mStopRefreshingRunnable != null) {
-            mSwipeRefreshLayout.removeCallbacks(mStopRefreshingRunnable);
+            ThreadUtils.getUiThreadHandler().removeCallbacks(mStopRefreshingRunnable);
         }
     }
 
     private void cancelDetachLayoutRunnable() {
-        if (mDetachLayoutRunnable != null) {
-            mSwipeRefreshLayout.removeCallbacks(mDetachLayoutRunnable);
-            mDetachLayoutRunnable = null;
+        if (mDetachRefreshLayoutRunnable != null) {
+            ThreadUtils.getUiThreadHandler().removeCallbacks(mDetachRefreshLayoutRunnable);
+            mDetachRefreshLayoutRunnable = null;
         }
     }
 
@@ -219,4 +310,39 @@
             mContainerView.removeView(mSwipeRefreshLayout);
         }
     }
+
+    private void cancelStopNavigatingRunnable() {
+        if (mStopNavigatingRunnable != null) {
+            mSideSlideLayout.removeCallbacks(mStopNavigatingRunnable);
+            mStopNavigatingRunnable = null;
+        }
+    }
+
+    private void cancelDetachSideSlideLayoutRunnable() {
+        if (mDetachSideSlideLayoutRunnable != null) {
+            mSideSlideLayout.removeCallbacks(mDetachSideSlideLayoutRunnable);
+            mDetachSideSlideLayoutRunnable = null;
+        }
+    }
+
+    private Runnable getStopNavigatingRunnable() {
+        if (mStopNavigatingRunnable == null) {
+            mStopNavigatingRunnable = () -> mSideSlideLayout.stopNavigating();
+        }
+        return mStopNavigatingRunnable;
+    }
+
+    private void attachSideSlideLayoutIfNecessary() {
+        cancelDetachSideSlideLayoutRunnable();
+        if (mSideSlideLayout.getParent() == null) {
+            mContainerView.addView(mSideSlideLayout);
+        }
+    }
+
+    private void detachSideSlideLayoutIfNecessary() {
+        cancelDetachSideSlideLayoutRunnable();
+        if (mSideSlideLayout.getParent() != null) {
+            mContainerView.removeView(mSideSlideLayout);
+        }
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/TabThemeColorProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/TabThemeColorProvider.java
new file mode 100644
index 0000000..219a7bdf
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/TabThemeColorProvider.java
@@ -0,0 +1,44 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser;
+
+import org.chromium.chrome.browser.ActivityTabProvider.ActivityTabTabObserver;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabThemeColorHelper;
+
+/** A ThemeColorProvider for the current tab's theming. */
+public class TabThemeColorProvider extends ThemeColorProvider {
+    /** The {@link sActivityTabTabObserver} used to know when the active tab color changed. */
+    private ActivityTabTabObserver mActivityTabTabObserver;
+
+    public TabThemeColorProvider() {}
+
+    /**
+     * @param provider A means of getting the activity's tab.
+     */
+    public void setActivityTabProvider(ActivityTabProvider provider) {
+        mActivityTabTabObserver = new ActivityTabTabObserver(provider) {
+            @Override
+            public void onObservingDifferentTab(Tab tab) {
+                if (tab == null) return;
+                updatePrimaryColor(TabThemeColorHelper.getColor(tab), false);
+            }
+
+            @Override
+            public void onDidChangeThemeColor(Tab tab, int color) {
+                updatePrimaryColor(color, true);
+            }
+        };
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+        if (mActivityTabTabObserver != null) {
+            mActivityTabTabObserver.destroy();
+            mActivityTabTabObserver = null;
+        }
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ThemeColorProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/ThemeColorProvider.java
new file mode 100644
index 0000000..26732b9a
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ThemeColorProvider.java
@@ -0,0 +1,104 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.support.v7.content.res.AppCompatResources;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.ObserverList;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.util.ColorUtils;
+
+/**
+ * An abstract class that provides the current theme color.
+ */
+public abstract class ThemeColorProvider {
+    /**
+     * An interface to be notified about changes to the theme color.
+     */
+    public interface ThemeColorObserver {
+        /**
+         * @param color The new color the observer should use.
+         * @param shouldAnimate Whether the change of color should be animated.
+         */
+        void onThemeColorChanged(int color, boolean shouldAnimate);
+    }
+
+    /**
+     * An interface to be notified about changes to the tint.
+     */
+    public interface TintObserver {
+        /**
+         * @param tint The new tint the observer should use.
+         * @param useLight Whether the observer should use light mode.
+         */
+        void onTintChanged(ColorStateList tint, boolean useLight);
+    }
+
+    /** Light mode tint (used when color is dark). */
+    private final ColorStateList mLightModeTint;
+
+    /** Dark mode tint (used when color is light). */
+    private final ColorStateList mDarkModeTint;
+
+    /** Current primary color. */
+    private int mPrimaryColor;
+
+    /** Whether should use light tint (corresponds to dark color). */
+    private boolean mUseLight;
+
+    /** List of {@link ThemeColorObserver}s. These are used to broadcast events to listeners. */
+    private final ObserverList<ThemeColorObserver> mThemeColorObservers;
+
+    /** List of {@link TintObserver}s. These are used to broadcast events to listeners. */
+    private final ObserverList<TintObserver> mTintObservers;
+
+    public ThemeColorProvider() {
+        mThemeColorObservers = new ObserverList<ThemeColorObserver>();
+        mTintObservers = new ObserverList<TintObserver>();
+        final Context context = ContextUtils.getApplicationContext();
+        mLightModeTint = AppCompatResources.getColorStateList(context, R.color.light_mode_tint);
+        mDarkModeTint = AppCompatResources.getColorStateList(context, R.color.dark_mode_tint);
+    }
+
+    public void addThemeColorObserver(ThemeColorObserver observer) {
+        mThemeColorObservers.addObserver(observer);
+    }
+
+    public void removeThemeColorObserver(ThemeColorObserver observer) {
+        mThemeColorObservers.removeObserver(observer);
+    }
+
+    public void addTintObserver(TintObserver observer) {
+        mTintObservers.addObserver(observer);
+    }
+
+    public void removeTintObserver(TintObserver observer) {
+        mTintObservers.removeObserver(observer);
+    }
+
+    public void destroy() {
+        mThemeColorObservers.clear();
+        mTintObservers.clear();
+    }
+
+    protected void updatePrimaryColor(int color, boolean shouldAnimate) {
+        if (mPrimaryColor == color) return;
+        mPrimaryColor = color;
+        for (ThemeColorObserver observer : mThemeColorObservers) {
+            observer.onThemeColorChanged(color, shouldAnimate);
+        }
+
+        final boolean useLight = ColorUtils.shouldUseLightForegroundOnBackground(color);
+        if (useLight == mUseLight) return;
+        mUseLight = useLight;
+        final ColorStateList tint = useLight ? mLightModeTint : mDarkModeTint;
+        for (TintObserver observer : mTintObservers) {
+            observer.onTintChanged(tint, useLight);
+        }
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index f48e886c..8c2bdb2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -276,7 +276,7 @@
             getToolbarManager().setUrlBarHidden(true);
         }
         int toolbarColor = mIntentDataProvider.getToolbarColor();
-        getToolbarManager().updatePrimaryColor(toolbarColor, false);
+        getToolbarManager().onThemeColorChanged(toolbarColor, false);
         if (!mIntentDataProvider.isOpenedByChrome()) {
             getToolbarManager().setShouldUpdateToolbarPrimaryColor(false);
         }
@@ -519,11 +519,11 @@
 
                 if (tab.isPreview()) {
                     final int defaultColor = ColorUtils.getDefaultThemeColor(getResources(), false);
-                    manager.updatePrimaryColor(defaultColor, false);
+                    manager.onThemeColorChanged(defaultColor, false);
                     setStatusBarColor(defaultColor, false);
                     mTriggeredPreviewChange = true;
                 } else if (mOriginalColor != manager.getPrimaryColor() && mTriggeredPreviewChange) {
-                    manager.updatePrimaryColor(mOriginalColor, false);
+                    manager.onThemeColorChanged(mOriginalColor, false);
                     setStatusBarColor(mOriginalColor, false);
 
                     mTriggeredPreviewChange = false;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/ArrowDrawable.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/ArrowDrawable.java
new file mode 100644
index 0000000..22339ccf
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/ArrowDrawable.java
@@ -0,0 +1,161 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.gesturenav;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+
+import org.chromium.chrome.R;
+
+/**
+ * Arrow drawable indicating forward/backward direction for Material theme.
+ */
+class ArrowDrawable extends Drawable {
+    private static final int CIRCLE_DIAMETER_DP = 40;
+    private static final float CENTER_RADIUS_DP = 8.75f;
+    private static final float STROKE_WIDTH_DP = 2.5f;
+    private static final int ARROW_WIDTH_DP = 10;
+
+    private final Resources mResources;
+
+    private final RectF mTempBounds = new RectF();
+    private final Paint mPaint = new Paint();
+    private final Paint mArrowFill = new Paint();
+    private final Path mArrow = new Path();
+
+    private final float mArrowWidth;
+    private final float mStrokeWidth;
+    private final float mStrokeInset;
+
+    private float mArrowScale;
+    private float mCenterRadius;
+    private float mWidth;
+    private float mHeight;
+    private int mAlpha;
+    private @ColorInt int mBackgroundColor;
+    private @ColorInt int mForegroundColor;
+    private boolean mIsForward;
+
+    public ArrowDrawable(Resources resources) {
+        mResources = resources;
+        mForegroundColor = mResources.getColor(R.color.light_active_color);
+
+        float screenDensity = mResources.getDisplayMetrics().density;
+        mWidth = CIRCLE_DIAMETER_DP * screenDensity;
+        mHeight = CIRCLE_DIAMETER_DP * screenDensity;
+        mArrowWidth = ARROW_WIDTH_DP * screenDensity;
+        mStrokeWidth = STROKE_WIDTH_DP * screenDensity;
+        mCenterRadius = CENTER_RADIUS_DP * screenDensity;
+        mStrokeInset = Math.min(mWidth, mHeight) / 2.0f - mCenterRadius;
+
+        mPaint.setStrokeCap(Paint.Cap.SQUARE);
+        mPaint.setAntiAlias(true);
+        mPaint.setStyle(Style.STROKE);
+        mPaint.setStrokeWidth(mStrokeWidth);
+
+        mArrow.setFillType(android.graphics.Path.FillType.EVEN_ODD);
+        mArrowFill.setStyle(Paint.Style.FILL);
+        mArrowFill.setAntiAlias(true);
+    }
+
+    /**
+     * Sets the direction the arrow points to.
+     * @param forward {@code true} if the arrow points forward.
+     */
+    public void setDirection(boolean forward) {
+        mIsForward = forward;
+    }
+
+    /**
+     * Set the scale of the arrow.
+     * @param scale Scale value of the range 0..1
+     */
+    public void setArrowScale(float scale) {
+        if (scale == mArrowScale) return;
+        mArrowScale = scale;
+        invalidateSelf();
+    }
+
+    /**
+     * Update the background color to match the parent view.
+     * @param color Background color.
+     */
+    public void setBackgroundColor(@ColorInt int color) {
+        mBackgroundColor = color;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return (int) mHeight;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return (int) mWidth;
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter filter) {
+        mPaint.setColorFilter(filter);
+        invalidateSelf();
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        if (mAlpha == alpha) return;
+        mAlpha = alpha;
+        invalidateSelf();
+    }
+
+    @Override
+    public int getAlpha() {
+        return mAlpha;
+    }
+
+    @Override
+    public void draw(Canvas c) {
+        final Rect bounds = getBounds();
+        final int saveCount = c.save();
+        final RectF lineBounds = mTempBounds;
+
+        lineBounds.set(bounds);
+        lineBounds.inset(mStrokeInset, mStrokeInset);
+        mPaint.setColor(mForegroundColor);
+
+        c.drawLine(lineBounds.left, (lineBounds.top + lineBounds.bottom) / 2, lineBounds.right,
+                (lineBounds.top + lineBounds.bottom) / 2, mPaint);
+
+        float delta = mArrowWidth * mArrowScale / 2f;
+        mArrow.reset();
+        mArrow.moveTo(delta, -delta);
+        mArrow.lineTo(delta, delta);
+        mArrow.lineTo(delta * 2, 0);
+
+        // Adjust the position of the triangle so that it is positioned at the end of the line.
+        float inset = (int) mStrokeInset / 2f * mArrowScale;
+        float x = (float) (mCenterRadius + bounds.exactCenterX());
+        float y = (float) bounds.exactCenterY();
+        mArrow.offset(x - inset, y);
+        mArrow.close();
+        mArrowFill.setColor(mForegroundColor);
+        if (!mIsForward) c.rotate(180, bounds.exactCenterX(), bounds.exactCenterY());
+        c.drawPath(mArrow, mArrowFill);
+        c.restoreToCount(saveCount);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java
new file mode 100644
index 0000000..a28b64f0
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java
@@ -0,0 +1,201 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.gesturenav;
+
+import android.content.Context;
+import android.support.annotation.IntDef;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.widget.FrameLayout;
+
+import org.chromium.chrome.browser.ActivityTabProvider;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.tab.Tab;
+
+/**
+ * FrameLayout that supports side-wise slide gesture for history navigation. Inheriting
+ * class may need to override {@link #isGestureConsumed()} if {@link #onTouchEvent} cannot
+ * be relied upon to know whether the side-wise swipe related event was handled. Namely
+ * {@link  android.support.v7.widget.RecyclerView}) always claims to handle touch events.
+ */
+public class HistoryNavigationLayout extends FrameLayout {
+    @IntDef({GestureState.NONE, GestureState.STARTED, GestureState.DRAGGED})
+    private @interface GestureState {
+        int NONE = 0;
+        int STARTED = 1;
+        int DRAGGED = 2;
+    }
+
+    private GestureDetector mDetector;
+
+    private SideSlideLayout mSideSlideLayout;
+
+    // Async runnable for ending the refresh animation after the page first
+    // loads a frame. This is used to provide a reasonable minimum animation time.
+    private Runnable mStopNavigatingRunnable;
+
+    // Handles removing the layout from the view hierarchy.  This is posted to ensure
+    // it does not conflict with pending Android draws.
+    private Runnable mDetachLayoutRunnable;
+
+    // Provides activity tab where the navigation should happen.
+    private ActivityTabProvider mTabProvider;
+
+    public HistoryNavigationLayout(Context context) {
+        this(context, null);
+    }
+
+    public HistoryNavigationLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.GESTURE_NAVIGATION)) return;
+        if (context instanceof ChromeActivity) {
+            mTabProvider = ((ChromeActivity) context).getActivityTabProvider();
+            mDetector = new GestureDetector(getContext(), new SideNavGestureListener());
+        } else {
+            throw new IllegalStateException("This native page should be under ChromeActivity");
+        }
+    }
+
+    private void createLayout() {
+        mSideSlideLayout = new SideSlideLayout(getContext());
+        mSideSlideLayout.setLayoutParams(
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        mSideSlideLayout.setEnabled(false);
+        mSideSlideLayout.setOnNavigationListener((isForward) -> {
+            if (mTabProvider == null) return;
+            Tab tab = mTabProvider.getActivityTab();
+            if (isForward) {
+                tab.goForward();
+            } else {
+                tab.goBack();
+            }
+            cancelStopNavigatingRunnable();
+            mSideSlideLayout.post(getStopNavigatingRunnable());
+        });
+
+        mSideSlideLayout.setOnResetListener(() -> {
+            if (mDetachLayoutRunnable != null) return;
+            mDetachLayoutRunnable = () -> {
+                mDetachLayoutRunnable = null;
+                detachSideSlideLayoutIfNecessary();
+            };
+            mSideSlideLayout.post(mDetachLayoutRunnable);
+        });
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent e) {
+        if (mDetector != null) {
+            mDetector.onTouchEvent(e);
+            if (e.getAction() == MotionEvent.ACTION_UP) {
+                if (mSideSlideLayout != null) mSideSlideLayout.release(true);
+            }
+        }
+        return super.dispatchTouchEvent(e);
+    }
+
+    private class SideNavGestureListener extends GestureDetector.SimpleOnGestureListener {
+        private @GestureState int mState = GestureState.NONE;
+
+        @Override
+        public boolean onDown(MotionEvent event) {
+            mState = GestureState.STARTED;
+            return true;
+        }
+
+        @Override
+        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+            if (wasLastSideSwipeGestureConsumed()) {
+                reset();
+                mState = GestureState.NONE;
+                return true;
+            }
+            if (mState == GestureState.STARTED) {
+                if (Math.abs(distanceX) > Math.abs(distanceY)) {
+                    boolean forward = distanceX > 0;
+                    if (canNavigate(forward)) {
+                        start(forward);
+                        mState = GestureState.DRAGGED;
+                    }
+                }
+                if (mState != GestureState.DRAGGED) mState = GestureState.NONE;
+            }
+            if (mState == GestureState.DRAGGED) mSideSlideLayout.pull(-distanceX);
+            return true;
+        }
+    }
+
+    private boolean canNavigate(boolean forward) {
+        if (mTabProvider == null) return false;
+        Tab tab = mTabProvider.getActivityTab();
+        return forward ? tab.canGoForward() : tab.canGoBack();
+    }
+
+    /**
+     * Checks if the gesture event was consumed by one of children views, in which case
+     * history navigation should not proceed. Whatever the child view does with the gesture
+     * events should take precedence and not be disturbed by the navigation.
+     *
+     * @return {@code true} if gesture event is consumed by one of the children.
+     */
+    public boolean wasLastSideSwipeGestureConsumed() {
+        return false;
+    }
+
+    private void start(boolean isForward) {
+        if (mSideSlideLayout == null) createLayout();
+        mSideSlideLayout.setEnabled(true);
+        mSideSlideLayout.setDirection(isForward);
+        attachSideSlideLayoutIfNecessary();
+        mSideSlideLayout.start();
+    }
+
+    /**
+     * Reset navigation UI in action.
+     */
+    public void reset() {
+        cancelStopNavigatingRunnable();
+        if (mSideSlideLayout != null) mSideSlideLayout.reset();
+    }
+
+    private void cancelStopNavigatingRunnable() {
+        if (mStopNavigatingRunnable != null) {
+            mSideSlideLayout.removeCallbacks(mStopNavigatingRunnable);
+            mStopNavigatingRunnable = null;
+        }
+    }
+
+    private void cancelDetachLayoutRunnable() {
+        if (mDetachLayoutRunnable != null) {
+            mSideSlideLayout.removeCallbacks(mDetachLayoutRunnable);
+            mDetachLayoutRunnable = null;
+        }
+    }
+
+    private Runnable getStopNavigatingRunnable() {
+        if (mStopNavigatingRunnable == null) {
+            mStopNavigatingRunnable = () -> mSideSlideLayout.stopNavigating();
+        }
+        return mStopNavigatingRunnable;
+    }
+
+    private void attachSideSlideLayoutIfNecessary() {
+        // The animation view is attached/detached on-demand to minimize overlap
+        // with composited SurfaceView content.
+        cancelDetachLayoutRunnable();
+        if (mSideSlideLayout.getParent() == null) {
+            addView(mSideSlideLayout);
+        }
+    }
+
+    private void detachSideSlideLayoutIfNecessary() {
+        cancelDetachLayoutRunnable();
+        if (mSideSlideLayout.getParent() != null) {
+            removeView(mSideSlideLayout);
+        }
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/SideSlideLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/SideSlideLayout.java
new file mode 100644
index 0000000..6d70fc8
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/SideSlideLayout.java
@@ -0,0 +1,377 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.gesturenav;
+
+import android.content.Context;
+import android.support.annotation.IntDef;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Transformation;
+
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.R;
+import org.chromium.third_party.android.swiperefresh.CircleImageView;
+
+/**
+ * The SideSlideLayout can be used whenever the user navigates the contents
+ * of a view using horizontal gesture. Shows an arrow widget moving horizontally
+ * in reaction to the gesture which, if goes over a threshold, triggers navigation.
+ * The caller that instantiates this view should add an {@link #OnNavigateListener}
+ * to be notified whenever the gesture is completed.
+ * Based on {@link org.chromium.third_party.android.swiperefresh.SwipeRefreshLayout}
+ * and modified accordingly to support horizontal gesture.
+ */
+public class SideSlideLayout extends ViewGroup {
+    // Used to record the UMA histogram Overscroll.* This definition should be
+    // in sync with that in content/browser/web_contents/aura/types.h
+    // TODO(jinsukkim): Generate java enum from the native header.
+    @IntDef({UmaNavigationType.NAVIGATION_TYPE_NONE, UmaNavigationType.FORWARD_TOUCHPAD,
+            UmaNavigationType.BACK_TOUCHPAD, UmaNavigationType.FORWARD_TOUCHSCREEN,
+            UmaNavigationType.BACK_TOUCHSCREEN, UmaNavigationType.RELOAD_TOUCHPAD,
+            UmaNavigationType.RELOAD_TOUCHSCREEN, UmaNavigationType.NAVIGATION_TYPE_COUNT})
+    private @interface UmaNavigationType {
+        int NAVIGATION_TYPE_NONE = 0;
+        int FORWARD_TOUCHPAD = 1;
+        int BACK_TOUCHPAD = 2;
+        int FORWARD_TOUCHSCREEN = 3;
+        int BACK_TOUCHSCREEN = 4;
+        int RELOAD_TOUCHPAD = 5;
+        int RELOAD_TOUCHSCREEN = 6;
+        int NAVIGATION_TYPE_COUNT = 7;
+    }
+
+    /**
+     * Classes that wish to be notified when the swipe gesture correctly
+     * triggers navigation should implement this interface.
+     */
+    public interface OnNavigateListener { void onNavigate(boolean isForward); }
+
+    /**
+     * Classes that wish to be notified when a reset is triggered should
+     * implement this interface.
+     */
+    public interface OnResetListener { void onReset(); }
+
+    private static final int MAX_ALPHA = 255;
+    private static final int STARTING_ALPHA = (int) (.3f * MAX_ALPHA);
+
+    private static final int CIRCLE_DIAMETER_DP = 40;
+    private static final int MAX_CIRCLE_RADIUS_DP = 30;
+
+    // Offset in dips from the border of the view. Gesture triggers the navigation
+    // if slid by this amount or more.
+    private static final int TARGET_THRESHOLD_DP = 128;
+
+    private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
+
+    private static final int SCALE_DOWN_DURATION_MS = 500;
+    private static final int ANIMATE_TO_START_DURATION_MS = 500;
+
+    // Minimum number of pull updates necessary to trigger a side nav.
+    private static final int MIN_PULLS_TO_ACTIVATE = 3;
+
+    private final DecelerateInterpolator mDecelerateInterpolator;
+    private final float mTotalDragDistance;
+    private final int mMediumAnimationDuration;
+    private final int mCircleWidth;
+    private final int mCircleHeight;
+
+    private OnNavigateListener mListener;
+    private OnResetListener mResetListener;
+
+    // Flag indicating that the navigation will be activated.
+    private boolean mNavigating;
+
+    private int mCurrentTargetOffset;
+    private float mTotalMotionY;
+
+    // Whether or not the starting offset has been determined.
+    private boolean mOriginalOffsetCalculated;
+
+    // True while side gesture is in progress.
+    private boolean mIsBeingDragged;
+
+    private CircleImageView mCircleView;
+    private ArrowDrawable mArrow;
+
+    // Start position for animation moving the UI back to original offset.
+    private int mFrom;
+    private int mOriginalOffset;
+
+    private Animation mScaleDownAnimation;
+    private AnimationListener mCancelAnimationListener;
+
+    private boolean mIsForward;
+
+    private final AnimationListener mNavigateListener = new AnimationListener() {
+        @Override
+        public void onAnimationStart(Animation animation) {}
+
+        @Override
+        public void onAnimationRepeat(Animation animation) {}
+
+        @Override
+        public void onAnimationEnd(Animation animation) {
+            if (mNavigating) {
+                // Make sure the arrow widget is fully visible
+                mArrow.setAlpha(MAX_ALPHA);
+                if (mListener != null) mListener.onNavigate(mIsForward);
+                recordHistogram("Overscroll.Navigated3", mIsForward);
+            } else {
+                reset();
+            }
+            mCurrentTargetOffset = mCircleView.getLeft();
+        }
+    };
+
+    private final Animation mAnimateToStartPosition = new Animation() {
+        @Override
+        public void applyTransformation(float interpolatedTime, Transformation t) {
+            int targetTop = mFrom + (int) ((mOriginalOffset - mFrom) * interpolatedTime);
+            int offset = targetTop - mCircleView.getLeft();
+            mTotalMotionY += offset;
+
+            float progress = Math.min(1.f, Math.abs(mTotalMotionY) / mTotalDragDistance);
+            mCircleView.setProgress(progress);
+            setTargetOffsetLeftAndRight(offset);
+        }
+    };
+
+    public SideSlideLayout(Context context) {
+        super(context);
+
+        mMediumAnimationDuration =
+                getResources().getInteger(android.R.integer.config_mediumAnimTime);
+
+        setWillNotDraw(false);
+        mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
+
+        final float density = getResources().getDisplayMetrics().density;
+        mCircleWidth = (int) (CIRCLE_DIAMETER_DP * density);
+        mCircleHeight = (int) (CIRCLE_DIAMETER_DP * density);
+
+        int background = getContext().getResources().getColor(R.color.modern_grey_50);
+        mCircleView = new CircleImageView(getContext(), background, CIRCLE_DIAMETER_DP / 2,
+                MAX_CIRCLE_RADIUS_DP, R.color.modern_blue_300);
+
+        mArrow = new ArrowDrawable(getContext().getResources());
+        mArrow.setBackgroundColor(background);
+        mCircleView.setImageDrawable(mArrow);
+        mCircleView.setVisibility(View.GONE);
+        addView(mCircleView);
+
+        // The absolute offset has to take into account that the circle starts at an offset
+        mTotalDragDistance = TARGET_THRESHOLD_DP * density;
+    }
+
+    /**
+     * Set the listener to be notified when the navigation is triggered.
+     */
+    public void setOnNavigationListener(OnNavigateListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Set the reset listener to be notified when a reset is triggered.
+     */
+    public void setOnResetListener(OnResetListener listener) {
+        mResetListener = listener;
+    }
+
+    /**
+     * Stop navigation.
+     */
+    public void stopNavigating() {
+        setNavigating(false);
+    }
+
+    private void setNavigating(boolean navigating) {
+        if (mNavigating != navigating) {
+            mNavigating = navigating;
+            if (mNavigating) startScaleDownAnimation(mNavigateListener);
+        }
+    }
+
+    private void startScaleDownAnimation(AnimationListener listener) {
+        if (mScaleDownAnimation == null) {
+            mScaleDownAnimation = new Animation() {
+                @Override
+                public void applyTransformation(float interpolatedTime, Transformation t) {
+                    float progress = 1 - interpolatedTime; // [0..1]
+                    mCircleView.setScaleX(progress);
+                    mCircleView.setScaleY(progress);
+                }
+            };
+            mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION_MS);
+        }
+        mCircleView.setAnimationListener(listener);
+        mCircleView.clearAnimation();
+        mCircleView.startAnimation(mScaleDownAnimation);
+    }
+
+    /**
+     * Set the direction used for sliding gesture.
+     * @param forward {@code true} if direction is forward.
+     */
+    public void setDirection(boolean forward) {
+        mIsForward = forward;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        if (getChildCount() == 0) return;
+
+        final int height = getMeasuredHeight();
+        int circleWidth = mCircleView.getMeasuredWidth();
+        int circleHeight = mCircleView.getMeasuredHeight();
+        mCircleView.layout(mCurrentTargetOffset, height / 2 - circleHeight / 2,
+                mCurrentTargetOffset + circleWidth, height / 2 + circleHeight / 2);
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY));
+        if (!mOriginalOffsetCalculated) {
+            initializeOffset();
+            mOriginalOffsetCalculated = true;
+        }
+    }
+
+    private void initializeOffset() {
+        mCurrentTargetOffset = mOriginalOffset =
+                mIsForward ? getMeasuredWidth() : -mCircleView.getMeasuredWidth();
+    }
+
+    /**
+     * Start the pull effect. If the effect is disabled or a navigation animation
+     * is currently active, the request will be ignored.
+     * @return whether a new pull sequence has started.
+     */
+    public boolean start() {
+        if (!isEnabled() || mNavigating || mListener == null) return false;
+        mCircleView.clearAnimation();
+        mTotalMotionY = 0;
+        mIsBeingDragged = true;
+        mArrow.setAlpha(STARTING_ALPHA);
+        mArrow.setDirection(mIsForward);
+        initializeOffset();
+        return true;
+    }
+
+    /**
+     * Apply a pull impulse to the effect. If the effect is disabled or has yet
+     * to start, the pull will be ignored.
+     * @param delta the magnitude of the pull.
+     */
+    public void pull(float delta) {
+        if (!isEnabled() || !mIsBeingDragged) return;
+
+        float maxDelta = mTotalDragDistance / MIN_PULLS_TO_ACTIVATE;
+        delta = Math.max(-maxDelta, Math.min(maxDelta, delta));
+        mTotalMotionY += delta;
+
+        float overscroll = mTotalMotionY;
+        float extraOs = Math.abs(overscroll) - mTotalDragDistance;
+        float slingshotDist = mTotalDragDistance;
+        float tensionSlingshotPercent =
+                Math.max(0, Math.min(extraOs, slingshotDist * 2) / slingshotDist);
+        float tensionPercent =
+                (float) ((tensionSlingshotPercent / 4) - Math.pow((tensionSlingshotPercent / 4), 2))
+                * 2f;
+
+        if (mCircleView.getVisibility() != View.VISIBLE) mCircleView.setVisibility(View.VISIBLE);
+        mCircleView.setScaleX(1f);
+        mCircleView.setScaleY(1f);
+
+        float originalDragPercent = Math.abs(overscroll) / mTotalDragDistance;
+        float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
+        float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;
+        mArrow.setArrowScale(Math.min(1f, adjustedPercent));
+
+        float alphaStrength = Math.max(0f, Math.min(1f, (dragPercent - .9f) / .1f));
+        mArrow.setAlpha(STARTING_ALPHA + (int) (alphaStrength * (MAX_ALPHA - STARTING_ALPHA)));
+        mCircleView.setProgress(Math.min(1.f, Math.abs(overscroll) / mTotalDragDistance));
+
+        float extraMove = slingshotDist * tensionPercent * 2;
+        int targetDiff = (int) (slingshotDist * dragPercent + extraMove);
+        int targetX = mOriginalOffset + (mIsForward ? -targetDiff : targetDiff);
+        setTargetOffsetLeftAndRight(targetX - mCurrentTargetOffset);
+    }
+
+    private void setTargetOffsetLeftAndRight(int offset) {
+        mCircleView.offsetLeftAndRight(offset);
+        mCurrentTargetOffset = mCircleView.getLeft();
+    }
+
+    /**
+     * Release the active pull. If no pull has started, the release will be ignored.
+     * If the pull was sufficiently large, the navigation sequence will be initiated.
+     * @param allowNav whether to allow a sufficiently large pull to trigger
+     *                     the navigation action and animation sequence.
+     */
+    public void release(boolean allowNav) {
+        if (!mIsBeingDragged) return;
+
+        // See ACTION_UP handling in {@link #onTouchEvent(...)}.
+        mIsBeingDragged = false;
+        final float overscroll = Math.abs(mTotalMotionY);
+        if (isEnabled() && allowNav && overscroll > mTotalDragDistance) {
+            setNavigating(true);
+            return;
+        }
+        // Cancel navigation
+        mNavigating = false;
+        if (mCancelAnimationListener == null) {
+            mCancelAnimationListener = new AnimationListener() {
+                @Override
+                public void onAnimationStart(Animation animation) {}
+
+                @Override
+                public void onAnimationEnd(Animation animation) {
+                    startScaleDownAnimation(mNavigateListener);
+                }
+
+                @Override
+                public void onAnimationRepeat(Animation animation) {}
+            };
+        }
+        mFrom = mCurrentTargetOffset;
+        mAnimateToStartPosition.reset();
+        mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION_MS);
+        mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
+        mCircleView.setAnimationListener(mCancelAnimationListener);
+        mCircleView.clearAnimation();
+        mCircleView.startAnimation(mAnimateToStartPosition);
+        recordHistogram("Overscroll.Cancelled3", mIsForward);
+    }
+
+    /**
+     * Reset the effect, clearing any active animations.
+     */
+    public void reset() {
+        mIsBeingDragged = false;
+        setNavigating(false);
+        mCircleView.setVisibility(View.GONE);
+        mCircleView.getBackground().setAlpha(MAX_ALPHA);
+        mArrow.setAlpha(MAX_ALPHA);
+
+        // Return the circle to its start position
+        setTargetOffsetLeftAndRight(mOriginalOffset - mCurrentTargetOffset);
+        mCurrentTargetOffset = mCircleView.getLeft();
+        if (mResetListener != null) mResetListener.onReset();
+    }
+
+    private static void recordHistogram(String name, boolean forward) {
+        RecordHistogram.recordEnumeratedHistogram(name,
+                forward ? UmaNavigationType.FORWARD_TOUCHSCREEN
+                        : UmaNavigationType.BACK_TOUCHSCREEN,
+                UmaNavigationType.NAVIGATION_TYPE_COUNT);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPageView.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPageView.java
index 9fd1a791..8c15fe3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPageView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPageView.java
@@ -8,18 +8,17 @@
 import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.view.View;
-import android.widget.FrameLayout;
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout;
 import org.chromium.chrome.browser.util.ViewUtils;
 
 /**
  * The New Tab Page for use in the incognito profile.
  */
-public class IncognitoNewTabPageView extends FrameLayout {
-
+public class IncognitoNewTabPageView extends HistoryNavigationLayout {
     private IncognitoNewTabPageManager mManager;
     private boolean mFirstShow = true;
     private NewTabPageScrollView mScrollView;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageUma.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageUma.java
index fc16548..8482c66 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageUma.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageUma.java
@@ -316,8 +316,8 @@
             status = ContentSuggestionsDisplayStatus.COLLAPSED;
         }
 
-        RecordHistogram.recordEnumeratedHistogram("ContentSuggestions.Feed.DisplayStatus", status,
-                ContentSuggestionsDisplayStatus.NUM_ENTRIES);
+        RecordHistogram.recordEnumeratedHistogram("ContentSuggestions.Feed.DisplayStatusOnOpen",
+                status, ContentSuggestionsDisplayStatus.NUM_ENTRIES);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
index 84e4f7a..cfd61ab1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
@@ -12,12 +12,12 @@
 import android.support.v7.widget.RecyclerView.ViewHolder;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
-import android.widget.FrameLayout;
 
 import org.chromium.base.TraceEvent;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.compositor.layouts.content.InvalidationAwareThumbnailProvider;
+import org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.ntp.NewTabPage.FakeboxDelegate;
 import org.chromium.chrome.browser.ntp.cards.NewTabPageAdapter;
@@ -35,7 +35,7 @@
  * The native new tab page, represented by some basic data such as title and url, and an Android
  * View that displays the page.
  */
-public class NewTabPageView extends FrameLayout {
+public class NewTabPageView extends HistoryNavigationLayout {
     private static final String TAG = "NewTabPageView";
 
     private NewTabPageRecyclerView mRecyclerView;
@@ -228,6 +228,11 @@
         return mNewTabPageLayout;
     }
 
+    @Override
+    public boolean wasLastSideSwipeGestureConsumed() {
+        return mRecyclerView.isCardBeingSwiped();
+    }
+
     /**
      * Sets the {@link FakeboxDelegate} associated with the new tab page.
      * @param fakeboxDelegate The {@link FakeboxDelegate} used to determine whether the URL bar
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
index 76b1bc2..bacc34eb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
@@ -29,8 +29,9 @@
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController.OnSuggestionsReceivedListener;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator.AutocompleteDelegate;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator.SuggestionProcessor;
+import org.chromium.chrome.browser.omnibox.suggestions.basic.AnswerSuggestionProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.basic.BasicSuggestionProcessor;
-import org.chromium.chrome.browser.omnibox.suggestions.basic.BasicSuggestionProcessor.SuggestionHost;
+import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionHost;
 import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionView.SuggestionViewDelegate;
 import org.chromium.chrome.browser.omnibox.suggestions.editurl.EditUrlSuggestionProcessor;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -87,6 +88,7 @@
     private final Handler mHandler;
     private final BasicSuggestionProcessor mBasicSuggestionProcessor;
     private EditUrlSuggestionProcessor mEditUrlProcessor;
+    private final AnswerSuggestionProcessor mAnswerSuggestionProcessor;
 
     private ToolbarDataProvider mDataProvider;
     private boolean mNativeInitialized;
@@ -145,6 +147,7 @@
         mBasicSuggestionProcessor = new BasicSuggestionProcessor(mContext, this, textProvider);
         mEditUrlProcessor = new EditUrlSuggestionProcessor(
                 delegate, (suggestion) -> onSelection(suggestion, 0));
+        mAnswerSuggestionProcessor = new AnswerSuggestionProcessor(mContext, this);
     }
 
     /**
@@ -262,7 +265,7 @@
             mHandler.post(deferredRunnable);
         }
         mDeferredNativeRunnables.clear();
-        mBasicSuggestionProcessor.onNativeInitialized();
+        mAnswerSuggestionProcessor.onNativeInitialized();
     }
 
     /**
@@ -293,8 +296,9 @@
             // a consequence the omnibox is unfocused).
             hideSuggestions();
         }
-        mBasicSuggestionProcessor.onUrlFocusChange(hasFocus);
         if (mEditUrlProcessor != null) mEditUrlProcessor.onUrlFocusChange(hasFocus);
+        mAnswerSuggestionProcessor.onUrlFocusChange(hasFocus);
+        mBasicSuggestionProcessor.onUrlFocusChange(hasFocus);
     }
 
     /**
@@ -642,7 +646,10 @@
      * @return The appropriate suggestion processor for the provided suggestion.
      */
     private SuggestionProcessor getProcessorForSuggestion(OmniboxSuggestion suggestion) {
-        if (mEditUrlProcessor != null && mEditUrlProcessor.doesProcessSuggestion(suggestion)) {
+        if (mAnswerSuggestionProcessor.doesProcessSuggestion(suggestion)) {
+            return mAnswerSuggestionProcessor;
+        } else if (mEditUrlProcessor != null
+                && mEditUrlProcessor.doesProcessSuggestion(suggestion)) {
             return mEditUrlProcessor;
         }
         return mBasicSuggestionProcessor;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/AnswerSuggestionProcessor.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/AnswerSuggestionProcessor.java
new file mode 100644
index 0000000..b965b6f
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/AnswerSuggestionProcessor.java
@@ -0,0 +1,233 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.omnibox.suggestions.basic;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.util.Pair;
+import android.util.TypedValue;
+import android.view.View;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.omnibox.suggestions.AnswersImageFetcher;
+import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator.SuggestionProcessor;
+import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestion;
+import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
+import org.chromium.chrome.browser.omnibox.suggestions.SuggestionCommonProperties;
+import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionViewProperties.SuggestionIcon;
+import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionViewProperties.SuggestionTextContainer;
+import org.chromium.components.omnibox.AnswerType;
+import org.chromium.components.omnibox.SuggestionAnswer;
+import org.chromium.ui.modelutil.PropertyModel;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** A class that handles model and view creation for the most commonly used omnibox suggestion. */
+public class AnswerSuggestionProcessor implements SuggestionProcessor {
+    private final Map<String, List<PropertyModel>> mPendingAnswerRequestUrls;
+    private final Context mContext;
+    private final SuggestionHost mSuggestionHost;
+    private final AnswersImageFetcher mImageFetcher;
+    private boolean mEnableNewAnswerLayout;
+
+    /**
+     * @param context An Android context.
+     * @param suggestionHost A handle to the object using the suggestions.
+     */
+    public AnswerSuggestionProcessor(Context context, SuggestionHost suggestionHost) {
+        mContext = context;
+        mSuggestionHost = suggestionHost;
+        mPendingAnswerRequestUrls = new HashMap<>();
+        mImageFetcher = new AnswersImageFetcher();
+    }
+
+    @Override
+    public boolean doesProcessSuggestion(OmniboxSuggestion suggestion) {
+        return suggestion.hasAnswer();
+    }
+
+    /**
+     * Signals that native initialization has completed.
+     */
+    public void onNativeInitialized() {
+        // Experiment: controls presence of certain answer icon types.
+        mEnableNewAnswerLayout =
+                ChromeFeatureList.isEnabled(ChromeFeatureList.OMNIBOX_NEW_ANSWER_LAYOUT);
+    }
+
+    @Override
+    public int getViewTypeId() {
+        return OmniboxSuggestionUiType.DEFAULT;
+    }
+
+    @Override
+    public PropertyModel createModelForSuggestion(OmniboxSuggestion suggestion) {
+        return new PropertyModel(SuggestionViewProperties.ALL_KEYS);
+    }
+
+    @Override
+    public void populateModel(OmniboxSuggestion suggestion, PropertyModel model, int position) {
+        maybeFetchAnswerIcon(suggestion, model);
+
+        model.set(SuggestionViewProperties.SUGGESTION_ICON_TYPE,
+                SuggestionViewProperties.SuggestionIcon.UNDEFINED);
+        model.set(SuggestionViewProperties.DELEGATE,
+                mSuggestionHost.createSuggestionViewDelegate(suggestion, position));
+
+        setStateForSuggestion(model, suggestion.getAnswer());
+    }
+
+    @Override
+    public void onUrlFocusChange(boolean hasFocus) {
+        if (!hasFocus) mImageFetcher.clearCache();
+    }
+
+    private void maybeFetchAnswerIcon(OmniboxSuggestion suggestion, PropertyModel model) {
+        ThreadUtils.assertOnUiThread();
+
+        // Attempting to fetch answer data before we have a profile to request it for.
+        if (mSuggestionHost.getCurrentProfile() == null) return;
+
+        if (!suggestion.hasAnswer()) return;
+        final String url = suggestion.getAnswer().getSecondLine().getImage();
+        if (url == null) return;
+
+        // Do not make duplicate answer image requests for the same URL (to avoid generating
+        // duplicate bitmaps for the same image).
+        if (mPendingAnswerRequestUrls.containsKey(url)) {
+            mPendingAnswerRequestUrls.get(url).add(model);
+            return;
+        }
+
+        List<PropertyModel> models = new ArrayList<>();
+        models.add(model);
+        mPendingAnswerRequestUrls.put(url, models);
+        mImageFetcher.requestAnswersImage(mSuggestionHost.getCurrentProfile(), url,
+                new AnswersImageFetcher.AnswersImageObserver() {
+                    @Override
+                    public void onAnswersImageChanged(Bitmap bitmap) {
+                        ThreadUtils.assertOnUiThread();
+
+                        List<PropertyModel> models = mPendingAnswerRequestUrls.remove(url);
+                        boolean didUpdate = false;
+                        for (int i = 0; i < models.size(); i++) {
+                            PropertyModel model = models.get(i);
+                            if (!mSuggestionHost.isActiveModel(model)) continue;
+                            model.set(SuggestionViewProperties.ANSWER_IMAGE, bitmap);
+                            didUpdate = true;
+                        }
+                        if (didUpdate) mSuggestionHost.notifyPropertyModelsChanged();
+                    }
+                });
+    }
+
+    /**
+     * Sets both lines of the Omnibox suggestion based on an Answers in Suggest result.
+     */
+    private void setStateForSuggestion(PropertyModel model, SuggestionAnswer answer) {
+        float density = mContext.getResources().getDisplayMetrics().density;
+        SuggestionAnswer.ImageLine firstLine = answer.getFirstLine();
+        SuggestionAnswer.ImageLine secondLine = answer.getSecondLine();
+        int numAnswerLines = parseNumAnswerLines(secondLine.getTextFields());
+        if (numAnswerLines == -1) numAnswerLines = 1;
+        model.set(SuggestionViewProperties.IS_ANSWER, true);
+
+        if (mEnableNewAnswerLayout) {
+            model.set(SuggestionViewProperties.TEXT_LINE_2_SIZING,
+                    Pair.create(TypedValue.COMPLEX_UNIT_SP,
+                            (float) AnswerTextBuilder.getMaxTextHeightSp(firstLine)));
+            model.set(SuggestionViewProperties.TEXT_LINE_2_TEXT,
+                    new SuggestionTextContainer(
+                            AnswerTextBuilder.buildSpannable(firstLine, density)));
+
+            model.set(SuggestionViewProperties.TEXT_LINE_1_SIZING,
+                    Pair.create(TypedValue.COMPLEX_UNIT_SP,
+                            (float) AnswerTextBuilder.getMaxTextHeightSp(secondLine)));
+            model.set(SuggestionViewProperties.TEXT_LINE_1_TEXT,
+                    new SuggestionTextContainer(
+                            AnswerTextBuilder.buildSpannable(secondLine, density)));
+            model.set(SuggestionViewProperties.TEXT_LINE_1_MAX_LINES, numAnswerLines);
+            model.set(SuggestionViewProperties.TEXT_LINE_2_MAX_LINES, 1);
+            model.set(SuggestionViewProperties.TEXT_LINE_2_TEXT_COLOR,
+                    SuggestionViewViewBinder.getStandardFontColor(
+                            mContext, model.get(SuggestionCommonProperties.USE_DARK_COLORS)));
+            model.set(SuggestionViewProperties.TEXT_LINE_1_TEXT_DIRECTION,
+                    View.TEXT_DIRECTION_INHERIT);
+        } else {
+            model.set(SuggestionViewProperties.TEXT_LINE_1_SIZING,
+                    Pair.create(TypedValue.COMPLEX_UNIT_SP,
+                            (float) AnswerTextBuilder.getMaxTextHeightSp(firstLine)));
+            model.set(SuggestionViewProperties.TEXT_LINE_1_TEXT,
+                    new SuggestionTextContainer(
+                            AnswerTextBuilder.buildSpannable(firstLine, density)));
+
+            model.set(SuggestionViewProperties.TEXT_LINE_2_SIZING,
+                    Pair.create(TypedValue.COMPLEX_UNIT_SP,
+                            (float) AnswerTextBuilder.getMaxTextHeightSp(secondLine)));
+            model.set(SuggestionViewProperties.TEXT_LINE_2_TEXT,
+                    new SuggestionTextContainer(
+                            AnswerTextBuilder.buildSpannable(secondLine, density)));
+            model.set(SuggestionViewProperties.TEXT_LINE_1_MAX_LINES, 1);
+            model.set(SuggestionViewProperties.TEXT_LINE_2_MAX_LINES, numAnswerLines);
+            model.set(SuggestionViewProperties.TEXT_LINE_2_TEXT_COLOR,
+                    SuggestionViewViewBinder.getStandardFontColor(
+                            mContext, model.get(SuggestionCommonProperties.USE_DARK_COLORS)));
+            model.set(SuggestionViewProperties.TEXT_LINE_2_TEXT_DIRECTION,
+                    View.TEXT_DIRECTION_INHERIT);
+        }
+
+        model.set(SuggestionViewProperties.HAS_ANSWER_IMAGE, secondLine.hasImage());
+
+        model.set(SuggestionViewProperties.REFINABLE, true);
+
+        @SuggestionIcon
+        int icon = SuggestionIcon.MAGNIFIER;
+        if (mEnableNewAnswerLayout) {
+            switch (answer.getType()) {
+                case AnswerType.DICTIONARY:
+                    icon = SuggestionIcon.DICTIONARY;
+                    break;
+                case AnswerType.FINANCE:
+                    icon = SuggestionIcon.FINANCE;
+                    break;
+                case AnswerType.KNOWLEDGE_GRAPH:
+                    icon = SuggestionIcon.KNOWLEDGE;
+                    break;
+                case AnswerType.SUNRISE:
+                    icon = SuggestionIcon.SUNRISE;
+                    break;
+                case AnswerType.TRANSLATION:
+                    icon = SuggestionIcon.TRANSLATION;
+                    break;
+                case AnswerType.WEATHER:
+                    icon = SuggestionIcon.WEATHER;
+                    break;
+                case AnswerType.WHEN_IS:
+                    icon = SuggestionIcon.EVENT;
+                    break;
+                case AnswerType.CURRENCY:
+                    icon = SuggestionIcon.CURRENCY;
+                    break;
+                case AnswerType.SPORTS:
+                    icon = SuggestionIcon.SPORTS;
+            }
+        }
+
+        model.set(SuggestionViewProperties.SUGGESTION_ICON_TYPE, icon);
+    }
+
+    private static int parseNumAnswerLines(List<SuggestionAnswer.TextField> textFields) {
+        for (int i = 0; i < textFields.size(); i++) {
+            if (textFields.get(i).hasNumLines()) {
+                return Math.min(3, textFields.get(i).getNumLines());
+            }
+        }
+        return -1;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/BasicSuggestionProcessor.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/BasicSuggestionProcessor.java
index 0a7b049..b0e7daf72 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/BasicSuggestionProcessor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/BasicSuggestionProcessor.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.omnibox.suggestions.basic;
 
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.TextUtils;
@@ -14,64 +13,25 @@
 import android.util.TypedValue;
 import android.view.View;
 
-import org.chromium.base.ThreadUtils;
-import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.omnibox.MatchClassificationStyle;
 import org.chromium.chrome.browser.omnibox.OmniboxSuggestionType;
 import org.chromium.chrome.browser.omnibox.UrlBarEditingTextStateProvider;
-import org.chromium.chrome.browser.omnibox.suggestions.AnswersImageFetcher;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator.SuggestionProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestion;
 import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionCommonProperties;
-import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionView.SuggestionViewDelegate;
 import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionViewProperties.SuggestionIcon;
 import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionViewProperties.SuggestionTextContainer;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.components.omnibox.AnswerType;
-import org.chromium.components.omnibox.SuggestionAnswer;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
-/** A class that handles model and view creation for the most commonly used omnibox suggestion. */
+/** A class that handles model and view creation for the basic omnibox suggestions. */
 public class BasicSuggestionProcessor implements SuggestionProcessor {
-    /** A mechanism for creating {@link SuggestionViewDelegate}s. */
-    public interface SuggestionHost {
-        /**
-         * @param suggestion The suggestion to create the delegate for.
-         * @param position The position of the delegate in the list.
-         * @return A delegate for the specified suggestion.
-         */
-        SuggestionViewDelegate createSuggestionViewDelegate(
-                OmniboxSuggestion suggestion, int position);
-
-        /**
-         * @param model The model to check.
-         * @return Whether the model is active in the list being shown.
-         */
-        boolean isActiveModel(PropertyModel model);
-
-        /**
-         * Notify the host that the suggestion models have changed.
-         */
-        void notifyPropertyModelsChanged();
-
-        /**
-         * @return The browser's active profile.
-         */
-        Profile getCurrentProfile();
-    }
-
-    private final Map<String, List<PropertyModel>> mPendingAnswerRequestUrls;
     private final Context mContext;
     private final SuggestionHost mSuggestionHost;
-    private final AnswersImageFetcher mImageFetcher;
     private final UrlBarEditingTextStateProvider mUrlBarEditingTextProvider;
-    private boolean mEnableNewAnswerLayout;
 
     /**
      * @param context An Android context.
@@ -82,8 +42,6 @@
             UrlBarEditingTextStateProvider editingTextProvider) {
         mContext = context;
         mSuggestionHost = suggestionHost;
-        mPendingAnswerRequestUrls = new HashMap<>();
-        mImageFetcher = new AnswersImageFetcher();
         mUrlBarEditingTextProvider = editingTextProvider;
     }
 
@@ -92,15 +50,6 @@
         return true;
     }
 
-    /**
-     * Signals that native initialization has completed.
-     */
-    public void onNativeInitialized() {
-        // Experiment: controls presence of certain answer icon types.
-        mEnableNewAnswerLayout =
-                ChromeFeatureList.isEnabled(ChromeFeatureList.OMNIBOX_NEW_ANSWER_LAYOUT);
-    }
-
     @Override
     public int getViewTypeId() {
         return OmniboxSuggestionUiType.DEFAULT;
@@ -113,162 +62,18 @@
 
     @Override
     public void populateModel(OmniboxSuggestion suggestion, PropertyModel model, int position) {
-        maybeFetchAnswerIcon(suggestion, model);
-
         model.set(SuggestionViewProperties.SUGGESTION_ICON_TYPE,
                 SuggestionViewProperties.SuggestionIcon.UNDEFINED);
         model.set(SuggestionViewProperties.DELEGATE,
                 mSuggestionHost.createSuggestionViewDelegate(suggestion, position));
 
-        // Suggestions with attached answers are rendered with rich results regardless of which
-        // suggestion type they are.
-        if (suggestion.hasAnswer()) {
-            setStateForAnswerSuggestion(model, suggestion.getAnswer());
-        } else {
-            setStateForTextSuggestion(model, suggestion);
-        }
+        setStateForSuggestion(model, suggestion);
     }
 
     @Override
-    public void onUrlFocusChange(boolean hasFocus) {
-        if (!hasFocus) mImageFetcher.clearCache();
-    }
+    public void onUrlFocusChange(boolean hasFocus) {}
 
-    private void maybeFetchAnswerIcon(OmniboxSuggestion suggestion, PropertyModel model) {
-        ThreadUtils.assertOnUiThread();
-
-        // Attempting to fetch answer data before we have a profile to request it for.
-        if (mSuggestionHost.getCurrentProfile() == null) return;
-
-        if (!suggestion.hasAnswer()) return;
-        final String url = suggestion.getAnswer().getSecondLine().getImage();
-        if (url == null) return;
-
-        // Do not make duplicate answer image requests for the same URL (to avoid generating
-        // duplicate bitmaps for the same image).
-        if (mPendingAnswerRequestUrls.containsKey(url)) {
-            mPendingAnswerRequestUrls.get(url).add(model);
-            return;
-        }
-
-        List<PropertyModel> models = new ArrayList<>();
-        models.add(model);
-        mPendingAnswerRequestUrls.put(url, models);
-        mImageFetcher.requestAnswersImage(mSuggestionHost.getCurrentProfile(), url,
-                new AnswersImageFetcher.AnswersImageObserver() {
-                    @Override
-                    public void onAnswersImageChanged(Bitmap bitmap) {
-                        ThreadUtils.assertOnUiThread();
-
-                        List<PropertyModel> models = mPendingAnswerRequestUrls.remove(url);
-                        boolean didUpdate = false;
-                        for (int i = 0; i < models.size(); i++) {
-                            PropertyModel model = models.get(i);
-                            if (!mSuggestionHost.isActiveModel(model)) continue;
-                            model.set(SuggestionViewProperties.ANSWER_IMAGE, bitmap);
-                            didUpdate = true;
-                        }
-                        if (didUpdate) mSuggestionHost.notifyPropertyModelsChanged();
-                    }
-                });
-    }
-
-    /**
-     * Sets both lines of the Omnibox suggestion based on an Answers in Suggest result.
-     */
-    private void setStateForAnswerSuggestion(PropertyModel model, SuggestionAnswer answer) {
-        float density = mContext.getResources().getDisplayMetrics().density;
-        SuggestionAnswer.ImageLine firstLine = answer.getFirstLine();
-        SuggestionAnswer.ImageLine secondLine = answer.getSecondLine();
-        int numAnswerLines = parseNumAnswerLines(secondLine.getTextFields());
-        if (numAnswerLines == -1) numAnswerLines = 1;
-        model.set(SuggestionViewProperties.IS_ANSWER, true);
-
-        if (mEnableNewAnswerLayout) {
-            model.set(SuggestionViewProperties.TEXT_LINE_2_SIZING,
-                    Pair.create(TypedValue.COMPLEX_UNIT_SP,
-                            (float) AnswerTextBuilder.getMaxTextHeightSp(firstLine)));
-            model.set(SuggestionViewProperties.TEXT_LINE_2_TEXT,
-                    new SuggestionTextContainer(
-                            AnswerTextBuilder.buildSpannable(firstLine, density)));
-
-            model.set(SuggestionViewProperties.TEXT_LINE_1_SIZING,
-                    Pair.create(TypedValue.COMPLEX_UNIT_SP,
-                            (float) AnswerTextBuilder.getMaxTextHeightSp(secondLine)));
-            model.set(SuggestionViewProperties.TEXT_LINE_1_TEXT,
-                    new SuggestionTextContainer(
-                            AnswerTextBuilder.buildSpannable(secondLine, density)));
-            model.set(SuggestionViewProperties.TEXT_LINE_1_MAX_LINES, numAnswerLines);
-            model.set(SuggestionViewProperties.TEXT_LINE_2_MAX_LINES, 1);
-            model.set(SuggestionViewProperties.TEXT_LINE_2_TEXT_COLOR,
-                    SuggestionViewViewBinder.getStandardFontColor(
-                            mContext, model.get(SuggestionCommonProperties.USE_DARK_COLORS)));
-            model.set(SuggestionViewProperties.TEXT_LINE_1_TEXT_DIRECTION,
-                    View.TEXT_DIRECTION_INHERIT);
-        } else {
-            model.set(SuggestionViewProperties.TEXT_LINE_1_SIZING,
-                    Pair.create(TypedValue.COMPLEX_UNIT_SP,
-                            (float) AnswerTextBuilder.getMaxTextHeightSp(firstLine)));
-            model.set(SuggestionViewProperties.TEXT_LINE_1_TEXT,
-                    new SuggestionTextContainer(
-                            AnswerTextBuilder.buildSpannable(firstLine, density)));
-
-            model.set(SuggestionViewProperties.TEXT_LINE_2_SIZING,
-                    Pair.create(TypedValue.COMPLEX_UNIT_SP,
-                            (float) AnswerTextBuilder.getMaxTextHeightSp(secondLine)));
-            model.set(SuggestionViewProperties.TEXT_LINE_2_TEXT,
-                    new SuggestionTextContainer(
-                            AnswerTextBuilder.buildSpannable(secondLine, density)));
-            model.set(SuggestionViewProperties.TEXT_LINE_1_MAX_LINES, 1);
-            model.set(SuggestionViewProperties.TEXT_LINE_2_MAX_LINES, numAnswerLines);
-            model.set(SuggestionViewProperties.TEXT_LINE_2_TEXT_COLOR,
-                    SuggestionViewViewBinder.getStandardFontColor(
-                            mContext, model.get(SuggestionCommonProperties.USE_DARK_COLORS)));
-            model.set(SuggestionViewProperties.TEXT_LINE_2_TEXT_DIRECTION,
-                    View.TEXT_DIRECTION_INHERIT);
-        }
-
-        model.set(SuggestionViewProperties.HAS_ANSWER_IMAGE, secondLine.hasImage());
-
-        model.set(SuggestionViewProperties.REFINABLE, true);
-
-        @SuggestionIcon
-        int icon = SuggestionIcon.MAGNIFIER;
-        if (mEnableNewAnswerLayout) {
-            switch (answer.getType()) {
-                case AnswerType.DICTIONARY:
-                    icon = SuggestionIcon.DICTIONARY;
-                    break;
-                case AnswerType.FINANCE:
-                    icon = SuggestionIcon.FINANCE;
-                    break;
-                case AnswerType.KNOWLEDGE_GRAPH:
-                    icon = SuggestionIcon.KNOWLEDGE;
-                    break;
-                case AnswerType.SUNRISE:
-                    icon = SuggestionIcon.SUNRISE;
-                    break;
-                case AnswerType.TRANSLATION:
-                    icon = SuggestionIcon.TRANSLATION;
-                    break;
-                case AnswerType.WEATHER:
-                    icon = SuggestionIcon.WEATHER;
-                    break;
-                case AnswerType.WHEN_IS:
-                    icon = SuggestionIcon.EVENT;
-                    break;
-                case AnswerType.CURRENCY:
-                    icon = SuggestionIcon.CURRENCY;
-                    break;
-                case AnswerType.SPORTS:
-                    icon = SuggestionIcon.SPORTS;
-            }
-        }
-
-        model.set(SuggestionViewProperties.SUGGESTION_ICON_TYPE, icon);
-    }
-
-    private void setStateForTextSuggestion(PropertyModel model, OmniboxSuggestion suggestion) {
+    private void setStateForSuggestion(PropertyModel model, OmniboxSuggestion suggestion) {
         int suggestionType = suggestion.getType();
         @SuggestionIcon
         int suggestionIcon;
@@ -406,15 +211,6 @@
         return str;
     }
 
-    private static int parseNumAnswerLines(List<SuggestionAnswer.TextField> textFields) {
-        for (int i = 0; i < textFields.size(); i++) {
-            if (textFields.get(i).hasNumLines()) {
-                return Math.min(3, textFields.get(i).getNumLines());
-            }
-        }
-        return -1;
-    }
-
     private static boolean applyHighlightToMatchRegions(
             Spannable str, List<OmniboxSuggestion.MatchClassification> classifications) {
         boolean hasMatch = false;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionHost.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionHost.java
new file mode 100644
index 0000000..886fce3
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionHost.java
@@ -0,0 +1,36 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.omnibox.suggestions.basic;
+
+import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestion;
+import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionView.SuggestionViewDelegate;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/** A mechanism for creating {@link SuggestionViewDelegate}s. */
+public interface SuggestionHost {
+    /**
+     * @param suggestion The suggestion to create the delegate for.
+     * @param position The position of the delegate in the list.
+     * @return A delegate for the specified suggestion.
+     */
+    SuggestionViewDelegate createSuggestionViewDelegate(OmniboxSuggestion suggestion, int position);
+
+    /**
+     * @param model The model to check.
+     * @return Whether the model is active in the list being shown.
+     */
+    boolean isActiveModel(PropertyModel model);
+
+    /**
+     * Notify the host that the suggestion models have changed.
+     */
+    void notifyPropertyModelsChanged();
+
+    /**
+     * @return The browser's active profile.
+     */
+    Profile getCurrentProfile();
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsRecyclerView.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsRecyclerView.java
index 999b6cb2..f56c2d9a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsRecyclerView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsRecyclerView.java
@@ -82,6 +82,8 @@
     /** The context menu manager for this view. */
     private ContextMenuManager mContextMenuManager;
 
+    private boolean mIsCardBeingSwiped;
+
     public SuggestionsRecyclerView(Context context) {
         this(context, null);
     }
@@ -381,6 +383,8 @@
         @Override
         public void onChildDraw(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder,
                 float dX, float dY, int actionState, boolean isCurrentlyActive) {
+            mIsCardBeingSwiped = isCurrentlyActive && dX != 0.f;
+
             // In some cases a removed child may call this method when unrelated items are
             // interacted with (https://crbug.com/664466, b/32900699), but in that case
             // getSiblingDismissalViewHolders() below will return an empty list.
@@ -393,6 +397,14 @@
         }
     }
 
+    /**
+     * Tells if one of card views is being swiped now.
+     * @return {@code true} if a card view is being swiped.
+     */
+    public boolean isCardBeingSwiped() {
+        return mIsCardBeingSwiped;
+    }
+
     private List<ViewHolder> getDismissalGroupViewHolders(ViewHolder viewHolder) {
         int position = viewHolder.getAdapterPosition();
         if (position == NO_POSITION) return Collections.emptyList();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
index f70c5d6..adbe5ef 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -6,7 +6,6 @@
 
 import android.annotation.SuppressLint;
 import android.app.Activity;
-import android.app.Application;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -1052,6 +1051,7 @@
                 maybeShowNativePage(nativePage.getUrl(), true);
             }
             NativePageAssassin.getInstance().tabShown(this);
+            TabImportanceManager.tabShown(this);
 
             // If the page is still loading, update the progress bar (otherwise it would not show
             // until the renderer notifies of new progress being made).
@@ -1095,7 +1095,7 @@
         }
     }
 
-    public final void setImportance(@ChildProcessImportance int importance) {
+    /* package */ final void setImportance(@ChildProcessImportance int importance) {
         if (mImportance == importance) return;
         mImportance = importance;
         WebContents webContents = getWebContents();
@@ -1681,6 +1681,8 @@
         destroyNativePageInternal(currentNativePage);
         destroyWebContents(true);
 
+        TabImportanceManager.tabDestroyed(this);
+
         // Destroys the native tab after destroying the ContentView but before destroying the
         // InfoBarContainer. The native tab should be destroyed before the infobar container as
         // destroying the native tab cleanups up any remaining infobars. The infobar container
@@ -2132,7 +2134,7 @@
     }
 
     private static Rect getEstimatedContentSize(Context context) {
-        return ExternalPrerenderHandler.estimateContentSize((Application) context, false);
+        return ExternalPrerenderHandler.estimateContentSize(context, false);
     }
 
     /** This is currently called when committing a pre-rendered page. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImportanceManager.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImportanceManager.java
new file mode 100644
index 0000000..e38137d
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImportanceManager.java
@@ -0,0 +1,45 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.tab;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.content_public.browser.ChildProcessImportance;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Manages the importance for all Tabs in the same process. Ensures that at least one tab is
+ * important, and unless multiple tabs are simultaneously visible, only one is important.
+ */
+public class TabImportanceManager {
+    // Typically no more than 2 visible tabs at once (multi-window).
+    private static final List<Tab> sImportantTabs = new ArrayList<>(2);
+
+    public static void tabShown(Tab shownTab) {
+        ThreadUtils.assertOnUiThread();
+        shownTab.setImportance(ChildProcessImportance.MODERATE);
+        // Shown tabs should always be important, but hidden tabs should only be normal if there's
+        // at least one important tab for two reasons:
+        // 1. We could be switching between tabs within the same process and don't want the process
+        //      to be killed while switching.
+        // 2. We want the most recently used tab to stay alive.
+        Iterator<Tab> it = sImportantTabs.iterator();
+        while (it.hasNext()) {
+            Tab importantTab = it.next();
+            if (importantTab.isHidden()) {
+                importantTab.setImportance(ChildProcessImportance.NORMAL);
+                it.remove();
+            }
+        }
+        if (!sImportantTabs.contains(shownTab)) sImportantTabs.add(shownTab);
+    }
+
+    public static void tabDestroyed(Tab tab) {
+        ThreadUtils.assertOnUiThread();
+        sImportantTabs.remove(tab);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorImpl.java
index 87acda5..9326962 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorImpl.java
@@ -14,7 +14,6 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.Tab.TabHidingType;
 import org.chromium.chrome.browser.tabmodel.TabPersistentStore.TabPersistentStoreObserver;
-import org.chromium.content_public.browser.ChildProcessImportance;
 import org.chromium.content_public.browser.LoadUrlParams;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -341,7 +340,6 @@
     public void requestToShowTab(Tab tab, @TabSelectionType int type) {
         boolean isFromExternalApp =
                 tab != null && tab.getLaunchType() == TabLaunchType.FROM_EXTERNAL_APP;
-        Tab tabToDropImportance = null;
         if (mVisibleTab != tab && tab != null && !tab.isNativePage()) {
             TabModelImpl.startTabSwitchLatencyTiming(type);
         }
@@ -357,14 +355,10 @@
                 mVisibleTab.hide(TabHidingType.CHANGED_TABS);
                 mTabSaver.addTabToSaveQueue(mVisibleTab);
             }
-            tabToDropImportance = mVisibleTab;
             mVisibleTab = null;
         }
 
         if (tab == null) {
-            if (tabToDropImportance != null) {
-                tabToDropImportance.setImportance(ChildProcessImportance.NORMAL);
-            }
             notifyChanged();
             return;
         }
@@ -377,9 +371,6 @@
             // |tabToDropImportance| must be null, so no need to drop importance.
             return;
         }
-        if (tabToDropImportance == null) {
-            tabToDropImportance = mVisibleTab;
-        }
         mVisibleTab = tab;
 
         // Don't execute the tab display part if Chrome has just been sent to background. This
@@ -389,13 +380,6 @@
             tab.show(type);
             mUma.onShowTab(tab.getId(), tab.isBeingRestored());
         }
-
-        // Always raise importance before lowering it on old Tab because in case these two Tabs
-        // are hosted by the same process, the process importance is not dropped momentarily.
-        mVisibleTab.setImportance(ChildProcessImportance.MODERATE);
-        if (tabToDropImportance != null) {
-            tabToDropImportance.setImportance(ChildProcessImportance.NORMAL);
-        }
     }
 
     private void cacheTabBitmap(Tab tabToCache) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
index 5d3907ea..8e80a46 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
@@ -19,19 +19,19 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.ActivityTabProvider.ActivityTabTabObserver;
+import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.ThemeColorProvider.TintObserver;
 import org.chromium.chrome.browser.ntp.NewTabPage;
 import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.ui.widget.ChromeImageButton;
 
 /**
  * The home button.
  */
-public class HomeButton extends ChromeImageButton implements ThemeColorObserver,
-                                                             OnCreateContextMenuListener,
-                                                             MenuItem.OnMenuItemClickListener {
+public class HomeButton extends ChromeImageButton
+        implements TintObserver, OnCreateContextMenuListener, MenuItem.OnMenuItemClickListener {
     private static final int ID_REMOVE = 0;
 
     /** A provider that notifies components when the theme color changes.*/
@@ -55,7 +55,7 @@
 
     public void destroy() {
         if (mThemeColorProvider != null) {
-            mThemeColorProvider.removeObserver(this);
+            mThemeColorProvider.removeTintObserver(this);
             mThemeColorProvider = null;
         }
         if (mActivityTabTabObserver != null) {
@@ -66,11 +66,11 @@
 
     public void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
         mThemeColorProvider = themeColorProvider;
-        mThemeColorProvider.addObserver(this);
+        mThemeColorProvider.addTintObserver(this);
     }
 
     @Override
-    public void onThemeColorChanged(ColorStateList tint, int primaryColor) {
+    public void onTintChanged(ColorStateList tint, boolean useLight) {
         ApiCompatibilityUtils.setImageTintList(this, tint);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/MenuButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/MenuButton.java
index 0188e10..9e14b33 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/MenuButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/MenuButton.java
@@ -15,14 +15,14 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.ThemeColorProvider.TintObserver;
 import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
-import org.chromium.chrome.browser.util.ColorUtils;
 
 /**
  * The overflow menu button.
  */
-public class MenuButton extends FrameLayout implements ThemeColorObserver {
+public class MenuButton extends FrameLayout implements TintObserver {
     /** The {@link ImageButton} for the menu button. */
     private ImageButton mMenuImageButton;
 
@@ -151,18 +151,18 @@
 
     public void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
         mThemeColorProvider = themeColorProvider;
-        mThemeColorProvider.addObserver(this);
+        mThemeColorProvider.addTintObserver(this);
     }
 
     @Override
-    public void onThemeColorChanged(ColorStateList tintList, int primaryColor) {
+    public void onTintChanged(ColorStateList tintList, boolean useLight) {
         ApiCompatibilityUtils.setImageTintList(mMenuImageButton, tintList);
-        setUseLightDrawables(ColorUtils.shouldUseLightForegroundOnBackground(primaryColor));
+        setUseLightDrawables(useLight);
     }
 
     public void destroy() {
         if (mThemeColorProvider != null) {
-            mThemeColorProvider.removeObserver(this);
+            mThemeColorProvider.removeTintObserver(this);
             mThemeColorProvider = null;
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherButtonCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherButtonCoordinator.java
index d2f0130..9becd72b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherButtonCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherButtonCoordinator.java
@@ -9,11 +9,12 @@
 import android.view.ViewGroup;
 
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.ThemeColorProvider.TintObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
 import org.chromium.chrome.browser.toolbar.TabCountProvider.TabCountObserver;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
 import org.chromium.chrome.browser.util.AccessibilityUtil;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
@@ -36,7 +37,7 @@
     private TabModelSelectorTabModelObserver mTabModelSelectorTabModelObserver;
 
     private ThemeColorProvider mThemeColorProvider;
-    private ThemeColorObserver mThemeColorObserver;
+    private TintObserver mTintObserver;
 
     private TabCountProvider mTabCountProvider;
     private TabCountObserver mTabCountObserver;
@@ -65,13 +66,13 @@
 
     public void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
         mThemeColorProvider = themeColorProvider;
-        mThemeColorObserver = new ThemeColorObserver() {
+        mTintObserver = new TintObserver() {
             @Override
-            public void onThemeColorChanged(ColorStateList tint, int primaryColor) {
+            public void onTintChanged(ColorStateList tint, boolean useLight) {
                 mTabSwitcherButtonModel.set(TabSwitcherButtonProperties.TINT, tint);
             }
         };
-        mThemeColorProvider.addObserver(mThemeColorObserver);
+        mThemeColorProvider.addTintObserver(mTintObserver);
     }
 
     public void setTabCountProvider(TabCountProvider tabCountProvider) {
@@ -87,7 +88,7 @@
 
     public void destroy() {
         if (mThemeColorProvider != null) {
-            mThemeColorProvider.removeObserver(mThemeColorObserver);
+            mThemeColorProvider.removeTintObserver(mTintObserver);
             mThemeColorProvider = null;
         }
         if (mTabCountProvider != null) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ThemeColorProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ThemeColorProvider.java
deleted file mode 100644
index eac9716..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ThemeColorProvider.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.toolbar;
-
-import android.content.res.ColorStateList;
-
-/**
- * An interface that provides the current theme color.
- */
-public interface ThemeColorProvider {
-    /**
-     * An interface to be notified about changes to the theme color.
-     */
-    public interface ThemeColorObserver {
-        void onThemeColorChanged(ColorStateList tint, int primaryColor);
-    }
-
-    /**
-     * @param observer An observer to be notified of theme color changes.
-     */
-    void addObserver(ThemeColorObserver observer);
-
-    /**
-     * @param observer Remove the observer.
-     */
-    void removeObserver(ThemeColorObserver observer);
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index a1c6d3bc..155447f5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -33,6 +33,8 @@
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.TabLoadStatus;
+import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.ThemeColorProvider.ThemeColorObserver;
 import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.WindowDelegate;
 import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
@@ -92,7 +94,6 @@
 import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator;
 import org.chromium.chrome.browser.toolbar.top.ViewShiftingActionBarDelegate;
 import org.chromium.chrome.browser.translate.TranslateBridge;
-import org.chromium.chrome.browser.util.ColorUtils;
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.chrome.browser.widget.ScrimView;
 import org.chromium.chrome.browser.widget.ScrimView.ScrimObserver;
@@ -122,7 +123,8 @@
  * Contains logic for managing the toolbar visual component.  This class manages the interactions
  * with the rest of the application to ensure the toolbar is always visually up to date.
  */
-public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlFocusChangeListener {
+public class ToolbarManager
+        implements ScrimObserver, ToolbarTabController, UrlFocusChangeListener, ThemeColorObserver {
     /**
      * Handle UI updates of menu icons. Only applicable for phones.
      */
@@ -168,6 +170,7 @@
     private final AsyncViewProvider<ToolbarLayout> mToolbarProvider;
     private final IncognitoStateProvider mIncognitoStateProvider;
     private final TabCountProvider mTabCountProvider;
+    private final ThemeColorProvider mThemeColorProvider;
     private TopToolbarCoordinator mToolbar;
     private final ToolbarControlContainer mControlContainer;
 
@@ -235,7 +238,8 @@
      */
     public ToolbarManager(ChromeActivity activity, ToolbarControlContainer controlContainer,
             final AppMenuHandler menuHandler, AppMenuPropertiesDelegate appMenuPropertiesDelegate,
-            Invalidator invalidator, Callback<Boolean> urlFocusChangedCallback) {
+            Invalidator invalidator, Callback<Boolean> urlFocusChangedCallback,
+            ThemeColorProvider themeColorProvider) {
         mActivity = activity;
         mActionBarDelegate = new ViewShiftingActionBarDelegate(activity, controlContainer);
 
@@ -301,6 +305,8 @@
         mToolbarProvider.whenLoaded((toolbar)
                                             -> onToolbarInflationComplete(menuHandler,
                                                     appMenuPropertiesDelegate, invalidator));
+        mThemeColorProvider = themeColorProvider;
+        mThemeColorProvider.addThemeColorObserver(this);
     }
 
     @Override
@@ -1173,6 +1179,8 @@
             mLocationBar.removeUrlFocusChangeListener(mLocationBarFocusObserver);
             mLocationBarFocusObserver = null;
         }
+
+        if (mThemeColorProvider != null) mThemeColorProvider.removeThemeColorObserver(this);
     }
 
     /**
@@ -1412,7 +1420,8 @@
      * @param color The primary color for the current tab.
      * @param shouldAnimate Whether the change of color should be animated.
      */
-    public void updatePrimaryColor(int color, boolean shouldAnimate) {
+    @Override
+    public void onThemeColorChanged(int color, boolean shouldAnimate) {
         if (!mShouldUpdateToolbarPrimaryColor) return;
 
         boolean colorChanged = mCurrentThemeColor != color;
@@ -1652,11 +1661,6 @@
                 }
                 if (tab != null) tab.addObserver(mTabObserver);
             }
-            int defaultPrimaryColor =
-                    ColorUtils.getDefaultThemeColor(mActivity.getResources(), isIncognito);
-            int primaryColor =
-                    tab != null ? TabThemeColorHelper.getColor(tab) : defaultPrimaryColor;
-            updatePrimaryColor(primaryColor, false);
 
             mToolbar.onTabOrModelChanged();
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarNewTabButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarNewTabButton.java
index 56a1ad5..6cfab10 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarNewTabButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarNewTabButton.java
@@ -16,18 +16,19 @@
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.ThemeColorProvider.ThemeColorObserver;
+import org.chromium.chrome.browser.ThemeColorProvider.TintObserver;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider.IncognitoStateObserver;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
 import org.chromium.chrome.browser.util.ColorUtils;
 import org.chromium.ui.widget.ChromeImageButton;
 
 /**
  * The tab switcher new tab button.
  */
-class BottomToolbarNewTabButton
-        extends ChromeImageButton implements IncognitoStateObserver, ThemeColorObserver {
+class BottomToolbarNewTabButton extends ChromeImageButton
+        implements IncognitoStateObserver, ThemeColorObserver, TintObserver {
     /** The gray pill background behind the plus icon. */
     private final Drawable mBackground;
 
@@ -62,7 +63,8 @@
             mIncognitoStateProvider = null;
         }
         if (mThemeColorProvider != null) {
-            mThemeColorProvider.removeObserver(this);
+            mThemeColorProvider.removeThemeColorObserver(this);
+            mThemeColorProvider.removeTintObserver(this);
             mThemeColorProvider = null;
         }
     }
@@ -88,14 +90,19 @@
 
     void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
         mThemeColorProvider = themeColorProvider;
-        mThemeColorProvider.addObserver(this);
+        mThemeColorProvider.addThemeColorObserver(this);
+        mThemeColorProvider.addTintObserver(this);
     }
 
     @Override
-    public void onThemeColorChanged(ColorStateList tint, int primaryColor) {
-        ApiCompatibilityUtils.setImageTintList(this, tint);
+    public void onThemeColorChanged(int primaryColor, boolean shouldAnimate) {
         mBackground.setColorFilter(
                 ColorUtils.getTextBoxColorForToolbarBackground(mResources, false, primaryColor),
                 PorterDuff.Mode.SRC_IN);
     }
+
+    @Override
+    public void onTintChanged(ColorStateList tint, boolean useLight) {
+        ApiCompatibilityUtils.setImageTintList(this, tint);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarThemeColorProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarThemeColorProvider.java
index f0b39ca..69cdb44 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarThemeColorProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarThemeColorProvider.java
@@ -5,34 +5,22 @@
 package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.content.Context;
-import android.content.res.ColorStateList;
-import android.support.v7.content.res.AppCompatResources;
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ObserverList;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.compositor.layouts.EmptyOverviewModeObserver;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior.OverviewModeObserver;
 import org.chromium.chrome.browser.device.DeviceClassManager;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider.IncognitoStateObserver;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
 
 /** A ThemeColorProvider for the bottom toolbar. */
-public class BottomToolbarThemeColorProvider implements ThemeColorProvider, IncognitoStateObserver {
-    /** List of {@link ThemeColorObserver}s. These are used to broadcast events to listeners. */
-    private final ObserverList<ThemeColorObserver> mThemeColorObservers;
-
-    /** Tint to be used in dark mode. */
-    private final ColorStateList mDarkModeTint;
-
-    /** Tint to be used in light mode. */
-    private final ColorStateList mLightModeTint;
-
+public class BottomToolbarThemeColorProvider
+        extends ThemeColorProvider implements IncognitoStateObserver {
     /** Primary color for light mode. */
     private final int mLightPrimaryColor;
 
@@ -46,7 +34,7 @@
     private OverviewModeBehavior mOverviewModeBehavior;
 
     /** Observer to know when overview mode is entered/exited. */
-    private OverviewModeObserver mOverviewModeObserver;
+    private final OverviewModeObserver mOverviewModeObserver;
 
     /** Whether theme is dark mode. */
     private boolean mIsUsingDarkBackground;
@@ -58,11 +46,7 @@
     private boolean mIsOverviewVisible;
 
     public BottomToolbarThemeColorProvider() {
-        mThemeColorObservers = new ObserverList<ThemeColorObserver>();
-
         final Context context = ContextUtils.getApplicationContext();
-        mDarkModeTint = AppCompatResources.getColorStateList(context, R.color.light_mode_tint);
-        mLightModeTint = AppCompatResources.getColorStateList(context, R.color.dark_mode_tint);
         mLightPrimaryColor = ApiCompatibilityUtils.getColor(
                 context.getResources(), R.color.modern_primary_color);
         mDarkPrimaryColor = ApiCompatibilityUtils.getColor(
@@ -83,16 +67,6 @@
         };
     }
 
-    @Override
-    public void addObserver(ThemeColorObserver observer) {
-        mThemeColorObservers.addObserver(observer);
-    }
-
-    @Override
-    public void removeObserver(ThemeColorObserver observer) {
-        mThemeColorObservers.removeObserver(observer);
-    }
-
     void setIncognitoStateProvider(IncognitoStateProvider provider) {
         mIncognitoStateProvider = provider;
         mIncognitoStateProvider.addIncognitoStateObserverAndTrigger(this);
@@ -117,16 +91,12 @@
                 && (isAccessibilityEnabled || isHorizontalTabSwitcherEnabled
                         || !mIsOverviewVisible);
 
-        if (shouldUseDarkBackground == mIsUsingDarkBackground) return;
-        mIsUsingDarkBackground = shouldUseDarkBackground;
-        final int primaryColor = mIsUsingDarkBackground ? mDarkPrimaryColor : mLightPrimaryColor;
-        final ColorStateList tint = mIsUsingDarkBackground ? mDarkModeTint : mLightModeTint;
-        for (ThemeColorObserver observer : mThemeColorObservers) {
-            observer.onThemeColorChanged(tint, primaryColor);
-        }
+        updatePrimaryColor(shouldUseDarkBackground ? mDarkPrimaryColor : mLightPrimaryColor, false);
     }
 
-    void destroy() {
+    @Override
+    public void destroy() {
+        super.destroy();
         if (mIncognitoStateProvider != null) {
             mIncognitoStateProvider.removeObserver(this);
             mIncognitoStateProvider = null;
@@ -135,6 +105,5 @@
             mOverviewModeBehavior.removeOverviewModeObserver(mOverviewModeObserver);
             mOverviewModeBehavior = null;
         }
-        mThemeColorObservers.clear();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java
index 9088647..264d087 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java
@@ -10,6 +10,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.ActivityTabProvider.HintlessActivityTabObserver;
+import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
@@ -22,7 +23,6 @@
 import org.chromium.chrome.browser.toolbar.MenuButton;
 import org.chromium.chrome.browser.toolbar.TabCountProvider;
 import org.chromium.chrome.browser.toolbar.TabSwitcherButtonCoordinator;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
 import org.chromium.chrome.browser.toolbar.bottom.BrowsingModeBottomToolbarViewBinder.ViewHolder;
 import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.ui.base.WindowAndroid;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarMediator.java
index c531357..19606ef4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarMediator.java
@@ -4,13 +4,14 @@
 
 package org.chromium.chrome.browser.toolbar.bottom;
 
-import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.view.View;
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.ThemeColorProvider.ThemeColorObserver;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager.OverlayPanelManagerObserver;
 import org.chromium.chrome.browser.compositor.layouts.Layout;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
@@ -21,8 +22,6 @@
 import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeHandler;
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager.FullscreenListener;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
 import org.chromium.chrome.browser.widget.FeatureHighlightProvider;
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.feature_engagement.Tracker;
@@ -98,7 +97,7 @@
 
     void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
         mThemeColorProvider = themeColorProvider;
-        mThemeColorProvider.addObserver(this);
+        mThemeColorProvider.addThemeColorObserver(this);
     }
 
     void setResourceManager(ResourceManager resourceManager) {
@@ -175,7 +174,7 @@
             manager.removeSceneChangeObserver(this);
         }
         if (mThemeColorProvider != null) {
-            mThemeColorProvider.removeObserver(this);
+            mThemeColorProvider.removeThemeColorObserver(this);
             mThemeColorProvider = null;
         }
     }
@@ -264,7 +263,7 @@
     }
 
     @Override
-    public void onThemeColorChanged(ColorStateList tintList, int primaryColor) {
+    public void onThemeColorChanged(int primaryColor, boolean shouldAnimate) {
         mModel.set(BrowsingModeBottomToolbarModel.PRIMARY_COLOR, primaryColor);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/CloseAllTabsButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/CloseAllTabsButton.java
index 89ad0ebc4..64e17f2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/CloseAllTabsButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/CloseAllTabsButton.java
@@ -12,19 +12,19 @@
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.ThemeColorProvider.TintObserver;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider.IncognitoStateObserver;
 import org.chromium.chrome.browser.toolbar.TabCountProvider;
 import org.chromium.chrome.browser.toolbar.TabCountProvider.TabCountObserver;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
 import org.chromium.ui.widget.ChromeImageButton;
 
 /**
  * The close all tabs button.
  */
 class CloseAllTabsButton extends ChromeImageButton
-        implements ThemeColorObserver, IncognitoStateObserver, TabCountObserver {
+        implements TintObserver, IncognitoStateObserver, TabCountObserver {
     /** A provider that notifies when the theme color changes.*/
     private ThemeColorProvider mThemeColorProvider;
 
@@ -43,7 +43,7 @@
 
     void destroy() {
         if (mThemeColorProvider != null) {
-            mThemeColorProvider.removeObserver(this);
+            mThemeColorProvider.removeTintObserver(this);
             mThemeColorProvider = null;
         }
         if (mIncognitoStateProvider != null) {
@@ -58,11 +58,11 @@
 
     void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
         mThemeColorProvider = themeColorProvider;
-        mThemeColorProvider.addObserver(this);
+        mThemeColorProvider.addTintObserver(this);
     }
 
     @Override
-    public void onThemeColorChanged(ColorStateList tint, int primaryColor) {
+    public void onTintChanged(ColorStateList tint, boolean useLight) {
         ApiCompatibilityUtils.setImageTintList(this, tint);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/SearchAccelerator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/SearchAccelerator.java
index 7fdcc90..5160a50 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/SearchAccelerator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/SearchAccelerator.java
@@ -13,15 +13,16 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
+import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.ThemeColorProvider.ThemeColorObserver;
+import org.chromium.chrome.browser.ThemeColorProvider.TintObserver;
 import org.chromium.chrome.browser.util.ColorUtils;
 import org.chromium.ui.widget.ChromeImageButton;
 
 /**
  * The search accelerator.
  */
-class SearchAccelerator extends ChromeImageButton implements ThemeColorObserver {
+class SearchAccelerator extends ChromeImageButton implements ThemeColorObserver, TintObserver {
     /** A provider that notifies components when the theme color changes.*/
     private ThemeColorProvider mThemeColorProvider;
 
@@ -43,21 +44,27 @@
 
     void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
         mThemeColorProvider = themeColorProvider;
-        mThemeColorProvider.addObserver(this);
+        mThemeColorProvider.addThemeColorObserver(this);
+        mThemeColorProvider.addTintObserver(this);
     }
 
     void destroy() {
         if (mThemeColorProvider != null) {
-            mThemeColorProvider.removeObserver(this);
+            mThemeColorProvider.removeThemeColorObserver(this);
+            mThemeColorProvider.removeTintObserver(this);
             mThemeColorProvider = null;
         }
     }
 
     @Override
-    public void onThemeColorChanged(ColorStateList tint, int primaryColor) {
-        ApiCompatibilityUtils.setImageTintList(this, tint);
+    public void onThemeColorChanged(int color, boolean shouldAnimate) {
         mBackground.setColorFilter(
-                ColorUtils.getTextBoxColorForToolbarBackground(mResources, false, primaryColor),
+                ColorUtils.getTextBoxColorForToolbarBackground(mResources, false, color),
                 PorterDuff.Mode.SRC_IN);
     }
+
+    @Override
+    public void onTintChanged(ColorStateList tint, boolean useLight) {
+        ApiCompatibilityUtils.setImageTintList(this, tint);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ShareButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ShareButton.java
index 9a590a92..d61f271 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ShareButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ShareButton.java
@@ -11,16 +11,16 @@
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.ActivityTabProvider.ActivityTabTabObserver;
+import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.ThemeColorProvider.TintObserver;
 import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
 import org.chromium.ui.widget.ChromeImageButton;
 
 /**
  * The share button.
  */
-class ShareButton extends ChromeImageButton implements ThemeColorObserver {
+class ShareButton extends ChromeImageButton implements TintObserver {
     /** A provider that notifies components when the theme color changes.*/
     private ThemeColorProvider mThemeColorProvider;
 
@@ -31,9 +31,9 @@
         super(context, attrs);
     }
 
-    void setThemeColorProvider(ThemeColorProvider themeStateProvider) {
-        mThemeColorProvider = themeStateProvider;
-        mThemeColorProvider.addObserver(this);
+    void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
+        mThemeColorProvider = themeColorProvider;
+        mThemeColorProvider.addTintObserver(this);
     }
 
     void setActivityTabProvider(ActivityTabProvider activityTabProvider) {
@@ -54,7 +54,7 @@
 
     void destroy() {
         if (mThemeColorProvider != null) {
-            mThemeColorProvider.removeObserver(this);
+            mThemeColorProvider.removeTintObserver(this);
             mThemeColorProvider = null;
         }
         if (mActivityTabTabObserver != null) {
@@ -71,7 +71,7 @@
     }
 
     @Override
-    public void onThemeColorChanged(ColorStateList tint, int primaryColor) {
+    public void onTintChanged(ColorStateList tint, boolean useLight) {
         ApiCompatibilityUtils.setImageTintList(this, tint);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java
index a1e4ce7..dfb041a1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java
@@ -9,13 +9,13 @@
 import android.view.ViewStub;
 
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
 import org.chromium.chrome.browser.toolbar.MenuButton;
 import org.chromium.chrome.browser.toolbar.TabCountProvider;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
 /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarMediator.java
index 49e9359..aa8769f4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarMediator.java
@@ -4,12 +4,10 @@
 
 package org.chromium.chrome.browser.toolbar.bottom;
 
-import android.content.res.ColorStateList;
-
+import org.chromium.chrome.browser.ThemeColorProvider;
+import org.chromium.chrome.browser.ThemeColorProvider.ThemeColorObserver;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior.OverviewModeObserver;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
-import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
 
 /**
  * This class is responsible for reacting to events from the outside world, interacting with other
@@ -38,7 +36,7 @@
         mModel = model;
 
         mThemeColorProvider = themeColorProvider;
-        mThemeColorProvider.addObserver(this);
+        mThemeColorProvider.addThemeColorObserver(this);
 
         mOverviewModeBehavior = overviewModeBehavior;
         mOverviewModeBehavior.addOverviewModeObserver(this);
@@ -49,7 +47,7 @@
      */
     void destroy() {
         if (mOverviewModeBehavior != null) mOverviewModeBehavior.removeOverviewModeObserver(this);
-        if (mThemeColorProvider != null) mThemeColorProvider.removeObserver(this);
+        if (mThemeColorProvider != null) mThemeColorProvider.removeThemeColorObserver(this);
     }
 
     @Override
@@ -69,7 +67,7 @@
     public void onOverviewModeFinishedHiding() {}
 
     @Override
-    public void onThemeColorChanged(ColorStateList tint, int primaryColor) {
+    public void onThemeColorChanged(int primaryColor, boolean shouldAnimate) {
         mModel.set(TabSwitcherBottomToolbarModel.PRIMARY_COLOR, primaryColor);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index 0af635cd3..b1e70777 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -2186,6 +2186,7 @@
         });
         mBrandColorTransitionAnimation.start();
         mBrandColorTransitionActive = true;
+        mLayoutUpdateHost.requestUpdate();
     }
 
     private void updateNtpAnimationState() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShell.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShell.java
index 4062a55..6b78545 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShell.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShell.java
@@ -6,6 +6,7 @@
 
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
+import android.content.pm.PackageManager;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
@@ -413,13 +414,11 @@
         // relatively low-res Pixel, and higher-res Pixel XL and other devices.
         boolean lowDensity = dm.densityDpi <= DisplayMetrics.DENSITY_XXHIGH;
 
-        boolean hasOrCanRequestAudioPermission =
-                mActivity.getWindowAndroid().hasPermission(android.Manifest.permission.RECORD_AUDIO)
-                || mActivity.getWindowAndroid().canRequestPermission(
-                           android.Manifest.permission.RECORD_AUDIO);
+        boolean hasOrCanRequestRecordAudioPermission =
+                hasRecordAudioPermission() || canRequestRecordAudioPermission();
         boolean supportsRecognition = FeatureUtilities.isRecognitionIntentPresent(mActivity, false);
         mNativeVrShell = nativeInit(mDelegate, forWebVr, !mVrBrowsingEnabled,
-                hasOrCanRequestAudioPermission && supportsRecognition,
+                hasOrCanRequestRecordAudioPermission && supportsRecognition,
                 getGvrApi().getNativeGvrContext(), mReprojectedRendering, displayWidthMeters,
                 displayHeightMeters, dm.widthPixels, dm.heightPixels, pauseContent, lowDensity,
                 isStandaloneVrDevice);
@@ -534,8 +533,14 @@
 
     // Returns true if Chrome has permission to use audio input.
     @CalledByNative
-    public boolean hasAudioPermission() {
-        return mDelegate.hasAudioPermission();
+    public boolean hasRecordAudioPermission() {
+        return mDelegate.hasRecordAudioPermission();
+    }
+
+    // Returns true if Chrome has not been permanently denied audio input permission.
+    @CalledByNative
+    public boolean canRequestRecordAudioPermission() {
+        return mDelegate.canRequestRecordAudioPermission();
     }
 
     // Exits VR, telling the user to remove their headset, and returning to Chromium.
@@ -568,6 +573,8 @@
                             @Override
                             public void run() {
                                 VrShellDelegate.enterVrIfNecessary();
+                                nativeRequestRecordAudioPermissionResult(mNativeVrShell,
+                                        grantResults[0] == PackageManager.PERMISSION_GRANTED);
                             }
                         });
                     }
@@ -1273,7 +1280,7 @@
     }
 
     private native long nativeInit(VrShellDelegate delegate, boolean forWebVR,
-            boolean browsingDisabled, boolean hasOrCanRequestAudioPermission, long gvrApi,
+            boolean browsingDisabled, boolean hasOrCanRequestRecordAudioPermission, long gvrApi,
             boolean reprojectedRendering, float displayWidthMeters, float displayHeightMeters,
             int displayWidthPixels, int displayHeightPixels, boolean pauseContent,
             boolean lowDensity, boolean isStandaloneVrDevice);
@@ -1329,4 +1336,6 @@
             long nativeVrShell, int elementName, int timeoutMs, boolean visibility);
     private native void nativeResumeContentRendering(long nativeVrShell);
     private native void nativeOnOverlayTextureEmptyChanged(long nativeVrShell, boolean empty);
+    private native void nativeRequestRecordAudioPermissionResult(
+            long nativeVrShell, boolean canRecordAudio);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
index 67afd026..0b008695 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
@@ -1189,10 +1189,15 @@
         return false;
     }
 
-    public boolean hasAudioPermission() {
+    public boolean hasRecordAudioPermission() {
         return mActivity.getWindowAndroid().hasPermission(android.Manifest.permission.RECORD_AUDIO);
     }
 
+    public boolean canRequestRecordAudioPermission() {
+        return mActivity.getWindowAndroid().canRequestPermission(
+                android.Manifest.permission.RECORD_AUDIO);
+    }
+
     private boolean isWindowModeCorrectForVr() {
         int flags = mActivity.getWindow().getDecorView().getSystemUiVisibility();
         int orientation = mActivity.getResources().getConfiguration().orientation;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
index b6b153f..d64ffcb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
@@ -771,7 +771,7 @@
             taskDescriptionColor = mBrandColor;
             statusBarColor = ColorUtils.getDarkenedColorForStatusBar(mBrandColor);
             if (getToolbarManager() != null) {
-                getToolbarManager().updatePrimaryColor(mBrandColor, false);
+                getToolbarManager().onThemeColorChanged(mBrandColor, false);
             }
         }
 
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 4ca7824..c83429b 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -70,6 +70,8 @@
   "java/src/org/chromium/chrome/browser/SnackbarActivity.java",
   "java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java",
   "java/src/org/chromium/chrome/browser/SynchronousInitializationActivity.java",
+  "java/src/org/chromium/chrome/browser/TabThemeColorProvider.java",
+  "java/src/org/chromium/chrome/browser/ThemeColorProvider.java",
   "java/src/org/chromium/chrome/browser/UrlConstants.java",
   "java/src/org/chromium/chrome/browser/UsbChooserDialog.java",
   "java/src/org/chromium/chrome/browser/WarmupManager.java",
@@ -742,6 +744,9 @@
   "java/src/org/chromium/chrome/browser/gcore/ConnectedTask.java",
   "java/src/org/chromium/chrome/browser/gcore/GoogleApiClientHelper.java",
   "java/src/org/chromium/chrome/browser/gcore/LifecycleHook.java",
+  "java/src/org/chromium/chrome/browser/gesturenav/ArrowDrawable.java",
+  "java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java",
+  "java/src/org/chromium/chrome/browser/gesturenav/SideSlideLayout.java",
   "java/src/org/chromium/chrome/browser/gsa/ContextReporter.java",
   "java/src/org/chromium/chrome/browser/gsa/GSAAccountChangeListener.java",
   "java/src/org/chromium/chrome/browser/gsa/GSAContextDisplaySelection.java",
@@ -1168,8 +1173,10 @@
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListProperties.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/SuggestionListViewBinder.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/VoiceSuggestionProvider.java",
+  "java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/AnswerSuggestionProcessor.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/AnswerTextBuilder.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/BasicSuggestionProcessor.java",
+  "java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionHost.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionView.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionViewProperties.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionViewViewBinder.java",
@@ -1538,6 +1545,7 @@
   "java/src/org/chromium/chrome/browser/tab/TabFullscreenHandler.java",
   "java/src/org/chromium/chrome/browser/tab/TabGestureStateListener.java",
   "java/src/org/chromium/chrome/browser/tab/TabIdManager.java",
+  "java/src/org/chromium/chrome/browser/tab/TabImportanceManager.java",
   "java/src/org/chromium/chrome/browser/tab/TabObserver.java",
   "java/src/org/chromium/chrome/browser/tab/TabRedirectHandler.java",
   "java/src/org/chromium/chrome/browser/tab/TabState.java",
@@ -1605,7 +1613,6 @@
   "java/src/org/chromium/chrome/browser/toolbar/TabSwitcherButtonView.java",
   "java/src/org/chromium/chrome/browser/toolbar/TabSwitcherButtonViewBinder.java",
   "java/src/org/chromium/chrome/browser/toolbar/TabSwitcherDrawable.java",
-  "java/src/org/chromium/chrome/browser/toolbar/ThemeColorProvider.java",
   "java/src/org/chromium/chrome/browser/toolbar/ToolbarButtonInProductHelpController.java",
   "java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java",
   "java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageAutoFetchTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageAutoFetchTest.java
index 3e0dbf0..d94ce4b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageAutoFetchTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageAutoFetchTest.java
@@ -152,6 +152,7 @@
     }
 
     @Test
+    @DisabledTest(message = "crbug.com/923212")
     @MediumTest
     @Feature({"OfflineAutoFetch"})
     public void testAutoFetchRequestRetainedOnOtherTabClosed() throws Exception {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java
index 9d893afb..1b67a0b7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java
@@ -152,7 +152,7 @@
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                mActivityTestRule.getActivity().getToolbarManager().updatePrimaryColor(
+                mActivityTestRule.getActivity().getToolbarManager().onThemeColorChanged(
                         mDefaultColor, false);
                 // Since the color should change instantly, there is no need to use the criteria
                 // helper.
diff --git a/chrome/android/modules/vr/vr_module_tmpl.gni b/chrome/android/modules/vr/vr_module_tmpl.gni
index 9ff046a..7e0028e 100644
--- a/chrome/android/modules/vr/vr_module_tmpl.gni
+++ b/chrome/android/modules/vr/vr_module_tmpl.gni
@@ -30,6 +30,7 @@
                            [
                              "base_module_target",
                              "module_name",
+                             "uncompress_shared_libraries",
                              "version_code",
                              "version_name",
                            ])
@@ -39,6 +40,8 @@
       "//chrome/browser/android/vr:java",
     ]
     proguard_enabled = !is_java_debug
-    uncompress_shared_libraries = chromium_linker_supported
+    if (!defined(invoker.uncompress_shared_libraries)) {
+      uncompress_shared_libraries = chromium_linker_supported
+    }
   }
 }
diff --git a/chrome/app/bookmarks_strings.grdp b/chrome/app/bookmarks_strings.grdp
index 91e4d951..2d46be6 100644
--- a/chrome/app/bookmarks_strings.grdp
+++ b/chrome/app/bookmarks_strings.grdp
@@ -305,7 +305,7 @@
 
   <!-- Begin of Bookmarks Manager strings. -->
   <message name="IDS_BOOKMARK_MANAGER_TITLE" desc="Title of the bookmark manager window.">
-    Bookmark Manager
+    Bookmarks
   </message>
   <message name="IDS_BOOKMARK_MANAGER_SEARCH_BUTTON" desc="Title of the button in the bookmark manager that triggers a search">
     Search bookmarks
@@ -377,133 +377,127 @@
   <message name="IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME" desc="Filename that pre-populates the filename field when user attempts to export their bookmarks from Bookmark Manager.">
     bookmarks_<ph name="DATESTAMP">$1<ex>02_11_11</ex></ph>.html
   </message>
-  <!-- End of Bookmarks Manager strings. -->
-
-  <!-- Begin of material design Bookmarks Manager strings. -->
-  <message name="IDS_MD_BOOKMARK_MANAGER_ADD_BOOKMARK_TITLE" desc="Title of the dialog in the bookmark manager that creates a new bookmark.">
+  <message name="IDS_BOOKMARK_MANAGER_ADD_BOOKMARK_TITLE" desc="Title of the dialog in the bookmark manager that creates a new bookmark.">
     Add bookmark
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_ADD_FOLDER_TITLE" desc="Title of the dialog in the bookmark manager that creates a new bookmark folder.">
+  <message name="IDS_BOOKMARK_MANAGER_ADD_FOLDER_TITLE" desc="Title of the dialog in the bookmark manager that creates a new bookmark folder.">
     Add folder
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_CLEAR_SEARCH" desc="Title of the button in the bookmark manager that stops a search.">
+  <message name="IDS_BOOKMARK_MANAGER_CLEAR_SEARCH" desc="Title of the button in the bookmark manager that stops a search.">
     Clear search
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_EMPTY_LIST" desc="The message shown when the user has no bookmarks added. 'Star' refers to the icon in the omnibox for adding to bookmarks.">
+  <message name="IDS_BOOKMARK_MANAGER_EMPTY_LIST" desc="The message shown when the user has no bookmarks added. 'Star' refers to the icon in the omnibox for adding to bookmarks.">
     To bookmark pages, click the star in the address bar
   </message>
-   <message name="IDS_MD_BOOKMARK_MANAGER_EMPTY_UNMODIFIABLE_LIST" desc="The message shown when an unmodifiable bookmark folder is empty.">
+   <message name="IDS_BOOKMARK_MANAGER_EMPTY_UNMODIFIABLE_LIST" desc="The message shown when an unmodifiable bookmark folder is empty.">
     This folder is empty
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_FOLDER_LABEL" desc="Label for a folder of bookmarks which is used to label folders for screen reader users.">
+  <message name="IDS_BOOKMARK_MANAGER_FOLDER_LABEL" desc="Label for a folder of bookmarks which is used to label folders for screen reader users.">
     Folder
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_FOLDER_RENAME_TITLE" desc="Title of the bookmark editor window when editing folders.">
+  <message name="IDS_BOOKMARK_MANAGER_FOLDER_RENAME_TITLE" desc="Title of the bookmark editor window when editing folders.">
     Rename folder
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_FOLDER_LIST_CHANGED" desc="Message announced by screenreaders when the bookmark list changes.">
+  <message name="IDS_BOOKMARK_MANAGER_FOLDER_LIST_CHANGED" desc="Message announced by screenreaders when the bookmark list changes.">
     {COUNT, plural,
       =1 {1 item in bookmark list}
       other {# items in bookmark list}}
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_LIST_AX_LABEL" desc="Spoken feedback for focusing the bookmark list.">
+  <message name="IDS_BOOKMARK_MANAGER_LIST_AX_LABEL" desc="Spoken feedback for focusing the bookmark list.">
     Bookmark list
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_ADD_BOOKMARK" desc="Title of the bookmark toolbar dropdown menu item that adds a new bookmark.">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_ADD_BOOKMARK" desc="Title of the bookmark toolbar dropdown menu item that adds a new bookmark.">
     Add new bookmark
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_ADD_FOLDER" desc="Title of the bookmark toolbar dropdown menu item that adds a new folder.">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_ADD_FOLDER" desc="Title of the bookmark toolbar dropdown menu item that adds a new folder.">
     Add new folder
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_COPY_URL" desc="Title of the bookmark dropdown menu item that copies the url of the bookmark.">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_COPY_URL" desc="Title of the bookmark dropdown menu item that copies the url of the bookmark.">
     Copy URL
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_EXPORT" desc="Title of the bookmark toolbar dropdown menu item that exports bookmarks.">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_EXPORT" desc="Title of the bookmark toolbar dropdown menu item that exports bookmarks.">
     Export bookmarks
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_HELP_CENTER" desc="Title of the bookmark toolbar dropdown menu item that opens the help center.">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_HELP_CENTER" desc="Title of the bookmark toolbar dropdown menu item that opens the help center.">
     Help center
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_IMPORT" desc="Title of the bookmark toolbar dropdown menu item that imports bookmarks.">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_IMPORT" desc="Title of the bookmark toolbar dropdown menu item that imports bookmarks.">
     Import bookmarks
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_OPEN_ALL" desc="Menu title for opening all urls in a bookmark folder">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_OPEN_ALL" desc="Menu title for opening all urls in a bookmark folder">
     Open all bookmarks
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_OPEN_ALL_NEW_WINDOW" desc="Menu title for opening all urls in a bookmark folder in a new window">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_OPEN_ALL_NEW_WINDOW" desc="Menu title for opening all urls in a bookmark folder in a new window">
     Open all in new window
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_OPEN_ALL_INCOGNITO" desc="Menu description for opening all urls in a bookmark folder in an incognito window">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_OPEN_ALL_INCOGNITO" desc="Menu description for opening all urls in a bookmark folder in an incognito window">
     Open all in incognito window
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_TAB" desc="Menu description for opening a bookmark in a new tab">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_TAB" desc="Menu description for opening a bookmark in a new tab">
     Open in new tab
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_WINDOW" desc="Menu description for opening a bookmark in a new window">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_WINDOW" desc="Menu description for opening a bookmark in a new window">
     Open in new window
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_OPEN_INCOGNITO" desc="Menu description for opening a bookmark in incognito window">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_OPEN_INCOGNITO" desc="Menu description for opening a bookmark in incognito window">
     Open in incognito window
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_RENAME" desc="Title of the bookmark list dropdown menu item that renames folders.">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_RENAME" desc="Title of the bookmark list dropdown menu item that renames folders.">
     Rename
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_SHOW_IN_FOLDER" desc="The label of the menu item in the bookmark manager context menu that changes the selection in the tree to match the folder of the selected item in the list.">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_SHOW_IN_FOLDER" desc="The label of the menu item in the bookmark manager context menu that changes the selection in the tree to match the folder of the selected item in the list.">
     Show in folder
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MENU_SORT" desc="Title of the bookmark toolbar dropdown menu item that sorts bookmarks by title.">
+  <message name="IDS_BOOKMARK_MANAGER_MENU_SORT" desc="Title of the bookmark toolbar dropdown menu item that sorts bookmarks by title.">
     Sort by name
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MORE_ACTIONS" desc="Tooltip of the button in the bookmark manager which shows an action menu for a bookmark, with actions like 'Edit' or 'Delete'">
+  <message name="IDS_BOOKMARK_MANAGER_MORE_ACTIONS" desc="Tooltip of the button in the bookmark manager which shows an action menu for a bookmark, with actions like 'Edit' or 'Delete'">
     More actions
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_MORE_ACTIONS_AX_LABEL" desc="Spoken text for the button in the bookmark manager which shows an action menu for a bookmark, with actions like 'Edit' or 'Delete'">
+  <message name="IDS_BOOKMARK_MANAGER_MORE_ACTIONS_AX_LABEL" desc="Spoken text for the button in the bookmark manager which shows an action menu for a bookmark, with actions like 'Edit' or 'Delete'">
     More actions for <ph name="BOOKMARK_NAME">$1<ex>Bookmark X</ex></ph>
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_OPEN_DIALOG_TITLE" desc="Title of the dialog confirming whether a large number of bookmarks should be opened.">
+  <message name="IDS_BOOKMARK_MANAGER_OPEN_DIALOG_TITLE" desc="Title of the dialog confirming whether a large number of bookmarks should be opened.">
     Open selected items
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_OPEN_DIALOG_CONFIRM" desc="Label for the button to confirm opening a large number of bookmarks.">
+  <message name="IDS_BOOKMARK_MANAGER_OPEN_DIALOG_CONFIRM" desc="Label for the button to confirm opening a large number of bookmarks.">
     Open
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_ITEMS_SELECTED" desc="Label displayed in bookmark manager toolbar telling the user how many items they have selected.">
+  <message name="IDS_BOOKMARK_MANAGER_ITEMS_SELECTED" desc="Label displayed in bookmark manager toolbar telling the user how many items they have selected.">
     <ph name="NUMBER_OF_ITEMS_SELECTED">$1</ph> selected
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_SIDEBAR_AX_LABEL" desc="Message announced by screenreaders for the bookmark manager sidebar.">
+  <message name="IDS_BOOKMARK_MANAGER_SIDEBAR_AX_LABEL" desc="Message announced by screenreaders for the bookmark manager sidebar.">
     Bookmark folder tree
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_SIDEBAR_NODE_COLLAPSE_AX_LABEL" desc="Message announced by screenreaders for button to collapse a folder in the bookmark manager sidebar.">
+  <message name="IDS_BOOKMARK_MANAGER_SIDEBAR_NODE_COLLAPSE_AX_LABEL" desc="Message announced by screenreaders for button to collapse a folder in the bookmark manager sidebar.">
     Collapse <ph name="FOLDER_NAME">$1<ex>Recipes</ex></ph>
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_SIDEBAR_NODE_EXPAND_AX_LABEL" desc="Message announced by screenreaders for button to expand a folder in the bookmark manager sidebar.">
+  <message name="IDS_BOOKMARK_MANAGER_SIDEBAR_NODE_EXPAND_AX_LABEL" desc="Message announced by screenreaders for button to expand a folder in the bookmark manager sidebar.">
     Expand <ph name="FOLDER_NAME">$1<ex>Recipes</ex></ph>
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_TITLE" desc="Title of the bookmark manager window.">
-    Bookmarks
-  </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_TOAST_FOLDER_SORTED" desc="Label displayed in toast popup message when a folder's children are sorted.">
+  <message name="IDS_BOOKMARK_MANAGER_TOAST_FOLDER_SORTED" desc="Label displayed in toast popup message when a folder's children are sorted.">
     Folder sorted
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_TOAST_ITEM_DELETED" desc="Label displayed in toast popup message when a single item is deleted.">
+  <message name="IDS_BOOKMARK_MANAGER_TOAST_ITEM_DELETED" desc="Label displayed in toast popup message when a single item is deleted.">
     '<ph name="DELETED_ITEM_NAME">$1<ex>Bookmark X</ex></ph>' deleted
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_TOAST_ITEMS_DELETED" desc="Label displayed in toast popup message when two or more items are deleted.">
+  <message name="IDS_BOOKMARK_MANAGER_TOAST_ITEMS_DELETED" desc="Label displayed in toast popup message when two or more items are deleted.">
     {COUNT, plural,
       =1 {1 bookmark deleted}
       other {# bookmarks deleted}}
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_TOAST_URL_COPIED" desc="Label displayed in toast popup message when a URL is copied.">
+  <message name="IDS_BOOKMARK_MANAGER_TOAST_URL_COPIED" desc="Label displayed in toast popup message when a URL is copied.">
     URL copied
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_TOAST_ITEM_COPIED" desc="Label displayed in toast popup message when a single item is copied.">
+  <message name="IDS_BOOKMARK_MANAGER_TOAST_ITEM_COPIED" desc="Label displayed in toast popup message when a single item is copied.">
     '<ph name="COPIED_ITEM_NAME">$1<ex>Bookmark X</ex></ph>' copied
   </message>
-  <message name="IDS_MD_BOOKMARK_MANAGER_TOAST_ITEMS_COPIED" desc="Label displayed in a toast popup message when two or more bookmark items are copied">
+  <message name="IDS_BOOKMARK_MANAGER_TOAST_ITEMS_COPIED" desc="Label displayed in a toast popup message when two or more bookmark items are copied">
     {COUNT, plural,
       =1 {1 item copied}
       other {# items copied}}
   </message>
-  <!-- End of material design Bookmarks Manager strings. -->
+  <!-- End of Bookmarks Manager strings. -->
 
   <!-- Begin of Bookmarks Menu (in the Main Menu) strings. -->
   <message name="IDS_BOOKMARKS_MENU" desc="The title of the Bookmarks menu in the Main menu and in the Mac menu bar.">
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 81abca3..741b00e 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -3762,4 +3762,16 @@
   <message name="IDS_PLUGIN_VM_LAUNCHER_RETRY_BUTTON" desc="Label for the button in the PluginVm launcher dialog to retry PluginVm environment setting." translateable="false">
     Retry
   </message>
+
+  <!-- Strings for Account Manager welcome screen -->
+  <message name="IDS_ACCOUNT_MANAGER_WELCOME_TITLE" desc="Title for the Chrome OS Account Manager Welcome screen.">
+    Manage your Google Accounts in one place
+  </message>
+  <message name="IDS_ACCOUNT_MANAGER_WELCOME_TEXT" desc="Text body for the Chrome OS Account Manager Welcome screen.">
+    Apps and websites that have your permission can access the account information they need to work properly.
+
+    If you don't want to add an account, sign in as a guest or open an incognito window for web browsing.
+
+    You can go to Settings -> Google Accounts to view and manage all accounts.
+  </message>
 </grit-part>
diff --git a/chrome/app/chromeos_strings_grdp/IDS_ACCOUNT_MANAGER_WELCOME_TEXT.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_ACCOUNT_MANAGER_WELCOME_TEXT.png.sha1
new file mode 100644
index 0000000..226cbec
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_ACCOUNT_MANAGER_WELCOME_TEXT.png.sha1
@@ -0,0 +1 @@
+43b13f274f906c3b0e2aca792cc286b883b73c05
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_ACCOUNT_MANAGER_WELCOME_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_ACCOUNT_MANAGER_WELCOME_TITLE.png.sha1
new file mode 100644
index 0000000..226cbec
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_ACCOUNT_MANAGER_WELCOME_TITLE.png.sha1
@@ -0,0 +1 @@
+43b13f274f906c3b0e2aca792cc286b883b73c05
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index b3b6844..d0b1931 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5761,6 +5761,9 @@
         <message name="IDS_TAB_CXMENU_ADD_TAB_TO_EXISTING_GROUP" desc="The label of the tab context menu submenu for adding one or more tabs to an existing tab group.">
           Add to existing group
         </message>
+        <message name="IDS_TAB_CXMENU_REMOVE_TAB_FROM_GROUP" desc="The label of the tab context menu item for removing one or more tabs from the groups that contain them.">
+          Remove from group
+        </message>
       </if>
       <if expr="use_titlecase">
         <message name="IDS_TAB_CXMENU_NEWTAB" desc="In Title Case: The label of the 'New Tab' Tab context menu item.">
@@ -5808,6 +5811,9 @@
         <message name="IDS_TAB_CXMENU_ADD_TAB_TO_EXISTING_GROUP" desc="In Title Case: The label of the tab context menu submenu for adding one or more tabs to an existing tab group.">
           Add to Existing Group
         </message>
+        <message name="IDS_TAB_CXMENU_REMOVE_TAB_FROM_GROUP" desc="In Title Case: The label of the tab context menu item for removing one or more tabs from the groups that contain them.">
+          Remove From Group
+        </message>
         <message name="IDS_TAB_CXMENU_SEND_TO_MY_DEVICES" desc="In Title Case: The label of the tab context menu item for share this tab to other devices.">
           Send To My Devices
         </message>
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_CXMENU_REMOVE_TAB_FROM_GROUP.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_CXMENU_REMOVE_TAB_FROM_GROUP.png.sha1
new file mode 100644
index 0000000..4f6f73d0
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TAB_CXMENU_REMOVE_TAB_FROM_GROUP.png.sha1
@@ -0,0 +1 @@
+a728e9c9bfcfdb90c4a75006ecde8f77c2b53c06
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 8762d326..465284d1 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2598,6 +2598,12 @@
      flag_descriptions::kExperimentalAccessibilityLabelsName,
      flag_descriptions::kExperimentalAccessibilityLabelsDescription, kOsAll,
      SINGLE_VALUE_TYPE(::switches::kEnableExperimentalAccessibilityLabels)},
+    {"enable-experimental-accessibility-language-detection",
+     flag_descriptions::kExperimentalAccessibilityLanguageDetectionName,
+     flag_descriptions::kExperimentalAccessibilityLanguageDetectionDescription,
+     kOsCrOS,
+     SINGLE_VALUE_TYPE(
+         ::switches::kEnableExperimentalAccessibilityLanguageDetection)},
 #if defined(OS_CHROMEOS)
     {"opt-in-ime-menu", flag_descriptions::kEnableImeMenuName,
      flag_descriptions::kEnableImeMenuDescription, kOsCrOS,
diff --git a/chrome/browser/android/vr/vr_gl_thread.cc b/chrome/browser/android/vr/vr_gl_thread.cc
index 90351cfc7..6342cc9e 100644
--- a/chrome/browser/android/vr/vr_gl_thread.cc
+++ b/chrome/browser/android/vr/vr_gl_thread.cc
@@ -392,6 +392,16 @@
                                          weak_browser_ui_, reason));
 }
 
+void VrGLThread::SetHasOrCanRequestRecordAudioPermission(
+    bool const has_or_can_request_record_audio) {
+  DCHECK(OnMainThread());
+  task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &BrowserUiInterface::SetHasOrCanRequestRecordAudioPermission,
+          weak_browser_ui_, has_or_can_request_record_audio));
+}
+
 void VrGLThread::SetSpeechRecognitionEnabled(bool enabled) {
   DCHECK(OnMainThread());
   task_runner()->PostTask(
diff --git a/chrome/browser/android/vr/vr_gl_thread.h b/chrome/browser/android/vr/vr_gl_thread.h
index 60098650..0ffa15a 100644
--- a/chrome/browser/android/vr/vr_gl_thread.h
+++ b/chrome/browser/android/vr/vr_gl_thread.h
@@ -130,6 +130,8 @@
       const CapturingStateModel& potential_capturing) override;
   void ShowExitVrPrompt(UiUnsupportedMode reason) override;
   void SetSpeechRecognitionEnabled(bool enabled) override;
+  void SetHasOrCanRequestRecordAudioPermission(
+      bool has_or_can_request_record_audio) override;
   void SetRecognitionResult(const base::string16& result) override;
   void OnSpeechRecognitionStateChanged(int new_state) override;
   void SetOmniboxSuggestions(
diff --git a/chrome/browser/android/vr/vr_shell.cc b/chrome/browser/android/vr/vr_shell.cc
index 8dc3df1..e31b54df 100644
--- a/chrome/browser/android/vr/vr_shell.cc
+++ b/chrome/browser/android/vr/vr_shell.cc
@@ -947,7 +947,7 @@
   if (!active && !speech_recognizer_)
     return;
 
-  if (!HasAudioPermission()) {
+  if (!HasRecordAudioPermission()) {
     OnUnsupportedMode(
         UiUnsupportedMode::kVoiceSearchNeedsRecordAudioOsPermission);
     return;
@@ -986,9 +986,24 @@
   Java_VrShell_showPageInfo(base::android::AttachCurrentThread(), j_vr_shell_);
 }
 
-bool VrShell::HasAudioPermission() {
+bool VrShell::HasRecordAudioPermission() const {
   JNIEnv* env = base::android::AttachCurrentThread();
-  return Java_VrShell_hasAudioPermission(env, j_vr_shell_);
+  return Java_VrShell_hasRecordAudioPermission(env, j_vr_shell_);
+}
+
+bool VrShell::CanRequestRecordAudioPermission() const {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  return Java_VrShell_canRequestRecordAudioPermission(env, j_vr_shell_);
+}
+
+void VrShell::RequestRecordAudioPermissionResult(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& object,
+    jboolean can_record_audio) {
+  // If permission was denied, we need to check if it was *permanently* denied.
+  if (!can_record_audio && !CanRequestRecordAudioPermission()) {
+    ui_->SetHasOrCanRequestRecordAudioPermission(false);
+  }
 }
 
 void VrShell::PollCapturingState() {
@@ -1352,7 +1367,7 @@
                        const JavaParamRef<jobject>& delegate,
                        jboolean for_web_vr,
                        jboolean browsing_disabled,
-                       jboolean has_or_can_request_audio_permission,
+                       jboolean has_or_can_request_record_audio_permission,
                        jlong gvr_api,
                        jboolean reprojected_rendering,
                        jfloat display_width_meters,
@@ -1365,8 +1380,8 @@
   UiInitialState ui_initial_state;
   ui_initial_state.browsing_disabled = browsing_disabled;
   ui_initial_state.in_web_vr = for_web_vr;
-  ui_initial_state.has_or_can_request_audio_permission =
-      has_or_can_request_audio_permission;
+  ui_initial_state.has_or_can_request_record_audio_permission =
+      has_or_can_request_record_audio_permission;
   ui_initial_state.assets_supported = AssetsLoader::AssetsSupported();
   ui_initial_state.is_standalone_vr_device = is_standalone_vr_device;
   ui_initial_state.use_new_incognito_strings =
diff --git a/chrome/browser/android/vr/vr_shell.h b/chrome/browser/android/vr/vr_shell.h
index 25aa514..2a404d96 100644
--- a/chrome/browser/android/vr/vr_shell.h
+++ b/chrome/browser/android/vr/vr_shell.h
@@ -205,7 +205,12 @@
   void StartAutocomplete(const AutocompleteRequest& request);
   void StopAutocomplete();
   void ShowPageInfo();
-  bool HasAudioPermission();
+  bool HasRecordAudioPermission() const;
+  bool CanRequestRecordAudioPermission() const;
+  void RequestRecordAudioPermissionResult(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& object,
+      jboolean can_record_audio);
 
   void ClearFocusedElement();
   void ProcessContentGesture(std::unique_ptr<InputEvent> event, int content_id);
diff --git a/chrome/browser/apps/app_service/arc_apps.cc b/chrome/browser/apps/app_service/arc_apps.cc
index 095967e..832ea6b 100644
--- a/chrome/browser/apps/app_service/arc_apps.cc
+++ b/chrome/browser/apps/app_service/arc_apps.cc
@@ -237,6 +237,50 @@
   }
 }
 
+void ArcApps::OnAppRegistered(const std::string& app_id,
+                              const ArcAppListPrefs::AppInfo& app_info) {
+  OnAppStatesChanged(app_id, app_info);
+}
+
+void ArcApps::OnAppStatesChanged(const std::string& app_id,
+                                 const ArcAppListPrefs::AppInfo& app_info) {
+  apps::mojom::AppPtr app = apps::mojom::App::New();
+  app->app_type = apps::mojom::AppType::kArc;
+  app->app_id = app_id;
+  app->readiness = NewReadiness(app_info.ready);
+  Publish(std::move(app));
+}
+
+void ArcApps::OnAppRemoved(const std::string& app_id) {
+  apps::mojom::AppPtr app = apps::mojom::App::New();
+  app->app_type = apps::mojom::AppType::kArc;
+  app->app_id = app_id;
+  app->readiness = NewReadiness(false);
+  Publish(std::move(app));
+}
+
+void ArcApps::OnAppIconUpdated(const std::string& app_id,
+                               const ArcAppIconDescriptor& descriptor) {
+  apps::mojom::AppPtr app = apps::mojom::App::New();
+  app->app_type = apps::mojom::AppType::kArc;
+  app->app_id = app_id;
+  app->icon_key = NewIconKey(app_id);
+  Publish(std::move(app));
+}
+
+void ArcApps::OnAppNameUpdated(const std::string& app_id,
+                               const std::string& name) {
+  apps::mojom::AppPtr app = apps::mojom::App::New();
+  app->app_type = apps::mojom::AppType::kArc;
+  app->app_id = app_id;
+  app->name = name;
+  Publish(std::move(app));
+}
+
+void ArcApps::OnAppLastLaunchTimeUpdated(const std::string& app_id) {
+  // TODO(crbug.com/826982): implement.
+}
+
 void ArcApps::ObservePrefs() {
   prefs_->AddObserver(this);
   prefs_->app_connection_holder()->AddObserver(this);
@@ -248,13 +292,10 @@
 
   app->app_type = apps::mojom::AppType::kArc;
   app->app_id = app_id;
-  app->readiness = apps::mojom::Readiness::kReady;
+  app->readiness = NewReadiness(app_info.ready);
   app->name = app_info.name;
 
-  app->icon_key = apps::mojom::IconKey::New();
-  app->icon_key->icon_type = apps::mojom::IconType::kArc;
-  app->icon_key->s_key = app_id;
-  app->icon_key->u_key = next_u_key_++;
+  app->icon_key = NewIconKey(app_id);
 
   bool installed_internally =
       prefs_->IsDefault(app_id) ||
@@ -271,4 +312,29 @@
   return app;
 }
 
+apps::mojom::IconKeyPtr ArcApps::NewIconKey(const std::string& app_id) {
+  auto icon_key = apps::mojom::IconKey::New();
+  icon_key->icon_type = apps::mojom::IconType::kArc;
+  icon_key->s_key = app_id;
+  icon_key->u_key = next_u_key_++;
+  return icon_key;
+}
+
+// static
+apps::mojom::Readiness ArcApps::NewReadiness(bool ready) {
+  // TODO(crbug.com/826982): examine ArcAppListPrefs::AppInfo::suspended, and
+  // possibly have a corresponding 'suspended' apps::mojom::Readiness enum
+  // value.
+  return ready ? apps::mojom::Readiness::kReady
+               : apps::mojom::Readiness::kUninstalledByUser;
+}
+
+void ArcApps::Publish(apps::mojom::AppPtr app) {
+  subscribers_.ForAllPtrs([&app](apps::mojom::Subscriber* subscriber) {
+    std::vector<apps::mojom::AppPtr> apps;
+    apps.push_back(app.Clone());
+    subscriber->OnApps(std::move(apps));
+  });
+}
+
 }  // namespace apps
diff --git a/chrome/browser/apps/app_service/arc_apps.h b/chrome/browser/apps/app_service/arc_apps.h
index a83c46859..14d4dc05 100644
--- a/chrome/browser/apps/app_service/arc_apps.h
+++ b/chrome/browser/apps/app_service/arc_apps.h
@@ -60,12 +60,24 @@
   void OnConnectionReady() override;
 
   // ArcAppListPrefs::Observer overrides.
-  // TODO(crbug.com/826982): implement.
+  void OnAppRegistered(const std::string& app_id,
+                       const ArcAppListPrefs::AppInfo& app_info) override;
+  void OnAppStatesChanged(const std::string& app_id,
+                          const ArcAppListPrefs::AppInfo& app_info) override;
+  void OnAppRemoved(const std::string& app_id) override;
+  void OnAppIconUpdated(const std::string& app_id,
+                        const ArcAppIconDescriptor& descriptor) override;
+  void OnAppNameUpdated(const std::string& app_id,
+                        const std::string& name) override;
+  void OnAppLastLaunchTimeUpdated(const std::string& app_id) override;
 
   void ObservePrefs();
 
   apps::mojom::AppPtr Convert(const std::string& app_id,
                               const ArcAppListPrefs::AppInfo& app_info);
+  apps::mojom::IconKeyPtr NewIconKey(const std::string& app_id);
+  static apps::mojom::Readiness NewReadiness(bool ready);
+  void Publish(apps::mojom::AppPtr app);
 
   mojo::Binding<apps::mojom::Publisher> binding_;
   mojo::InterfacePtrSet<apps::mojom::Subscriber> subscribers_;
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 7b7ff54..b08c482 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -269,61 +269,61 @@
       </if>
 
       <if expr="not is_android">
-        <!-- MD Bookmarks. -->
-        <include name="IDR_MD_BOOKMARKS_IMAGES_FOLDER_OPEN_SVG" file="resources\bookmarks\images\folder_open.svg" type="BINDATA" />
-        <include name="IDR_MD_BOOKMARKS_IMAGES_FOLDER_SVG" file="resources\bookmarks\images\folder.svg" type="BINDATA" />
+        <!-- Bookmarks WebUI. -->
+        <include name="IDR_BOOKMARKS_IMAGES_FOLDER_OPEN_SVG" file="resources\bookmarks\images\folder_open.svg" type="BINDATA" />
+        <include name="IDR_BOOKMARKS_IMAGES_FOLDER_SVG" file="resources\bookmarks\images\folder.svg" type="BINDATA" />
         <if expr="optimize_webui">
           <then>
-            <include name="IDR_MD_BOOKMARKS_VULCANIZED_HTML" file="${root_gen_dir}\chrome\browser\resources\bookmarks\vulcanized.html" use_base_dir="false" preprocess="true" type="BINDATA" compress="gzip" />
-            <include name="IDR_MD_BOOKMARKS_VULCANIZED_P2_HTML" file="${root_gen_dir}\chrome\browser\resources\bookmarks\vulcanized.p2.html" use_base_dir="false" preprocess="true" type="BINDATA" compress="gzip" />
-            <include name="IDR_MD_BOOKMARKS_CRISPER_JS" file="${root_gen_dir}\chrome\browser\resources\bookmarks\crisper.js" use_base_dir="false" preprocess="true" type="BINDATA" compress="gzip" />
+            <include name="IDR_BOOKMARKS_VULCANIZED_HTML" file="${root_gen_dir}\chrome\browser\resources\bookmarks\vulcanized.html" use_base_dir="false" preprocess="true" type="BINDATA" compress="gzip" />
+            <include name="IDR_BOOKMARKS_VULCANIZED_P2_HTML" file="${root_gen_dir}\chrome\browser\resources\bookmarks\vulcanized.p2.html" use_base_dir="false" preprocess="true" type="BINDATA" compress="gzip" />
+            <include name="IDR_BOOKMARKS_CRISPER_JS" file="${root_gen_dir}\chrome\browser\resources\bookmarks\crisper.js" use_base_dir="false" preprocess="true" type="BINDATA" compress="gzip" />
           </then>
           <else>
-            <include name="IDR_MD_BOOKMARKS_ACTIONS_HTML" file="resources\bookmarks\actions.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_ACTIONS_JS" file="resources\bookmarks\actions.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_API_LISTENER_HTML" file="resources\bookmarks\api_listener.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_API_LISTENER_JS" file="resources\bookmarks\api_listener.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_APP_HTML" file="resources\bookmarks\app.html" type="BINDATA" preprocess="true" />
-            <include name="IDR_MD_BOOKMARKS_APP_JS" file="resources\bookmarks\app.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_BOOKMARKS_HTML" file="resources\bookmarks\bookmarks.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_COMMAND_MANAGER_HTML" file="resources\bookmarks\command_manager.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_COMMAND_MANAGER_JS" file="resources\bookmarks\command_manager.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_CONSTANTS_HTML" file="resources\bookmarks\constants.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_CONSTANTS_JS" file="resources\bookmarks\constants.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_DEBOUNCER_HTML" file="resources\bookmarks\debouncer.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_DEBOUNCER_JS" file="resources\bookmarks\debouncer.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_DIALOG_FOCUS_MANAGER_HTML" file="resources\bookmarks\dialog_focus_manager.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_DIALOG_FOCUS_MANAGER_JS" file="resources\bookmarks\dialog_focus_manager.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_DND_MANAGER_HTML" file="resources\bookmarks\dnd_manager.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_DND_MANAGER_JS" file="resources\bookmarks\dnd_manager.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_EDIT_DIALOG_HTML" file="resources\bookmarks\edit_dialog.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_EDIT_DIALOG_JS" file="resources\bookmarks\edit_dialog.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_FOLDER_NODE_HTML" file="resources\bookmarks\folder_node.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_FOLDER_NODE_JS" file="resources\bookmarks\folder_node.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_ITEM_HTML" file="resources\bookmarks\item.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_ITEM_JS" file="resources\bookmarks\item.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_LIST_HTML" file="resources\bookmarks\list.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_LIST_JS" file="resources\bookmarks\list.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_MOUSE_FOCUS_BEHAVIOR_HTML" file="resources\bookmarks\mouse_focus_behavior.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_MOUSE_FOCUS_BEHAVIOR_JS" file="resources\bookmarks\mouse_focus_behavior.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_REDUCERS_HTML" file="resources\bookmarks\reducers.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_REDUCERS_JS" file="resources\bookmarks\reducers.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_ROUTER_HTML" file="resources\bookmarks\router.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_ROUTER_JS" file="resources\bookmarks\router.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_SHARED_STYLE_HTML" file="resources\bookmarks\shared_style.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_SHARED_VARS_HTML" file="resources\bookmarks\shared_vars.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_STORE_CLIENT_HTML" file="resources\bookmarks\store_client.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_STORE_CLIENT_JS" file="resources\bookmarks\store_client.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_STORE_HTML" file="resources\bookmarks\store.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_STORE_JS" file="resources\bookmarks\store.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_STRINGS_HTML" file="resources\bookmarks\strings.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_ACTIONS_HTML" file="resources\bookmarks\actions.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_ACTIONS_JS" file="resources\bookmarks\actions.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_API_LISTENER_HTML" file="resources\bookmarks\api_listener.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_API_LISTENER_JS" file="resources\bookmarks\api_listener.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_APP_HTML" file="resources\bookmarks\app.html" type="BINDATA" preprocess="true" />
+            <include name="IDR_BOOKMARKS_APP_JS" file="resources\bookmarks\app.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_BOOKMARKS_HTML" file="resources\bookmarks\bookmarks.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_COMMAND_MANAGER_HTML" file="resources\bookmarks\command_manager.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_COMMAND_MANAGER_JS" file="resources\bookmarks\command_manager.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_CONSTANTS_HTML" file="resources\bookmarks\constants.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_CONSTANTS_JS" file="resources\bookmarks\constants.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_DEBOUNCER_HTML" file="resources\bookmarks\debouncer.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_DEBOUNCER_JS" file="resources\bookmarks\debouncer.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_DIALOG_FOCUS_MANAGER_HTML" file="resources\bookmarks\dialog_focus_manager.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_DIALOG_FOCUS_MANAGER_JS" file="resources\bookmarks\dialog_focus_manager.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_DND_MANAGER_HTML" file="resources\bookmarks\dnd_manager.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_DND_MANAGER_JS" file="resources\bookmarks\dnd_manager.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_EDIT_DIALOG_HTML" file="resources\bookmarks\edit_dialog.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_EDIT_DIALOG_JS" file="resources\bookmarks\edit_dialog.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_FOLDER_NODE_HTML" file="resources\bookmarks\folder_node.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_FOLDER_NODE_JS" file="resources\bookmarks\folder_node.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_ITEM_HTML" file="resources\bookmarks\item.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_ITEM_JS" file="resources\bookmarks\item.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_LIST_HTML" file="resources\bookmarks\list.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_LIST_JS" file="resources\bookmarks\list.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_MOUSE_FOCUS_BEHAVIOR_HTML" file="resources\bookmarks\mouse_focus_behavior.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_MOUSE_FOCUS_BEHAVIOR_JS" file="resources\bookmarks\mouse_focus_behavior.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_REDUCERS_HTML" file="resources\bookmarks\reducers.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_REDUCERS_JS" file="resources\bookmarks\reducers.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_ROUTER_HTML" file="resources\bookmarks\router.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_ROUTER_JS" file="resources\bookmarks\router.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_SHARED_STYLE_HTML" file="resources\bookmarks\shared_style.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_SHARED_VARS_HTML" file="resources\bookmarks\shared_vars.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_STORE_CLIENT_HTML" file="resources\bookmarks\store_client.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_STORE_CLIENT_JS" file="resources\bookmarks\store_client.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_STORE_HTML" file="resources\bookmarks\store.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_STORE_JS" file="resources\bookmarks\store.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_STRINGS_HTML" file="resources\bookmarks\strings.html" type="BINDATA" />
 
-            <include name="IDR_MD_BOOKMARKS_TOAST_MANAGER_HTML" file="resources\bookmarks\toast_manager.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_TOAST_MANAGER_JS" file="resources\bookmarks\toast_manager.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_TOOLBAR_HTML" file="resources\bookmarks\toolbar.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_TOOLBAR_JS" file="resources\bookmarks\toolbar.js" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_UTIL_HTML" file="resources\bookmarks\util.html" type="BINDATA" />
-            <include name="IDR_MD_BOOKMARKS_UTIL_JS" file="resources\bookmarks\util.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_TOAST_MANAGER_HTML" file="resources\bookmarks\toast_manager.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_TOAST_MANAGER_JS" file="resources\bookmarks\toast_manager.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_TOOLBAR_HTML" file="resources\bookmarks\toolbar.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_TOOLBAR_JS" file="resources\bookmarks\toolbar.js" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_UTIL_HTML" file="resources\bookmarks\util.html" type="BINDATA" />
+            <include name="IDR_BOOKMARKS_UTIL_JS" file="resources\bookmarks\util.js" type="BINDATA" />
           </else>
         </if>
 
@@ -498,6 +498,14 @@
             <include name="IDR_BLUETOOTH_PAIRING_DIALOG_JS" file="resources\chromeos\bluetooth_pairing_dialog\bluetooth_pairing_dialog.js" type="chrome_html" />
           </else>
         </if>
+        <!-- Chrome OS Account Manager welcome screen resources -->
+        <include name="IDR_ACCOUNT_MANAGER_WELCOME_CSS" file="resources\chromeos\account_manager_welcome.css" flattenhtml="true" type="chrome_html" />
+        <include name="IDR_ACCOUNT_MANAGER_WELCOME_HTML" file="resources\chromeos\account_manager_welcome.html" allowexternalscript="true" flattenhtml="true" type="chrome_html" />
+        <include name="IDR_ACCOUNT_MANAGER_WELCOME_JS" file="resources\chromeos\account_manager_welcome.js" flattenhtml="true" type="chrome_html" />
+        <include name="IDR_ACCOUNT_MANAGER_WELCOME_1X_PNG" file="resources\chromeos\account_manager_welcome_1x.png" type="BINDATA" />
+        <include name="IDR_ACCOUNT_MANAGER_WELCOME_2X_PNG" file="resources\chromeos\account_manager_welcome_2x.png" type="BINDATA" />
+        <include name="IDR_ACCOUNT_MANAGER_WELCOME_GOOGLE_LOGO_SVG" file="resources\chromeos\googleg.svg" type="BINDATA" />
+
         <include name="IDR_CROSH_BUILTIN_MANIFEST" file="resources\chromeos\crosh_builtin\manifest.json" type="BINDATA" />
         <include name="IDR_CRYPTOHOME_HTML" file="resources\chromeos\cryptohome.html" flattenhtml="true" type="BINDATA" />
         <include name="IDR_CRYPTOHOME_JS" file="resources\chromeos\cryptohome.js" type="BINDATA" />
diff --git a/chrome/browser/browsing_data/cookies_tree_model.cc b/chrome/browser/browsing_data/cookies_tree_model.cc
index e365b84d..9cd3d365 100644
--- a/chrome/browser/browsing_data/cookies_tree_model.cc
+++ b/chrome/browser/browsing_data/cookies_tree_model.cc
@@ -387,8 +387,8 @@
   }
 
   void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    callback.Run(this->GetDetailedInfo().origin,
-                 this->GetDetailedInfo().appcache_info->size);
+    const DetailedInfo info = GetDetailedInfo();
+    callback.Run(info.origin, info.appcache_info->size);
   }
 
  private:
@@ -432,8 +432,8 @@
   }
 
   void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    callback.Run(this->GetDetailedInfo().origin,
-                 this->GetDetailedInfo().database_info->size);
+    const DetailedInfo info = GetDetailedInfo();
+    callback.Run(info.origin, info.database_info->size);
   }
 
  private:
@@ -475,8 +475,8 @@
   }
 
   void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    callback.Run(this->GetDetailedInfo().origin,
-                 this->GetDetailedInfo().local_storage_info->size);
+    const DetailedInfo info = GetDetailedInfo();
+    callback.Run(info.origin, info.local_storage_info->size);
   }
 
  private:
@@ -559,8 +559,8 @@
   }
 
   void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    callback.Run(this->GetDetailedInfo().origin,
-                 this->GetDetailedInfo().usage_info->total_size_bytes);
+    const DetailedInfo info = GetDetailedInfo();
+    callback.Run(info.origin, info.usage_info->total_size_bytes);
   }
 
  private:
@@ -603,11 +603,11 @@
 
   void RetrieveSize(const SizeRetrievalCallback& callback) override {
     int64_t size = 0;
-    for (auto const& usage :
-         this->GetDetailedInfo().file_system_info->usage_map) {
+    const DetailedInfo info = GetDetailedInfo();
+    for (auto const& usage : info.file_system_info->usage_map) {
       size += usage.second;
     }
-    callback.Run(this->GetDetailedInfo().origin, size);
+    callback.Run(info.origin, size);
   }
 
  private:
@@ -686,8 +686,8 @@
   }
 
   void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    callback.Run(this->GetDetailedInfo().origin,
-                 this->GetDetailedInfo().usage_info->total_size_bytes);
+    const DetailedInfo info = GetDetailedInfo();
+    callback.Run(info.origin, info.usage_info->total_size_bytes);
   }
 
  private:
@@ -768,8 +768,8 @@
   }
 
   void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    callback.Run(this->GetDetailedInfo().origin,
-                 this->GetDetailedInfo().usage_info->total_size_bytes);
+    const DetailedInfo info = GetDetailedInfo();
+    callback.Run(info.origin, info.usage_info->total_size_bytes);
   }
 
  private:
@@ -806,13 +806,14 @@
       container->media_license_info_list_.erase(media_license_info_);
     }
   }
+
   DetailedInfo GetDetailedInfo() const override {
     return DetailedInfo().InitMediaLicense(&*media_license_info_);
   }
 
   void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    callback.Run(this->GetDetailedInfo().origin,
-                 this->GetDetailedInfo().media_license_info->size);
+    const DetailedInfo info = GetDetailedInfo();
+    callback.Run(info.origin, info.media_license_info->size);
   }
 
  private:
@@ -887,12 +888,32 @@
 };
 
 ///////////////////////////////////////////////////////////////////////////////
+// CookieTreeCollectionNode
+
+class CookieTreeCollectionNode : public CookieTreeNode {
+ public:
+  explicit CookieTreeCollectionNode(const base::string16& title)
+      : CookieTreeNode(title) {}
+
+  ~CookieTreeCollectionNode() override {}
+
+  void RetrieveSize(const SizeRetrievalCallback& callback) final {
+    for (int i = 0; i < this->child_count(); ++i) {
+      this->GetChild(i)->RetrieveSize(callback);
+    }
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CookieTreeCollectionNode);
+};
+
+///////////////////////////////////////////////////////////////////////////////
 // CookieTreeAppCachesNode
 
-class CookieTreeAppCachesNode : public CookieTreeNode {
+class CookieTreeAppCachesNode : public CookieTreeCollectionNode {
  public:
   CookieTreeAppCachesNode()
-      : CookieTreeNode(
+      : CookieTreeCollectionNode(
             l10n_util::GetStringUTF16(IDS_COOKIES_APPLICATION_CACHES)) {}
 
   ~CookieTreeAppCachesNode() override {}
@@ -901,12 +922,6 @@
     return DetailedInfo().Init(DetailedInfo::TYPE_APPCACHES);
   }
 
-  void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    for (int i = 0; i < this->child_count(); ++i) {
-      this->GetChild(i)->RetrieveSize(callback);
-    }
-  }
-
   void AddAppCacheNode(std::unique_ptr<CookieTreeAppCacheNode> child) {
     AddChildSortedByTitle(std::move(child));
   }
@@ -918,10 +933,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 // CookieTreeDatabasesNode
 
-class CookieTreeDatabasesNode : public CookieTreeNode {
+class CookieTreeDatabasesNode : public CookieTreeCollectionNode {
  public:
   CookieTreeDatabasesNode()
-      : CookieTreeNode(l10n_util::GetStringUTF16(IDS_COOKIES_WEB_DATABASES)) {}
+      : CookieTreeCollectionNode(
+            l10n_util::GetStringUTF16(IDS_COOKIES_WEB_DATABASES)) {}
 
   ~CookieTreeDatabasesNode() override {}
 
@@ -929,12 +945,6 @@
     return DetailedInfo().Init(DetailedInfo::TYPE_DATABASES);
   }
 
-  void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    for (int i = 0; i < this->child_count(); ++i) {
-      this->GetChild(i)->RetrieveSize(callback);
-    }
-  }
-
   void AddDatabaseNode(std::unique_ptr<CookieTreeDatabaseNode> child) {
     AddChildSortedByTitle(std::move(child));
   }
@@ -946,10 +956,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 // CookieTreeLocalStoragesNode
 
-class CookieTreeLocalStoragesNode : public CookieTreeNode {
+class CookieTreeLocalStoragesNode : public CookieTreeCollectionNode {
  public:
   CookieTreeLocalStoragesNode()
-      : CookieTreeNode(l10n_util::GetStringUTF16(IDS_COOKIES_LOCAL_STORAGE)) {}
+      : CookieTreeCollectionNode(
+            l10n_util::GetStringUTF16(IDS_COOKIES_LOCAL_STORAGE)) {}
 
   ~CookieTreeLocalStoragesNode() override {}
 
@@ -957,12 +968,6 @@
     return DetailedInfo().Init(DetailedInfo::TYPE_LOCAL_STORAGES);
   }
 
-  void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    for (int i = 0; i < this->child_count(); ++i) {
-      this->GetChild(i)->RetrieveSize(callback);
-    }
-  }
-
   void AddLocalStorageNode(std::unique_ptr<CookieTreeLocalStorageNode> child) {
     AddChildSortedByTitle(std::move(child));
   }
@@ -998,10 +1003,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 // CookieTreeIndexedDBsNode
 
-class CookieTreeIndexedDBsNode : public CookieTreeNode {
+class CookieTreeIndexedDBsNode : public CookieTreeCollectionNode {
  public:
   CookieTreeIndexedDBsNode()
-      : CookieTreeNode(l10n_util::GetStringUTF16(IDS_COOKIES_INDEXED_DBS)) {}
+      : CookieTreeCollectionNode(
+            l10n_util::GetStringUTF16(IDS_COOKIES_INDEXED_DBS)) {}
 
   ~CookieTreeIndexedDBsNode() override {}
 
@@ -1009,12 +1015,6 @@
     return DetailedInfo().Init(DetailedInfo::TYPE_INDEXED_DBS);
   }
 
-  void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    for (int i = 0; i < this->child_count(); ++i) {
-      this->GetChild(i)->RetrieveSize(callback);
-    }
-  }
-
   void AddIndexedDBNode(std::unique_ptr<CookieTreeIndexedDBNode> child) {
     AddChildSortedByTitle(std::move(child));
   }
@@ -1026,10 +1026,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 // CookieTreeFileSystemsNode
 
-class CookieTreeFileSystemsNode : public CookieTreeNode {
+class CookieTreeFileSystemsNode : public CookieTreeCollectionNode {
  public:
   CookieTreeFileSystemsNode()
-      : CookieTreeNode(l10n_util::GetStringUTF16(IDS_COOKIES_FILE_SYSTEMS)) {}
+      : CookieTreeCollectionNode(
+            l10n_util::GetStringUTF16(IDS_COOKIES_FILE_SYSTEMS)) {}
 
   ~CookieTreeFileSystemsNode() override {}
 
@@ -1037,12 +1038,6 @@
     return DetailedInfo().Init(DetailedInfo::TYPE_FILE_SYSTEMS);
   }
 
-  void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    for (int i = 0; i < this->child_count(); ++i) {
-      this->GetChild(i)->RetrieveSize(callback);
-    }
-  }
-
   void AddFileSystemNode(std::unique_ptr<CookieTreeFileSystemNode> child) {
     AddChildSortedByTitle(std::move(child));
   }
@@ -1054,11 +1049,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 // CookieTreeServiceWorkersNode
 
-class CookieTreeServiceWorkersNode : public CookieTreeNode {
+class CookieTreeServiceWorkersNode : public CookieTreeCollectionNode {
  public:
   CookieTreeServiceWorkersNode()
-      : CookieTreeNode(l10n_util::GetStringUTF16(IDS_COOKIES_SERVICE_WORKERS)) {
-  }
+      : CookieTreeCollectionNode(
+            l10n_util::GetStringUTF16(IDS_COOKIES_SERVICE_WORKERS)) {}
 
   ~CookieTreeServiceWorkersNode() override {}
 
@@ -1066,12 +1061,6 @@
     return DetailedInfo().Init(DetailedInfo::TYPE_SERVICE_WORKERS);
   }
 
-  void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    for (int i = 0; i < this->child_count(); ++i) {
-      this->GetChild(i)->RetrieveSize(callback);
-    }
-  }
-
   void AddServiceWorkerNode(
       std::unique_ptr<CookieTreeServiceWorkerNode> child) {
     AddChildSortedByTitle(std::move(child));
@@ -1106,10 +1095,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 // CookieTreeCacheStoragesNode
 
-class CookieTreeCacheStoragesNode : public CookieTreeNode {
+class CookieTreeCacheStoragesNode : public CookieTreeCollectionNode {
  public:
   CookieTreeCacheStoragesNode()
-      : CookieTreeNode(l10n_util::GetStringUTF16(IDS_COOKIES_CACHE_STORAGE)) {}
+      : CookieTreeCollectionNode(
+            l10n_util::GetStringUTF16(IDS_COOKIES_CACHE_STORAGE)) {}
 
   ~CookieTreeCacheStoragesNode() override {}
 
@@ -1117,12 +1107,6 @@
     return DetailedInfo().Init(DetailedInfo::TYPE_CACHE_STORAGES);
   }
 
-  void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    for (int i = 0; i < this->child_count(); ++i) {
-      this->GetChild(i)->RetrieveSize(callback);
-    }
-  }
-
   void AddCacheStorageNode(std::unique_ptr<CookieTreeCacheStorageNode> child) {
     AddChildSortedByTitle(std::move(child));
   }
@@ -1166,10 +1150,11 @@
 ///////////////////////////////////////////////////////////////////////////////
 // CookieTreeMediaLicensesNode
 
-class CookieTreeMediaLicensesNode : public CookieTreeNode {
+class CookieTreeMediaLicensesNode : public CookieTreeCollectionNode {
  public:
   CookieTreeMediaLicensesNode()
-      : CookieTreeNode(l10n_util::GetStringUTF16(IDS_COOKIES_MEDIA_LICENSES)) {}
+      : CookieTreeCollectionNode(
+            l10n_util::GetStringUTF16(IDS_COOKIES_MEDIA_LICENSES)) {}
 
   ~CookieTreeMediaLicensesNode() override {}
 
@@ -1177,12 +1162,6 @@
     return DetailedInfo().Init(DetailedInfo::TYPE_MEDIA_LICENSES);
   }
 
-  void RetrieveSize(const SizeRetrievalCallback& callback) override {
-    for (int i = 0; i < this->child_count(); ++i) {
-      this->GetChild(i)->RetrieveSize(callback);
-    }
-  }
-
   void AddMediaLicenseNode(std::unique_ptr<CookieTreeMediaLicenseNode> child) {
     AddChildSortedByTitle(std::move(child));
   }
diff --git a/chrome/browser/chrome_browser_main_mac.mm b/chrome/browser/chrome_browser_main_mac.mm
index ea96e17..cff6866 100644
--- a/chrome/browser/chrome_browser_main_mac.mm
+++ b/chrome/browser/chrome_browser_main_mac.mm
@@ -6,6 +6,9 @@
 
 #import <Cocoa/Cocoa.h>
 #include <libproc.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/param.h>
 
 #include "base/command_line.h"
 #include "base/files/file_path.h"
@@ -16,6 +19,7 @@
 #include "base/mac/scoped_nsobject.h"
 #include "base/mac/sdk_forward_declarations.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/optional.h"
 #include "base/path_service.h"
 #include "base/task/post_task.h"
 #include "base/task/task_traits.h"
@@ -251,12 +255,151 @@
               }];
 }
 
+bool IsDirectoryWriteable(NSString* dir_path) {
+  NSString* file_path = [dir_path stringByAppendingPathComponent:@"tempfile"];
+  NSData* data = [NSData dataWithBytes:"\01\02\03\04\05" length:5];
+  BOOL success = [data writeToFile:file_path atomically:NO];
+  if (success)
+    [[NSFileManager defaultManager] removeItemAtPath:file_path error:nil];
+
+  return success;
+}
+
+bool IsOnSameFilesystemAsChromium(NSString* dir_path) {
+  static const base::Optional<fsid_t> cr_fsid = []() -> base::Optional<fsid_t> {
+    struct statfs buf;
+    int result = statfs(
+        [[base::mac::OuterBundle() bundlePath] fileSystemRepresentation], &buf);
+    if (result != 0)
+      return base::nullopt;
+    return buf.f_fsid;
+  }();
+
+  if (!cr_fsid)
+    return false;
+
+  struct statfs buf;
+  int result = statfs([dir_path fileSystemRepresentation], &buf);
+  if (result != 0)
+    return false;
+
+  return cr_fsid->val[0] == buf.f_fsid.val[0] &&
+         cr_fsid->val[1] == buf.f_fsid.val[1];
+}
+
+// Used for UMA; never alter existing values.
+enum class StagingDirectoryStep {
+  kFailedToFindDirectory,
+  kItemReplacementDirectory,
+  kSiblingDirectory,
+  kNSTemporaryDirectory,
+  kTMPDIRDirectory,
+  kTmpDirectory,
+  kMaxValue = kTmpDirectory,
+};
+
+void LogStagingDirectoryLocation(StagingDirectoryStep step) {
+  UMA_HISTOGRAM_ENUMERATION("OSX.StagingDirectoryLocation", step);
+}
+
+void RecordStagingDirectoryStats() {
+  NSURL* bundle_url = [base::mac::OuterBundle() bundleURL];
+  NSFileManager* file_manager = [NSFileManager defaultManager];
+
+  // 1. NSItemReplacementDirectory
+
+  NSError* error = nil;
+  NSURL* item_replacement_dir =
+      [file_manager URLForDirectory:NSItemReplacementDirectory
+                           inDomain:NSUserDomainMask
+                  appropriateForURL:bundle_url
+                             create:YES
+                              error:&error];
+  if (item_replacement_dir && !error &&
+      IsDirectoryWriteable([item_replacement_dir path])) {
+    LogStagingDirectoryLocation(
+        StagingDirectoryStep::kItemReplacementDirectory);
+    return;
+  }
+
+  // 2. A directory alongside Chromium.
+
+  NSURL* bundle_parent_url =
+      [[bundle_url URLByStandardizingPath] URLByDeletingLastPathComponent];
+  NSURL* sibling_dir =
+      [bundle_parent_url URLByAppendingPathComponent:@".GoogleChromeStaging"
+                                         isDirectory:YES];
+  NSString* sibling_dir_path = [sibling_dir path];
+
+  BOOL is_directory;
+  BOOL path_existed = [file_manager fileExistsAtPath:sibling_dir_path
+                                         isDirectory:&is_directory];
+
+  BOOL success = true;
+  error = nil;
+  if (!path_existed) {
+    success = [file_manager createDirectoryAtURL:sibling_dir
+                     withIntermediateDirectories:YES
+                                      attributes:nil
+                                           error:&error];
+  } else if (!is_directory) {
+    // There is a non-directory there; don't attempt to use this location
+    // further.
+    success = false;
+  }
+
+  if (success) {
+    success &= !error && IsDirectoryWriteable(sibling_dir_path);
+
+    // Only delete this directory if this was the code that created it.
+    if (!path_existed)
+      [file_manager removeItemAtURL:sibling_dir error:nil];
+  }
+
+  if (success) {
+    LogStagingDirectoryLocation(StagingDirectoryStep::kSiblingDirectory);
+    return;
+  }
+
+  // 3. NSTemporaryDirectory()
+
+  NSString* ns_temporary_dir = NSTemporaryDirectory();
+  if (ns_temporary_dir && IsOnSameFilesystemAsChromium(ns_temporary_dir) &&
+      IsDirectoryWriteable(ns_temporary_dir)) {
+    LogStagingDirectoryLocation(StagingDirectoryStep::kNSTemporaryDirectory);
+    return;
+  }
+
+  // 4. $TMPDIR
+
+  const char* tmpdir_cstr = getenv("TMPDIR");
+  NSString* tmpdir = tmpdir_cstr ? @(tmpdir_cstr) : nil;
+  if (tmpdir && IsOnSameFilesystemAsChromium(tmpdir) &&
+      IsDirectoryWriteable(tmpdir)) {
+    LogStagingDirectoryLocation(StagingDirectoryStep::kTMPDIRDirectory);
+    return;
+  }
+
+  // 5. /tmp
+
+  NSString* tmp = @"/tmp";
+  if (IsOnSameFilesystemAsChromium(tmp) && IsDirectoryWriteable(tmp)) {
+    LogStagingDirectoryLocation(StagingDirectoryStep::kTmpDirectory);
+    return;
+  }
+
+  // 6. Give up.
+
+  LogStagingDirectoryLocation(StagingDirectoryStep::kFailedToFindDirectory);
+}
+
 // Records various bits of information about the local Chromium installation in
 // UMA.
 void RecordInstallationStats() {
   RecordFilesystemStats();
   RecordInstanceStats();
   InstallFastUserSwitchStatRecorder();
+  RecordStagingDirectoryStats();
 }
 
 }  // namespace
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index ebd07a34..b35dd7c5 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -315,6 +315,9 @@
     "account_mapper_util.h",
     "android_sms/android_sms_app_helper_delegate_impl.cc",
     "android_sms/android_sms_app_helper_delegate_impl.h",
+    "android_sms/android_sms_app_setup_controller.h",
+    "android_sms/android_sms_app_setup_controller_impl.cc",
+    "android_sms/android_sms_app_setup_controller_impl.h",
     "android_sms/android_sms_pairing_state_tracker_impl.cc",
     "android_sms/android_sms_pairing_state_tracker_impl.h",
     "android_sms/android_sms_service.cc",
@@ -2041,6 +2044,8 @@
   testonly = true
 
   sources = [
+    "android_sms/fake_android_sms_app_setup_controller.cc",
+    "android_sms/fake_android_sms_app_setup_controller.h",
     "android_sms/fake_connection_establisher.cc",
     "android_sms/fake_connection_establisher.h",
     "app_mode/test_kiosk_extension_builder.cc",
@@ -2125,6 +2130,7 @@
     "accessibility/switch_access_panel_unittest.cc",
     "account_manager/account_migration_runner_unittest.cc",
     "android_sms/android_sms_app_helper_delegate_impl_unittest.cc",
+    "android_sms/android_sms_app_setup_controller_impl_unittest.cc",
     "android_sms/connection_establisher_impl_unittest.cc",
     "android_sms/connection_manager_unittest.cc",
     "android_sms/pairing_lost_notifier_unittest.cc",
diff --git a/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller.h b/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller.h
new file mode 100644
index 0000000..94e2c13
--- /dev/null
+++ b/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller.h
@@ -0,0 +1,58 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_ANDROID_SMS_ANDROID_SMS_APP_SETUP_CONTROLLER_H_
+#define CHROME_BROWSER_CHROMEOS_ANDROID_SMS_ANDROID_SMS_APP_SETUP_CONTROLLER_H_
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "url/gurl.h"
+
+namespace extensions {
+class Extension;
+}  // namespace extensions
+
+namespace chromeos {
+
+namespace android_sms {
+
+// Manages the setup and uninstallation process of the Android SMS PWA.
+class AndroidSmsAppSetupController {
+ public:
+  AndroidSmsAppSetupController() = default;
+  virtual ~AndroidSmsAppSetupController() = default;
+
+  using SuccessCallback = base::OnceCallback<void(bool)>;
+
+  // Performs the setup process for the app at |url|, which includes:
+  //   (1) Installing the PWA,
+  //   (2) Granting permission for the PWA to show notifications, and
+  //   (3) Setting a cookie which defaults the PWA to remember this computer.
+  virtual void SetUpApp(const GURL& url, SuccessCallback callback) = 0;
+
+  // Returns the extension for the PWA at |url|; if no PWA exists, null is
+  // returned.
+  virtual const extensions::Extension* GetPwa(const GURL& url) = 0;
+
+  // Deletes the cookie which causes the PWA to remember this computer by
+  // default. Note that this does not actually stop the PWA from remembering
+  // this computer; rather, it stops the PWA from *defaulting* to remember the
+  // computer in the case that the user has not gone through the PWA's setup.
+  virtual void DeleteRememberDeviceByDefaultCookie(
+      const GURL& url,
+      SuccessCallback callback) = 0;
+
+  // Uninstalls the app at |url| and deletes relevant cookies from the setup
+  // process.
+  virtual void RemoveApp(const GURL& url, SuccessCallback callback) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AndroidSmsAppSetupController);
+};
+
+}  // namespace android_sms
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_ANDROID_SMS_ANDROID_SMS_APP_SETUP_CONTROLLER_H_
diff --git a/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl.cc b/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl.cc
new file mode 100644
index 0000000..05bc0be
--- /dev/null
+++ b/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl.cc
@@ -0,0 +1,247 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl.h"
+
+#include "base/callback.h"
+#include "base/containers/flat_map.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/optional.h"
+#include "chrome/browser/extensions/extension_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/components/pending_app_manager.h"
+#include "chrome/browser/web_applications/components/web_app_constants.h"
+#include "chromeos/components/multidevice/logging/logging.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "services/network/public/mojom/cookie_manager.mojom.h"
+#include "url/gurl.h"
+
+namespace chromeos {
+
+namespace android_sms {
+
+namespace {
+
+const char kDefaultToPersistCookieName[] = "default_to_persist";
+const char kDefaultToPersistCookieValue[] = "true";
+
+}  // namespace
+
+AndroidSmsAppSetupControllerImpl::PwaDelegate::PwaDelegate() = default;
+
+AndroidSmsAppSetupControllerImpl::PwaDelegate::~PwaDelegate() = default;
+
+const extensions::Extension*
+AndroidSmsAppSetupControllerImpl::PwaDelegate::GetPwaForUrl(const GURL& url,
+                                                            Profile* profile) {
+  return extensions::util::GetInstalledPwaForUrl(profile, url);
+}
+
+network::mojom::CookieManager*
+AndroidSmsAppSetupControllerImpl::PwaDelegate::GetCookieManager(
+    const GURL& url,
+    Profile* profile) {
+  return content::BrowserContext::GetStoragePartitionForSite(profile, url)
+      ->GetCookieManagerForBrowserProcess();
+}
+
+AndroidSmsAppSetupControllerImpl::AndroidSmsAppSetupControllerImpl(
+    Profile* profile,
+    web_app::PendingAppManager* pending_app_manager,
+    HostContentSettingsMap* host_content_settings_map)
+    : profile_(profile),
+      pending_app_manager_(pending_app_manager),
+      host_content_settings_map_(host_content_settings_map),
+      pwa_delegate_(std::make_unique<PwaDelegate>()),
+      weak_ptr_factory_(this) {}
+
+AndroidSmsAppSetupControllerImpl::~AndroidSmsAppSetupControllerImpl() = default;
+
+void AndroidSmsAppSetupControllerImpl::SetUpApp(const GURL& url,
+                                                SuccessCallback callback) {
+  PA_LOG(VERBOSE) << "AndroidSmsAppSetupControllerImpl::SetUpApp(): Setting "
+                  << "DefaultToPersist cookie at " << url << " before PWA "
+                  << "installation.";
+  pwa_delegate_->GetCookieManager(url, profile_)
+      ->SetCanonicalCookie(
+          *net::CanonicalCookie::CreateSanitizedCookie(
+              url, kDefaultToPersistCookieName, kDefaultToPersistCookieValue,
+              std::string() /* domain */, std::string() /* path */,
+              base::Time::Now() /* creation_time */,
+              base::Time() /* expiration_time */,
+              base::Time::Now() /* last_access_time */, true /* secure */,
+              false /* http_only */, net::CookieSameSite::STRICT_MODE,
+              net::COOKIE_PRIORITY_DEFAULT),
+          true /* secure_source */, false /* modify_http_only */,
+          base::BindOnce(&AndroidSmsAppSetupControllerImpl::OnSetCookieResult,
+                         weak_ptr_factory_.GetWeakPtr(), url,
+                         std::move(callback)));
+}
+
+const extensions::Extension* AndroidSmsAppSetupControllerImpl::GetPwa(
+    const GURL& url) {
+  return pwa_delegate_->GetPwaForUrl(url, profile_);
+}
+
+void AndroidSmsAppSetupControllerImpl::DeleteRememberDeviceByDefaultCookie(
+    const GURL& url,
+    SuccessCallback callback) {
+  PA_LOG(INFO) << "AndroidSmsAppSetupControllerImpl::"
+               << "DeleteRememberDeviceByDefaultCookie(): Deleting "
+               << "DefaultToPersist cookie at " << url << ".";
+  network::mojom::CookieDeletionFilterPtr filter(
+      network::mojom::CookieDeletionFilter::New());
+  filter->url = url;
+  filter->cookie_name = kDefaultToPersistCookieName;
+  pwa_delegate_->GetCookieManager(url, profile_)
+      ->DeleteCookies(
+          std::move(filter),
+          base::BindOnce(
+              &AndroidSmsAppSetupControllerImpl::OnDeleteCookiesResult,
+              weak_ptr_factory_.GetWeakPtr(), url, std::move(callback)));
+}
+
+void AndroidSmsAppSetupControllerImpl::RemoveApp(const GURL& url,
+                                                 SuccessCallback callback) {
+  // If there is no app installed at |url|, there is nothing more to do.
+  if (!pwa_delegate_->GetPwaForUrl(url, profile_)) {
+    PA_LOG(VERBOSE) << "AndroidSmsAppSetupControllerImpl::RemoveApp(): No app "
+                    << "is installed at " << url << "; skipping removal "
+                    << "process.";
+    std::move(callback).Run(true /* success */);
+    return;
+  }
+
+  PA_LOG(INFO) << "AndroidSmsAppSetupControllerImpl::RemoveApp(): "
+               << "Uninstalling app at " << url << ".";
+  // UninstallApps() takes a base::RepeatedCallback, but |callback| is a
+  // base::OnceCallback; thus, |callback| cannot be included in the closure
+  // because it has move-only semantics. Assign this uninstall attempt an ID
+  // associated with |callback| so that it can be retrieved in
+  // OnAppUninstallResult().
+  auto id = base::UnguessableToken::Create();
+  uninstall_id_to_callback_map_.emplace(id, std::move(callback));
+  pending_app_manager_->UninstallApps(
+      std::vector<GURL>{url},
+      base::BindRepeating(
+          &AndroidSmsAppSetupControllerImpl::OnAppUninstallResult,
+          weak_ptr_factory_.GetWeakPtr(), id));
+}
+
+void AndroidSmsAppSetupControllerImpl::OnSetCookieResult(
+    const GURL& url,
+    SuccessCallback callback,
+    bool succeeded) {
+  if (!succeeded) {
+    PA_LOG(WARNING) << "AndroidSmsAppSetupControllerImpl::"
+                    << "OnSetCookieResult(): Failed to set "
+                    << "DefaultToPersist cookie at " << url << ". Proceeding "
+                    << "with installation request.";
+  }
+
+  // If the app is already installed at |url|, there is nothing more to do.
+  if (pwa_delegate_->GetPwaForUrl(url, profile_)) {
+    PA_LOG(VERBOSE) << "AndroidSmsAppSetupControllerImpl::OnSetCookieResult(): "
+                    << "App is already installed at " << url << "; skipping "
+                    << "setup process.";
+    std::move(callback).Run(true /* success */);
+    return;
+  }
+
+  web_app::PendingAppManager::AppInfo info(url,
+                                           web_app::LaunchContainer::kWindow,
+                                           web_app::InstallSource::kInternal);
+  info.override_previous_user_uninstall = true;
+  // The ServiceWorker does not load in time for the installability check, so
+  // bypass it as a workaround.
+  info.bypass_service_worker_check = true;
+  info.require_manifest = true;
+
+  PA_LOG(VERBOSE) << "AndroidSmsAppSetupControllerImpl::OnSetCookieResult(): "
+                  << "Installing PWA for " << url << ".";
+  pending_app_manager_->Install(
+      std::move(info),
+      base::BindOnce(&AndroidSmsAppSetupControllerImpl::OnAppInstallResult,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void AndroidSmsAppSetupControllerImpl::OnAppInstallResult(
+    SuccessCallback callback,
+    const GURL& url,
+    web_app::InstallResultCode code) {
+  UMA_HISTOGRAM_ENUMERATION("AndroidSms.PWAInstallationResult", code);
+
+  if (code != web_app::InstallResultCode::kSuccess) {
+    PA_LOG(WARNING)
+        << "AndroidSmsAppSetupControllerImpl::OnAppInstallResult(): "
+        << "PWA for " << url << " failed to install. "
+        << "InstallResultCode: " << static_cast<int>(code);
+    std::move(callback).Run(false /* success */);
+    return;
+  }
+
+  PA_LOG(INFO) << "AndroidSmsAppSetupControllerImpl::OnAppInstallResult(): "
+               << "PWA for " << url << " was installed successfully.";
+
+  // Grant notification permission for the PWA.
+  host_content_settings_map_->SetWebsiteSettingDefaultScope(
+      url, GURL() /* top_level_url */,
+      ContentSettingsType::CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
+      content_settings::ResourceIdentifier(),
+      std::make_unique<base::Value>(ContentSetting::CONTENT_SETTING_ALLOW));
+
+  std::move(callback).Run(true /* success */);
+}
+
+void AndroidSmsAppSetupControllerImpl::OnAppUninstallResult(
+    const base::UnguessableToken& id,
+    const GURL& url,
+    bool succeeded) {
+  UMA_HISTOGRAM_BOOLEAN("AndroidSms.PWAUninstallationResult", succeeded);
+
+  // OnAppUninstallResult() should only be called once per ID, so the uninstall
+  // callback is always expected to exist in the map.
+  SuccessCallback callback = std::move(uninstall_id_to_callback_map_[id]);
+  CHECK(callback);
+  uninstall_id_to_callback_map_.erase(id);
+
+  if (!succeeded) {
+    PA_LOG(ERROR)
+        << "AndroidSmsAppSetupControllerImpl::OnAppUninstallResult(): "
+        << "PWA for " << url << " failed to uninstall.";
+    std::move(callback).Run(false /* success */);
+    return;
+  }
+
+  DeleteRememberDeviceByDefaultCookie(url, std::move(callback));
+}
+
+void AndroidSmsAppSetupControllerImpl::OnDeleteCookiesResult(
+    const GURL& url,
+    SuccessCallback callback,
+    uint32_t num_deleted) {
+  if (num_deleted != 1u) {
+    PA_LOG(WARNING) << "AndroidSmsAppSetupControllerImpl::"
+                    << "OnDeleteCookiesResult(): Tried to delete a single "
+                    << "cookie at " << url << ", but " << num_deleted << " "
+                    << "cookies were deleted.";
+  }
+
+  // Even if an unexpected number of cookies was deleted, consider this a
+  // success. If SetUpApp() failed to install a cookie earlier, the setup
+  // process is still considered a success, so failing to delete a cookie should
+  // also be considered a success.
+  std::move(callback).Run(true /* success */);
+}
+
+void AndroidSmsAppSetupControllerImpl::SetPwaDelegateForTesting(
+    std::unique_ptr<PwaDelegate> test_pwa_delegate) {
+  pwa_delegate_ = std::move(test_pwa_delegate);
+}
+
+}  // namespace android_sms
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl.h b/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl.h
new file mode 100644
index 0000000..95036f4c
--- /dev/null
+++ b/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl.h
@@ -0,0 +1,94 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_ANDROID_SMS_ANDROID_SMS_APP_SETUP_CONTROLLER_IMPL_H_
+#define CHROME_BROWSER_CHROMEOS_ANDROID_SMS_ANDROID_SMS_APP_SETUP_CONTROLLER_IMPL_H_
+
+#include "base/containers/flat_map.h"
+#include "base/memory/weak_ptr.h"
+#include "base/unguessable_token.h"
+#include "chrome/browser/chromeos/android_sms/android_sms_app_setup_controller.h"
+#include "url/gurl.h"
+
+class HostContentSettingsMap;
+class Profile;
+
+namespace network {
+namespace mojom {
+class CookieManager;
+}  // namespace mojom
+}  // namespace network
+
+namespace web_app {
+enum class InstallResultCode;
+class PendingAppManager;
+}  // namespace web_app
+
+namespace chromeos {
+
+namespace android_sms {
+
+// Concrete AndroidSmsAppSetupController implementation.
+class AndroidSmsAppSetupControllerImpl : public AndroidSmsAppSetupController {
+ public:
+  AndroidSmsAppSetupControllerImpl(
+      Profile* profile,
+      web_app::PendingAppManager* pending_app_manager,
+      HostContentSettingsMap* host_content_settings_map);
+  ~AndroidSmsAppSetupControllerImpl() override;
+
+ private:
+  friend class AndroidSmsAppSetupControllerImplTest;
+
+  // Thin wrapper around static PWA functions which is stubbed out for tests.
+  class PwaDelegate {
+   public:
+    PwaDelegate();
+    virtual ~PwaDelegate();
+
+    virtual const extensions::Extension* GetPwaForUrl(const GURL& url,
+                                                      Profile* profile);
+    virtual network::mojom::CookieManager* GetCookieManager(const GURL& url,
+                                                            Profile* profile);
+  };
+
+  // AndroidSmsAppSetupController:
+  void SetUpApp(const GURL& url, SuccessCallback callback) override;
+  const extensions::Extension* GetPwa(const GURL& url) override;
+  void DeleteRememberDeviceByDefaultCookie(const GURL& url,
+                                           SuccessCallback callback) override;
+  void RemoveApp(const GURL& url, SuccessCallback callback) override;
+
+  void OnSetCookieResult(const GURL& url,
+                         SuccessCallback callback,
+                         bool succeeded);
+  void OnAppInstallResult(SuccessCallback callback,
+                          const GURL& url,
+                          web_app::InstallResultCode code);
+  void OnAppUninstallResult(const base::UnguessableToken& id,
+                            const GURL& url,
+                            bool succeeded);
+  void OnDeleteCookiesResult(const GURL& url,
+                             SuccessCallback callback,
+                             uint32_t num_deleted);
+
+  void SetPwaDelegateForTesting(std::unique_ptr<PwaDelegate> test_pwa_delegate);
+
+  Profile* profile_;
+  web_app::PendingAppManager* pending_app_manager_;
+  HostContentSettingsMap* host_content_settings_map_;
+
+  std::unique_ptr<PwaDelegate> pwa_delegate_;
+  base::flat_map<base::UnguessableToken, SuccessCallback>
+      uninstall_id_to_callback_map_;
+  base::WeakPtrFactory<AndroidSmsAppSetupControllerImpl> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(AndroidSmsAppSetupControllerImpl);
+};
+
+}  // namespace android_sms
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_ANDROID_SMS_ANDROID_SMS_APP_SETUP_CONTROLLER_IMPL_H_
diff --git a/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl_unittest.cc b/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl_unittest.cc
new file mode 100644
index 0000000..b2e3f79
--- /dev/null
+++ b/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl_unittest.cc
@@ -0,0 +1,389 @@
+// 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/chromeos/android_sms/android_sms_app_setup_controller_impl.h"
+
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/containers/flat_map.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/web_applications/components/test_pending_app_manager.h"
+#include "chrome/browser/web_applications/components/web_app_constants.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/extension_paths.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+
+namespace android_sms {
+
+namespace {
+
+const char kTestUrl1[] = "https://test-url-1.com/";
+const char kTestUrl2[] = "https://test-url-2.com/";
+
+web_app::PendingAppManager::AppInfo GetAppInfoForUrl(const GURL& url) {
+  web_app::PendingAppManager::AppInfo info(url,
+                                           web_app::LaunchContainer::kWindow,
+                                           web_app::InstallSource::kInternal);
+  info.override_previous_user_uninstall = true;
+  info.bypass_service_worker_check = true;
+  info.require_manifest = true;
+  return info;
+}
+
+class FakeCookieManager : public network::mojom::CookieManager {
+ public:
+  FakeCookieManager() = default;
+  ~FakeCookieManager() override {
+    EXPECT_TRUE(set_canonical_cookie_calls_.empty());
+    EXPECT_TRUE(delete_cookies_calls_.empty());
+  }
+
+  void InvokePendingSetCanonicalCookieCallback(
+      const std::string& expected_cookie_name,
+      bool expected_secure_source,
+      bool expected_modify_http_only,
+      bool success) {
+    ASSERT_FALSE(set_canonical_cookie_calls_.empty());
+    auto params = std::move(set_canonical_cookie_calls_.front());
+    set_canonical_cookie_calls_.erase(set_canonical_cookie_calls_.begin());
+
+    EXPECT_EQ(expected_cookie_name, std::get<0>(params).Name());
+    EXPECT_EQ(expected_secure_source, std::get<1>(params));
+    EXPECT_EQ(expected_modify_http_only, std::get<2>(params));
+
+    std::move(std::get<3>(params)).Run(success);
+  }
+
+  void InvokePendingDeleteCookiesCallback(
+      const GURL& expected_url,
+      const std::string& expected_cookie_name,
+      bool success) {
+    ASSERT_FALSE(delete_cookies_calls_.empty());
+    auto params = std::move(delete_cookies_calls_.front());
+    delete_cookies_calls_.erase(delete_cookies_calls_.begin());
+
+    EXPECT_EQ(expected_url, params.first->url);
+    EXPECT_EQ(expected_cookie_name, params.first->cookie_name);
+
+    std::move(params.second).Run(success);
+  }
+
+  // network::mojom::CookieManager
+  void SetCanonicalCookie(const net::CanonicalCookie& cookie,
+                          bool secure_source,
+                          bool modify_http_only,
+                          SetCanonicalCookieCallback callback) override {
+    set_canonical_cookie_calls_.emplace_back(
+        cookie, secure_source, modify_http_only, std::move(callback));
+  }
+
+  void DeleteCookies(network::mojom::CookieDeletionFilterPtr filter,
+                     DeleteCookiesCallback callback) override {
+    delete_cookies_calls_.emplace_back(std::move(filter), std::move(callback));
+  }
+
+  void GetAllCookies(GetAllCookiesCallback callback) override {}
+  void GetCookieList(const GURL& url,
+                     const net::CookieOptions& cookie_options,
+                     GetCookieListCallback callback) override {}
+  void DeleteCanonicalCookie(const net::CanonicalCookie& cookie,
+                             DeleteCanonicalCookieCallback callback) override {}
+  void AddCookieChangeListener(
+      const GURL& url,
+      const std::string& name,
+      network::mojom::CookieChangeListenerPtr listener) override {}
+  void AddGlobalChangeListener(
+      network::mojom::CookieChangeListenerPtr notification_pointer) override {}
+  void CloneInterface(
+      network::mojom::CookieManagerRequest new_interface) override {}
+  void FlushCookieStore(FlushCookieStoreCallback callback) override {}
+  void SetContentSettings(
+      const std::vector<::ContentSettingPatternSource>& settings) override {}
+  void SetForceKeepSessionState() override {}
+  void BlockThirdPartyCookies(bool block) override {}
+
+ private:
+  std::vector<
+      std::tuple<net::CanonicalCookie, bool, bool, SetCanonicalCookieCallback>>
+      set_canonical_cookie_calls_;
+  std::vector<
+      std::pair<network::mojom::CookieDeletionFilterPtr, DeleteCookiesCallback>>
+      delete_cookies_calls_;
+};
+
+}  // namespace
+
+class AndroidSmsAppSetupControllerImplTest : public testing::Test {
+ protected:
+  class TestPwaDelegate : public AndroidSmsAppSetupControllerImpl::PwaDelegate {
+   public:
+    explicit TestPwaDelegate(FakeCookieManager* fake_cookie_manager)
+        : fake_cookie_manager_(fake_cookie_manager) {}
+    ~TestPwaDelegate() override = default;
+
+    void SetHasPwa(const GURL& url, bool has_pwa) {
+      // If no PWA should exist, erase any existing entry and return.
+      if (!has_pwa) {
+        url_to_pwa_map_.erase(url);
+        return;
+      }
+
+      // If a PWA already exists for this URL, there is nothing to do.
+      if (base::ContainsKey(url_to_pwa_map_, url))
+        return;
+
+      // Create a test Extension and add it to |url_to_pwa_map_|.
+      base::FilePath path;
+      base::PathService::Get(extensions::DIR_TEST_DATA, &path);
+      url_to_pwa_map_[url] = extensions::ExtensionBuilder(url.spec())
+                                 .SetPath(path.AppendASCII(url.spec()))
+                                 .Build();
+    }
+
+    // AndroidSmsAppSetupControllerImpl::PwaDelegate:
+    const extensions::Extension* GetPwaForUrl(const GURL& url,
+                                              Profile* profile) override {
+      if (!base::ContainsKey(url_to_pwa_map_, url))
+        return nullptr;
+
+      return url_to_pwa_map_[url].get();
+    }
+
+    network::mojom::CookieManager* GetCookieManager(const GURL& url,
+                                                    Profile* profile) override {
+      return fake_cookie_manager_;
+    }
+
+   private:
+    FakeCookieManager* fake_cookie_manager_;
+    base::flat_map<GURL, scoped_refptr<const extensions::Extension>>
+        url_to_pwa_map_;
+  };
+
+  AndroidSmsAppSetupControllerImplTest()
+      : host_content_settings_map_(
+            HostContentSettingsMapFactory::GetForProfile(&profile_)) {}
+
+  ~AndroidSmsAppSetupControllerImplTest() override = default;
+
+  // testing::Test:
+  void SetUp() override {
+    host_content_settings_map_->ClearSettingsForOneType(
+        ContentSettingsType::CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
+    fake_cookie_manager_ = std::make_unique<FakeCookieManager>();
+    auto test_pwa_delegate =
+        std::make_unique<TestPwaDelegate>(fake_cookie_manager_.get());
+    test_pwa_delegate_ = test_pwa_delegate.get();
+    test_pending_app_manager_ =
+        std::make_unique<web_app::TestPendingAppManager>();
+    setup_controller_ = base::WrapUnique(new AndroidSmsAppSetupControllerImpl(
+        &profile_, test_pending_app_manager_.get(),
+        host_content_settings_map_));
+
+    std::unique_ptr<AndroidSmsAppSetupControllerImpl::PwaDelegate>
+        base_delegate(test_pwa_delegate.release());
+
+    static_cast<AndroidSmsAppSetupControllerImpl*>(setup_controller_.get())
+        ->SetPwaDelegateForTesting(std::move(base_delegate));
+  }
+
+  void CallSetUpApp(const GURL url, size_t num_expected_app_installs) {
+    const auto& install_requests =
+        test_pending_app_manager_->install_requests();
+    size_t num_install_requests_before_call = install_requests.size();
+
+    base::RunLoop run_loop;
+    base::HistogramTester histogram_tester;
+
+    setup_controller_->SetUpApp(
+        url,
+        base::BindOnce(&AndroidSmsAppSetupControllerImplTest::OnSetUpAppResult,
+                       base::Unretained(this), run_loop.QuitClosure()));
+
+    fake_cookie_manager_->InvokePendingSetCanonicalCookieCallback(
+        "default_to_persist" /* expected_cookie_name */,
+        true /* expected_secure_source */,
+        false /* expected_modify_http_only */, true /* success */);
+
+    // If the PWA was not already installed at the URL, SetUpApp() should
+    // install it.
+    if (!test_pwa_delegate_->GetPwaForUrl(url, &profile_)) {
+      EXPECT_EQ(num_install_requests_before_call + 1u, install_requests.size());
+      EXPECT_EQ(GetAppInfoForUrl(url), install_requests.back());
+
+      EXPECT_EQ(ContentSetting::CONTENT_SETTING_ALLOW,
+                GetNotificationSetting(url));
+    }
+
+    if (num_expected_app_installs) {
+      histogram_tester.ExpectBucketCount("AndroidSms.PWAInstallationResult",
+                                         web_app::InstallResultCode::kSuccess,
+                                         num_expected_app_installs);
+    }
+
+    run_loop.Run();
+    EXPECT_TRUE(*last_set_up_app_result_);
+    last_set_up_app_result_.reset();
+  }
+
+  void CallDeleteRememberDeviceByDefaultCookie(const GURL url) {
+    base::RunLoop run_loop;
+
+    setup_controller_->DeleteRememberDeviceByDefaultCookie(
+        url, base::BindOnce(&AndroidSmsAppSetupControllerImplTest::
+                                OnDeleteRememberDeviceByDefaultCookieResult,
+                            base::Unretained(this), run_loop.QuitClosure()));
+
+    fake_cookie_manager_->InvokePendingDeleteCookiesCallback(
+        url, "default_to_persist" /* expected_cookie_name */,
+        true /* success */);
+
+    run_loop.Run();
+    EXPECT_TRUE(*last_delete_cookie_result_);
+    last_delete_cookie_result_.reset();
+  }
+
+  void CallRemoveApp(const GURL url, size_t num_expected_app_uninstalls) {
+    const auto& uninstall_requests =
+        test_pending_app_manager_->uninstall_requests();
+    size_t num_uninstall_requests_before_call = uninstall_requests.size();
+
+    base::RunLoop run_loop;
+    base::HistogramTester histogram_tester;
+
+    setup_controller_->RemoveApp(
+        url,
+        base::BindOnce(&AndroidSmsAppSetupControllerImplTest::OnRemoveAppResult,
+                       base::Unretained(this), run_loop.QuitClosure()));
+
+    // If the PWA was already installed at the URL, RemoveApp() should uninstall
+    // the it.
+    if (test_pwa_delegate_->GetPwaForUrl(url, &profile_)) {
+      EXPECT_EQ(num_uninstall_requests_before_call + 1u,
+                uninstall_requests.size());
+      EXPECT_EQ(url, uninstall_requests.back());
+
+      fake_cookie_manager_->InvokePendingDeleteCookiesCallback(
+          url, "default_to_persist" /* expected_cookie_name */,
+          true /* success */);
+    }
+
+    if (num_expected_app_uninstalls) {
+      histogram_tester.ExpectBucketCount("AndroidSms.PWAUninstallationResult",
+                                         true, num_expected_app_uninstalls);
+    }
+
+    run_loop.Run();
+    EXPECT_TRUE(*last_remove_app_result_);
+    last_remove_app_result_.reset();
+  }
+
+  TestPwaDelegate* test_pwa_delegate() { return test_pwa_delegate_; }
+
+ private:
+  ContentSetting GetNotificationSetting(const GURL& url) {
+    std::unique_ptr<base::Value> notification_settings_value =
+        host_content_settings_map_->GetWebsiteSetting(
+            url, GURL() /* top_level_url */,
+            ContentSettingsType::CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
+            content_settings::ResourceIdentifier(), nullptr);
+    return static_cast<ContentSetting>(notification_settings_value->GetInt());
+  }
+
+  void OnSetUpAppResult(base::OnceClosure quit_closure, bool success) {
+    EXPECT_FALSE(last_set_up_app_result_);
+    last_set_up_app_result_ = success;
+    std::move(quit_closure).Run();
+  }
+
+  void OnDeleteRememberDeviceByDefaultCookieResult(
+      base::OnceClosure quit_closure,
+      bool success) {
+    EXPECT_FALSE(last_delete_cookie_result_);
+    last_delete_cookie_result_ = success;
+    std::move(quit_closure).Run();
+  }
+
+  void OnRemoveAppResult(base::OnceClosure quit_closure, bool success) {
+    EXPECT_FALSE(last_remove_app_result_);
+    last_remove_app_result_ = success;
+    std::move(quit_closure).Run();
+  }
+
+  content::TestBrowserThreadBundle thread_bundle_;
+
+  base::Optional<bool> last_set_up_app_result_;
+  base::Optional<bool> last_delete_cookie_result_;
+  base::Optional<bool> last_remove_app_result_;
+
+  TestingProfile profile_;
+  HostContentSettingsMap* host_content_settings_map_;
+  std::unique_ptr<FakeCookieManager> fake_cookie_manager_;
+  std::unique_ptr<web_app::TestPendingAppManager> test_pending_app_manager_;
+  TestPwaDelegate* test_pwa_delegate_;
+  std::unique_ptr<AndroidSmsAppSetupController> setup_controller_;
+
+  DISALLOW_COPY_AND_ASSIGN(AndroidSmsAppSetupControllerImplTest);
+};
+
+TEST_F(AndroidSmsAppSetupControllerImplTest, SetUpApp_NoPreviousApp) {
+  CallSetUpApp(GURL(kTestUrl1), 1u /* num_expected_app_installs */);
+}
+
+TEST_F(AndroidSmsAppSetupControllerImplTest, SetUpApp_AppAlreadyInstalled) {
+  // Start with a PWA already installed at the URL.
+  test_pwa_delegate()->SetHasPwa(GURL(kTestUrl1), true);
+  CallSetUpApp(GURL(kTestUrl1), 0u /* num_expected_app_installs */);
+}
+
+TEST_F(AndroidSmsAppSetupControllerImplTest, SetUpApp_OtherPwaInstalled) {
+  // Start with a PWA already installed at a different URL.
+  test_pwa_delegate()->SetHasPwa(GURL(kTestUrl2), true);
+  CallSetUpApp(GURL(kTestUrl1), 1u /* num_expected_app_installs */);
+}
+
+TEST_F(AndroidSmsAppSetupControllerImplTest, SetUpAppThenDeleteCookie) {
+  CallSetUpApp(GURL(kTestUrl1), 1u /* num_expected_app_installs */);
+  CallDeleteRememberDeviceByDefaultCookie(GURL(kTestUrl1));
+}
+
+TEST_F(AndroidSmsAppSetupControllerImplTest, SetUpAppThenRemove) {
+  // Install and remove.
+  CallSetUpApp(GURL(kTestUrl1), 1u /* num_expected_app_installs */);
+  test_pwa_delegate()->SetHasPwa(GURL(kTestUrl1), true);
+  CallRemoveApp(GURL(kTestUrl1), 1u /* num_expected_app_uninstalls */);
+  test_pwa_delegate()->SetHasPwa(GURL(kTestUrl1), false);
+
+  // Repeat once more.
+  CallSetUpApp(GURL(kTestUrl1), 1u /* num_expected_app_installs */);
+  test_pwa_delegate()->SetHasPwa(GURL(kTestUrl1), true);
+  CallRemoveApp(GURL(kTestUrl1), 1u /* num_expected_app_uninstalls */);
+  test_pwa_delegate()->SetHasPwa(GURL(kTestUrl1), false);
+}
+
+TEST_F(AndroidSmsAppSetupControllerImplTest, RemoveApp_NoInstalledApp) {
+  // Do not have an installed app before attempting to remove it.
+  CallRemoveApp(GURL(kTestUrl1), 0u /* num_expected_app_uninstalls */);
+}
+
+}  // namespace android_sms
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/android_sms/fake_android_sms_app_setup_controller.cc b/chrome/browser/chromeos/android_sms/fake_android_sms_app_setup_controller.cc
new file mode 100644
index 0000000..0e56d0a
--- /dev/null
+++ b/chrome/browser/chromeos/android_sms/fake_android_sms_app_setup_controller.cc
@@ -0,0 +1,136 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/android_sms/fake_android_sms_app_setup_controller.h"
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_builder.h"
+#include "extensions/common/extension_paths.h"
+
+namespace chromeos {
+
+namespace android_sms {
+
+FakeAndroidSmsAppSetupController::AppMetadata::AppMetadata() = default;
+
+FakeAndroidSmsAppSetupController::AppMetadata::AppMetadata(
+    const AppMetadata& other) = default;
+
+FakeAndroidSmsAppSetupController::AppMetadata::~AppMetadata() = default;
+
+FakeAndroidSmsAppSetupController::FakeAndroidSmsAppSetupController() = default;
+
+FakeAndroidSmsAppSetupController::~FakeAndroidSmsAppSetupController() = default;
+
+const FakeAndroidSmsAppSetupController::AppMetadata*
+FakeAndroidSmsAppSetupController::GetAppMetadataAtUrl(const GURL& url) const {
+  const auto it = url_to_metadata_map_.find(url);
+  if (it == url_to_metadata_map_.end())
+    return nullptr;
+  return std::addressof(it->second);
+}
+
+void FakeAndroidSmsAppSetupController::SetAppAtUrl(
+    const GURL& url,
+    const base::Optional<extensions::ExtensionId>& id_for_app) {
+  if (!id_for_app) {
+    url_to_metadata_map_.erase(url);
+    return;
+  }
+
+  // Create a test Extension and add it to |url_to_metadata_map_|.
+  base::FilePath path;
+  base::PathService::Get(extensions::DIR_TEST_DATA, &path);
+  url_to_metadata_map_[url].pwa =
+      extensions::ExtensionBuilder(
+          url.spec(), extensions::ExtensionBuilder::Type::PLATFORM_APP)
+          .SetPath(path.AppendASCII(url.spec()))
+          .SetID(*id_for_app)
+          .Build();
+}
+
+void FakeAndroidSmsAppSetupController::CompletePendingSetUpAppRequest(
+    const GURL& expected_url,
+    const base::Optional<extensions::ExtensionId>& id_for_app) {
+  DCHECK(!pending_set_up_app_requests_.empty());
+
+  auto request = std::move(pending_set_up_app_requests_.front());
+  pending_set_up_app_requests_.erase(pending_set_up_app_requests_.begin());
+  DCHECK_EQ(expected_url, request->first);
+
+  if (!id_for_app) {
+    std::move(request->second).Run(false /* success */);
+    return;
+  }
+
+  SetAppAtUrl(expected_url, *id_for_app);
+  std::move(request->second).Run(true /* success */);
+}
+
+void FakeAndroidSmsAppSetupController::CompletePendingDeleteCookieRequest(
+    const GURL& expected_url) {
+  DCHECK(!pending_delete_cookie_requests_.empty());
+
+  auto request = std::move(pending_delete_cookie_requests_.front());
+  pending_delete_cookie_requests_.erase(
+      pending_delete_cookie_requests_.begin());
+  DCHECK_EQ(expected_url, request->first);
+
+  // The app must exist before the cookie is deleted.
+  auto it = url_to_metadata_map_.find(expected_url);
+  DCHECK(it != url_to_metadata_map_.end());
+
+  it->second.is_cookie_present = false;
+
+  std::move(request->second).Run(true /* success */);
+}
+
+void FakeAndroidSmsAppSetupController::CompleteRemoveAppRequest(
+    const GURL& expected_url,
+    bool should_succeed) {
+  DCHECK(!pending_remove_app_requests_.empty());
+
+  auto request = std::move(pending_remove_app_requests_.front());
+  pending_remove_app_requests_.erase(pending_remove_app_requests_.begin());
+  DCHECK_EQ(expected_url, request->first);
+
+  if (should_succeed)
+    SetAppAtUrl(expected_url, base::nullopt /* id_for_app */);
+
+  std::move(request->second).Run(should_succeed);
+}
+
+void FakeAndroidSmsAppSetupController::SetUpApp(const GURL& url,
+                                                SuccessCallback callback) {
+  pending_set_up_app_requests_.push_back(
+      std::make_unique<RequestData>(url, std::move(callback)));
+}
+
+const extensions::Extension* FakeAndroidSmsAppSetupController::GetPwa(
+    const GURL& url) {
+  auto it = url_to_metadata_map_.find(url);
+  if (it == url_to_metadata_map_.end())
+    return nullptr;
+  return it->second.pwa.get();
+}
+
+void FakeAndroidSmsAppSetupController::DeleteRememberDeviceByDefaultCookie(
+    const GURL& url,
+    SuccessCallback callback) {
+  pending_delete_cookie_requests_.push_back(
+      std::make_unique<RequestData>(url, std::move(callback)));
+}
+
+void FakeAndroidSmsAppSetupController::RemoveApp(const GURL& url,
+                                                 SuccessCallback callback) {
+  pending_remove_app_requests_.push_back(
+      std::make_unique<RequestData>(url, std::move(callback)));
+}
+
+}  // namespace android_sms
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/android_sms/fake_android_sms_app_setup_controller.h b/chrome/browser/chromeos/android_sms/fake_android_sms_app_setup_controller.h
new file mode 100644
index 0000000..05306384
--- /dev/null
+++ b/chrome/browser/chromeos/android_sms/fake_android_sms_app_setup_controller.h
@@ -0,0 +1,89 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_ANDROID_SMS_FAKE_ANDROID_SMS_APP_SETUP_CONTROLLER_H_
+#define CHROME_BROWSER_CHROMEOS_ANDROID_SMS_FAKE_ANDROID_SMS_APP_SETUP_CONTROLLER_H_
+
+#include <list>
+#include <memory>
+#include <utility>
+
+#include "base/containers/flat_map.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "chrome/browser/chromeos/android_sms/android_sms_app_setup_controller.h"
+#include "extensions/common/extension_id.h"
+#include "url/gurl.h"
+
+namespace extensions {
+class Extension;
+}  // namespace extensions
+
+namespace chromeos {
+
+namespace android_sms {
+
+// Test AndroidSmsAppSetupController implementation.
+class FakeAndroidSmsAppSetupController : public AndroidSmsAppSetupController {
+ public:
+  FakeAndroidSmsAppSetupController();
+  ~FakeAndroidSmsAppSetupController() override;
+
+  struct AppMetadata {
+    AppMetadata();
+    AppMetadata(const AppMetadata& other);
+    ~AppMetadata();
+
+    scoped_refptr<const extensions::Extension> pwa;
+    bool is_cookie_present = true;
+  };
+
+  // Returns null if no app has been installed at |url|.
+  const AppMetadata* GetAppMetadataAtUrl(const GURL& url) const;
+
+  // If |id_for_app| is provided, this function installs an app with the given
+  // ID at |ur|. Otherwise, this function removes any existing app at that URL.
+  void SetAppAtUrl(const GURL& url,
+                   const base::Optional<extensions::ExtensionId>& id_for_app);
+
+  // Completes a pending setup request (i.e., a previous call to SetUpApp()).
+  // If |id_for_app| is set, the request is successful and the installed app
+  // will have the provided ID; if |id_for_app| is null, the request fails.
+  void CompletePendingSetUpAppRequest(
+      const GURL& expected_url,
+      const base::Optional<extensions::ExtensionId>& id_for_app);
+
+  // Completes a pending cookie deletion request (i.e., a previous call to
+  // DeleteRememberDeviceByDefaultCookie()).
+  void CompletePendingDeleteCookieRequest(const GURL& expected_url);
+
+  // Completes a pending app removal request (i.e., a previous call to
+  // RemoveApp()). If |success| is true, the app will be removed; otherwise, the
+  // app will remain in place.
+  void CompleteRemoveAppRequest(const GURL& expected_url, bool success);
+
+ private:
+  // AndroidSmsAppSetupController:
+  void SetUpApp(const GURL& url, SuccessCallback callback) override;
+  const extensions::Extension* GetPwa(const GURL& url) override;
+  void DeleteRememberDeviceByDefaultCookie(const GURL& url,
+                                           SuccessCallback callback) override;
+  void RemoveApp(const GURL& url, SuccessCallback callback) override;
+
+  using RequestData = std::pair<GURL, SuccessCallback>;
+
+  std::list<std::unique_ptr<RequestData>> pending_set_up_app_requests_;
+  std::list<std::unique_ptr<RequestData>> pending_delete_cookie_requests_;
+  std::list<std::unique_ptr<RequestData>> pending_remove_app_requests_;
+
+  base::flat_map<GURL, AppMetadata> url_to_metadata_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeAndroidSmsAppSetupController);
+};
+
+}  // namespace android_sms
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_ANDROID_SMS_FAKE_ANDROID_SMS_APP_SETUP_CONTROLLER_H_
diff --git a/chrome/browser/chromeos/dbus/proxy_resolution_service_provider.cc b/chrome/browser/chromeos/dbus/proxy_resolution_service_provider.cc
index e1ebb04..eac39c6 100644
--- a/chrome/browser/chromeos/dbus/proxy_resolution_service_provider.cc
+++ b/chrome/browser/chromeos/dbus/proxy_resolution_service_provider.cc
@@ -29,25 +29,25 @@
 struct ProxyResolutionServiceProvider::Request {
  public:
   Request(const std::string& source_url,
-          std::unique_ptr<dbus::Response> response,
-          const dbus::ExportedObject::ResponseSender& response_sender,
-          scoped_refptr<net::URLRequestContextGetter> context_getter)
+          scoped_refptr<net::URLRequestContextGetter> context_getter,
+          scoped_refptr<base::SingleThreadTaskRunner> notify_thread,
+          NotifyCallback notify_callback)
       : source_url(source_url),
-        response(std::move(response)),
-        response_sender(response_sender),
-        context_getter(context_getter) {
-    DCHECK(this->response);
-    DCHECK(!response_sender.is_null());
-  }
+        context_getter(context_getter),
+        notify_thread(std::move(notify_thread)),
+        notify_callback(std::move(notify_callback)) {}
   ~Request() = default;
 
+  // Invokes |notify_callback| on |notify_thread|.
+  void PostNotify(const std::string& error, const std::string& pac_string) {
+    notify_thread->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(notify_callback), error, pac_string));
+  }
+
   // URL being resolved.
   const std::string source_url;
 
-  // D-Bus response and callback for returning data on resolution completion.
-  std::unique_ptr<dbus::Response> response;
-  const dbus::ExportedObject::ResponseSender response_sender;
-
   // Used to get the network context associated with the profile used to run
   // this request.
   const scoped_refptr<net::URLRequestContextGetter> context_getter;
@@ -58,8 +58,10 @@
   // ProxyInfo resolved for |source_url|.
   net::ProxyInfo proxy_info;
 
-  // Error from proxy resolution.
-  std::string error;
+  // The callback to invoke on completion, as well as the thread it should be
+  // invoked on.
+  scoped_refptr<base::SingleThreadTaskRunner> notify_thread;
+  NotifyCallback notify_callback;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(Request);
@@ -80,10 +82,10 @@
   VLOG(1) << "ProxyResolutionServiceProvider started";
   exported_object_->ExportMethod(
       kNetworkProxyServiceInterface, kNetworkProxyServiceResolveProxyMethod,
-      base::Bind(&ProxyResolutionServiceProvider::ResolveProxy,
-                 weak_ptr_factory_.GetWeakPtr()),
-      base::Bind(&ProxyResolutionServiceProvider::OnExported,
-                 weak_ptr_factory_.GetWeakPtr()));
+      base::BindRepeating(&ProxyResolutionServiceProvider::DbusResolveProxy,
+                          weak_ptr_factory_.GetWeakPtr()),
+      base::BindRepeating(&ProxyResolutionServiceProvider::OnExported,
+                          weak_ptr_factory_.GetWeakPtr()));
 }
 
 bool ProxyResolutionServiceProvider::OnOriginThread() {
@@ -100,7 +102,7 @@
     LOG(ERROR) << "Failed to export " << interface_name << "." << method_name;
 }
 
-void ProxyResolutionServiceProvider::ResolveProxy(
+void ProxyResolutionServiceProvider::DbusResolveProxy(
     dbus::MethodCall* method_call,
     dbus::ExportedObject::ResponseSender response_sender) {
   DCHECK(OnOriginThread());
@@ -117,33 +119,41 @@
 
   std::unique_ptr<dbus::Response> response =
       dbus::Response::FromMethodCall(method_call);
+
+  NotifyCallback notify_dbus_callback =
+      base::BindOnce(&ProxyResolutionServiceProvider::NotifyProxyResolved,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(response),
+                     std::move(response_sender));
+
+  ResolveProxyInternal(source_url, std::move(notify_dbus_callback));
+}
+
+void ProxyResolutionServiceProvider::ResolveProxyInternal(
+    const std::string& source_url,
+    NotifyCallback callback) {
   scoped_refptr<net::URLRequestContextGetter> context_getter =
       request_context_getter_for_test_
           ? request_context_getter_for_test_
           : ProfileManager::GetPrimaryUserProfile()->GetRequestContext();
 
   std::unique_ptr<Request> request = std::make_unique<Request>(
-      source_url, std::move(response), response_sender, context_getter);
-  NotifyCallback notify_callback =
-      base::Bind(&ProxyResolutionServiceProvider::NotifyProxyResolved,
-                 weak_ptr_factory_.GetWeakPtr());
+      source_url, context_getter, origin_thread_, std::move(callback));
 
   // This would ideally call PostTaskAndReply() instead of PostTask(), but
   // ResolveProxyOnNetworkThread()'s call to
-  // net::ProxyResolutionService::ResolveProxy() can result in an asynchronous
-  // lookup, in which case the result won't be available immediately.
+  // net::ProxyResolutionService::DbusResolveProxy() can result in an
+  // asynchronous lookup, in which case the result won't be available
+  // immediately.
   context_getter->GetNetworkTaskRunner()->PostTask(
       FROM_HERE,
       base::BindOnce(
           &ProxyResolutionServiceProvider::ResolveProxyOnNetworkThread,
-          std::move(request), origin_thread_, notify_callback));
+          std::move(request)));
 }
 
 // static
 void ProxyResolutionServiceProvider::ResolveProxyOnNetworkThread(
-    std::unique_ptr<Request> request,
-    scoped_refptr<base::SingleThreadTaskRunner> notify_thread,
-    NotifyCallback notify_callback) {
+    std::unique_ptr<Request> request) {
   DCHECK(request->context_getter->GetNetworkTaskRunner()
              ->BelongsToCurrentThread());
 
@@ -151,16 +161,14 @@
       request->context_getter->GetURLRequestContext()
           ->proxy_resolution_service();
   if (!proxy_resolution_service) {
-    request->error = "No proxy service in chrome";
-    OnResolutionComplete(std::move(request), notify_thread, notify_callback,
-                         net::ERR_UNEXPECTED);
+    request->PostNotify("No proxy service in chrome", "DIRECT");
     return;
   }
 
   Request* request_ptr = request.get();
-  net::CompletionCallback callback = base::Bind(
-      &ProxyResolutionServiceProvider::OnResolutionComplete,
-      base::Passed(std::move(request)), notify_thread, notify_callback);
+  net::CompletionCallback callback =
+      base::BindRepeating(&ProxyResolutionServiceProvider::OnResolutionComplete,
+                          base::Passed(std::move(request)));
 
   VLOG(1) << "Starting network proxy resolution for "
           << request_ptr->source_url;
@@ -176,28 +184,29 @@
 // static
 void ProxyResolutionServiceProvider::OnResolutionComplete(
     std::unique_ptr<Request> request,
-    scoped_refptr<base::SingleThreadTaskRunner> notify_thread,
-    NotifyCallback notify_callback,
     int result) {
   DCHECK(request->context_getter->GetNetworkTaskRunner()
              ->BelongsToCurrentThread());
 
-  if (request->error.empty() && result != net::OK)
-    request->error = net::ErrorToString(result);
+  request->request.reset();
 
-  notify_thread->PostTask(FROM_HERE,
-                          base::BindOnce(notify_callback, std::move(request)));
+  request->PostNotify(
+      result == net::OK ? "" : net::ErrorToString(result),
+      result == net::OK ? request->proxy_info.ToPacString() : "DIRECT");
 }
 
 void ProxyResolutionServiceProvider::NotifyProxyResolved(
-    std::unique_ptr<Request> request) {
+    std::unique_ptr<dbus::Response> response,
+    dbus::ExportedObject::ResponseSender response_sender,
+    const std::string& error,
+    const std::string& pac_string) {
   DCHECK(OnOriginThread());
 
   // Reply to the original D-Bus method call.
-  dbus::MessageWriter writer(request->response.get());
-  writer.AppendString(request->proxy_info.ToPacString());
-  writer.AppendString(request->error);
-  request->response_sender.Run(std::move(request->response));
+  dbus::MessageWriter writer(response.get());
+  writer.AppendString(pac_string);
+  writer.AppendString(error);
+  response_sender.Run(std::move(response));
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/dbus/proxy_resolution_service_provider.h b/chrome/browser/chromeos/dbus/proxy_resolution_service_provider.h
index 8516cef..cfa9181 100644
--- a/chrome/browser/chromeos/dbus/proxy_resolution_service_provider.h
+++ b/chrome/browser/chromeos/dbus/proxy_resolution_service_provider.h
@@ -13,7 +13,6 @@
 #include "base/memory/weak_ptr.h"
 #include "chromeos/dbus/services/cros_dbus_service.h"
 #include "dbus/exported_object.h"
-#include "net/base/completion_callback.h"
 
 namespace base {
 class SingleThreadTaskRunner;
@@ -71,6 +70,15 @@
   // Data used for a single proxy resolution.
   struct Request;
 
+  // Callback that is invoked with the result of proxy resolution. On success
+  // |error| is empty, and |pac_string| contains the result. Otherwise |error|
+  // is non-empty.
+  using NotifyCallback =
+      base::OnceCallback<void(const std::string& error,
+                              const std::string& pac_string)>;
+
+  friend class ProxyResolutionServiceProviderTestWrapper;
+
   // Returns true if called on |origin_thread_|.
   bool OnOriginThread();
 
@@ -81,35 +89,27 @@
 
   // Callback invoked when Chrome OS clients send network proxy resolution
   // requests to the service. Called on UI thread.
-  void ResolveProxy(dbus::MethodCall* method_call,
-                    dbus::ExportedObject::ResponseSender response_sender);
+  void DbusResolveProxy(dbus::MethodCall* method_call,
+                        dbus::ExportedObject::ResponseSender response_sender);
 
-  // Callback passed to network thread static methods to run
-  // NotifyProxyResolved() on |origin_thread_|. This callback can be bound to a
-  // WeakPtr from |weak_ptr_factory_| (since the pointer will be dereferenced on
-  // |origin_thread_|), but the network methods can't (since WeakPtr disallows
-  // use on threads besides the one where it was created) and are static as a
-  // result.
-  using NotifyCallback = base::Callback<void(std::unique_ptr<Request>)>;
+  void ResolveProxyInternal(const std::string& source_url,
+                            NotifyCallback callback);
 
   // Helper method for ResolveProxy() that runs on network thread.
-  static void ResolveProxyOnNetworkThread(
-      std::unique_ptr<Request> request,
-      scoped_refptr<base::SingleThreadTaskRunner> notify_thread,
-      NotifyCallback notify_callback);
+  static void ResolveProxyOnNetworkThread(std::unique_ptr<Request> request);
 
   // Callback on network thread for when
   // net::ProxyResolutionService::ResolveProxy() completes, synchronously or
   // asynchronously.
-  static void OnResolutionComplete(
-      std::unique_ptr<Request> request,
-      scoped_refptr<base::SingleThreadTaskRunner> notify_thread,
-      NotifyCallback notify_callback,
-      int result);
+  static void OnResolutionComplete(std::unique_ptr<Request> request,
+                                   int result);
 
   // Called on UI thread from OnResolutionComplete() to pass the resolved proxy
   // information to the client over D-Bus.
-  void NotifyProxyResolved(std::unique_ptr<Request> request);
+  void NotifyProxyResolved(std::unique_ptr<dbus::Response> response,
+                           dbus::ExportedObject::ResponseSender response_sender,
+                           const std::string& error,
+                           const std::string& pac_string);
 
   scoped_refptr<dbus::ExportedObject> exported_object_;
   scoped_refptr<base::SingleThreadTaskRunner> origin_thread_;
diff --git a/chrome/browser/chromeos/dbus/proxy_resolution_service_provider_browsertest.cc b/chrome/browser/chromeos/dbus/proxy_resolution_service_provider_browsertest.cc
new file mode 100644
index 0000000..914de97c
--- /dev/null
+++ b/chrome/browser/chromeos/dbus/proxy_resolution_service_provider_browsertest.cc
@@ -0,0 +1,144 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "chrome/browser/chromeos/dbus/proxy_resolution_service_provider.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/content_switches.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace chromeos {
+
+// Helper for calling ProxyResolutionServiceProvider's |ResolveProxyInternal()|
+// method. Unlike the unit-tests which mock the network setup, this uses the
+// default dependencies from the running browser.
+class ProxyResolutionServiceProviderTestWrapper {
+ public:
+  ProxyResolutionServiceProviderTestWrapper() {
+    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  }
+
+  ~ProxyResolutionServiceProviderTestWrapper() {
+    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  }
+
+  // Calls ResolveProxyInternal() and returns its result synchronously as a
+  // single string (which may be prefixed by "ERROR: " if it is an error message
+  // as opposed to a proxy result).
+  std::string ResolveProxyAndWait(const std::string& url) {
+    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+    base::RunLoop run_loop;
+
+    std::string result;
+    impl_.ResolveProxyInternal(
+        url,
+        base::BindOnce(
+            &ProxyResolutionServiceProviderTestWrapper::OnResolveProxyComplete,
+            &result, run_loop.QuitClosure()));
+
+    run_loop.Run();
+
+    return result;
+  }
+
+ private:
+  static void OnResolveProxyComplete(std::string* result,
+                                     base::RepeatingClosure quit_closure,
+                                     const std::string& error,
+                                     const std::string& pac_string) {
+    if (!error.empty()) {
+      *result = "ERROR: " + error;
+    } else {
+      *result = pac_string;
+    }
+
+    std::move(quit_closure).Run();
+  }
+
+  ProxyResolutionServiceProvider impl_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProxyResolutionServiceProviderTestWrapper);
+};
+
+// Base test fixture that exposes a way to invoke ProxyResolutionServiceProvider
+// synchronously from the UI thread.
+class ProxyResolutionServiceProviderBaseBrowserTest
+    : public InProcessBrowserTest {
+ public:
+  void SetUpOnMainThread() override {
+    InProcessBrowserTest::SetUpOnMainThread();
+    proxy_service_ =
+        std::make_unique<ProxyResolutionServiceProviderTestWrapper>();
+  }
+
+  void TearDownOnMainThread() override {
+    proxy_service_.reset();
+    InProcessBrowserTest::TearDownOnMainThread();
+  }
+
+  std::string ResolveProxyAndWait(const std::string& source_url) {
+    return proxy_service_->ResolveProxyAndWait(source_url);
+  }
+
+ private:
+  std::unique_ptr<ProxyResolutionServiceProviderTestWrapper> proxy_service_;
+};
+
+// Fixture that launches the browser with --proxy-server="https://proxy.test".
+class ProxyResolutionServiceProviderManualProxyBrowserTest
+    : public ProxyResolutionServiceProviderBaseBrowserTest {
+ public:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitchASCII(switches::kProxyServer,
+                                    "https://proxy.test");
+  }
+};
+
+// Tests that the D-Bus proxy resolver returns the correct result when using
+// --proxy-server flag. These resolutions will happen synchronously at the //net
+// layer.
+IN_PROC_BROWSER_TEST_F(ProxyResolutionServiceProviderManualProxyBrowserTest,
+                       ResolveProxy) {
+  EXPECT_EQ("HTTPS proxy.test:443",
+            ResolveProxyAndWait("http://www.google.com"));
+}
+
+// Simple PAC script that returns the same two proxies for all requests.
+const char kPacData[] =
+    "function FindProxyForURL(url, host) {\n"
+    "  return 'PROXY foo1; PROXY foo2';\n"
+    "}\n";
+
+// Fixture that launches the browser with --proxy-pac-url="data:...".
+class ProxyResolutionServiceProviderPacBrowserTest
+    : public ProxyResolutionServiceProviderBaseBrowserTest {
+ public:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitchASCII(switches::kProxyPacUrl, GetPacUrl());
+  }
+
+ private:
+  // Encode the PAC script as a data: URL.
+  static std::string GetPacUrl() {
+    std::string b64_encoded;
+    base::Base64Encode(kPacData, &b64_encoded);
+    return "data:application/x-javascript-config;base64," + b64_encoded;
+  }
+};
+
+// Tests that the D-Bus proxy resolver returns the correct result when using
+// --proxy-pac-url flag. These resolutions will happen asynchronously at the
+// //net layer, as they need to query a PAC script.
+IN_PROC_BROWSER_TEST_F(ProxyResolutionServiceProviderPacBrowserTest,
+                       ResolveProxy) {
+  EXPECT_EQ("PROXY foo1:80;PROXY foo2:80",
+            ResolveProxyAndWait("http://www.google.com"));
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/users/avatar/user_image_manager_browsertest.cc b/chrome/browser/chromeos/login/users/avatar/user_image_manager_browsertest.cc
index 45e590c..9c09df4 100644
--- a/chrome/browser/chromeos/login/users/avatar/user_image_manager_browsertest.cc
+++ b/chrome/browser/chromeos/login/users/avatar/user_image_manager_browsertest.cc
@@ -62,15 +62,12 @@
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
-#include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_image/user_image.h"
 #include "components/user_manager/user_manager.h"
 #include "crypto/rsa_private_key.h"
-#include "google_apis/gaia/gaia_oauth_client.h"
 #include "google_apis/gaia/gaia_urls.h"
-#include "google_apis/gaia/oauth2_token_service.h"
 #include "net/test/embedded_test_server/controllable_http_response.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
index 3fea3a1..1379f8ec 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
+++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
@@ -370,7 +370,6 @@
 
   autofill_util::AddressEntryList address_list =
       autofill_util::GenerateAddressList(*personal_data);
-  base::RecordAction(base::UserMetricsAction("AutofillAddressesViewed"));
   return RespondNow(ArgumentList(
       api::autofill_private::GetAddressList::Results::Create(address_list)));
 }
@@ -539,7 +538,6 @@
 
   autofill_util::CreditCardEntryList credit_card_list =
       autofill_util::GenerateCreditCardList(*personal_data);
-  base::RecordAction(base::UserMetricsAction("AutofillCreditCardsViewed"));
   return RespondNow(
       ArgumentList(api::autofill_private::GetCreditCardList::Results::Create(
           credit_card_list)));
diff --git a/chrome/browser/extensions/api/cast_streaming/performance_test.cc b/chrome/browser/extensions/api/cast_streaming/performance_test.cc
index 2d218e5..73d4dea 100644
--- a/chrome/browser/extensions/api/cast_streaming/performance_test.cc
+++ b/chrome/browser/extensions/api/cast_streaming/performance_test.cc
@@ -7,27 +7,28 @@
 
 #include <algorithm>
 #include <cmath>
+#include <cstring>
 #include <iterator>
 #include <map>
 #include <vector>
 
+#include "base/base64.h"
 #include "base/command_line.h"
-#include "base/macros.h"
-#include "base/strings/string_number_conversions.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/strings/strcat.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/trace_event_analyzer.h"
 #include "base/time/default_tick_clock.h"
+#include "base/values.h"
 #include "build/build_config.h"
-#include "chrome/browser/extensions/extension_apitest.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/tab_helper.h"
+#include "chrome/browser/extensions/api/tab_capture/tab_capture_performance_test_base.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_switches.h"
-#include "chrome/test/base/test_launcher_utils.h"
-#include "chrome/test/base/test_switches.h"
 #include "chrome/test/base/tracing.h"
 #include "content/public/common/content_switches.h"
-#include "extensions/common/switches.h"
-#include "extensions/test/extension_test_message_listener.h"
+#include "content/public/test/browser_test_utils.h"
 #include "media/base/audio_bus.h"
 #include "media/base/video_frame.h"
 #include "media/cast/test/skewed_tick_clock.h"
@@ -44,19 +45,10 @@
 #include "net/base/rand_callback.h"
 #include "net/log/net_log_source.h"
 #include "net/socket/udp_server_socket.h"
-#include "testing/gtest/include/gtest/gtest.h"
 #include "testing/perf/perf_test.h"
-#include "ui/compositor/compositor_switches.h"
-#include "ui/gl/gl_switches.h"
-
-#if defined(OS_WIN)
-#include "base/win/windows_version.h"
-#endif
 
 namespace {
 
-constexpr char kExtensionId[] = "ddchlicdkolnonkihahngkmmmjnjlkkf";
-
 // Number of events to trim from the begining and end. These events don't
 // contribute anything toward stable measurements: A brief moment of startup
 // "jank" is acceptable, and shutdown may result in missing events (e.g., if
@@ -67,9 +59,6 @@
 constexpr size_t kMinDataPoints = 100;  // 1 sec of audio, or ~5 sec at 24fps.
 
 enum TestFlags {
-  kUseGpu = 1 << 0,           // Only execute test if --enable-gpu was given
-                              // on the command line.  This is required for
-                              // tests that run on GPU.
   kSmallWindow = 1 << 2,      // Window size: 1 = 800x600, 0 = 2000x1000
   k24fps = 1 << 3,            // Use 24 fps video.
   k30fps = 1 << 4,            // Use 30 fps video.
@@ -350,25 +339,21 @@
   DISALLOW_COPY_AND_ASSIGN(TestPatternReceiver);
 };
 
-class CastV2PerformanceTest : public extensions::ExtensionApiTest,
+class CastV2PerformanceTest : public TabCapturePerformanceTestBase,
                               public testing::WithParamInterface<int> {
  public:
-  CastV2PerformanceTest() { LOG(ERROR) << __func__ << ": Hello!"; }
-
-  ~CastV2PerformanceTest() override { LOG(ERROR) << __func__ << ": Goodbye!"; }
+  CastV2PerformanceTest() = default;
+  ~CastV2PerformanceTest() override = default;
 
   bool HasFlag(TestFlags flag) const {
     return (GetParam() & flag) == flag;
   }
 
-  bool IsGpuAvailable() const {
-    return base::CommandLine::ForCurrentProcess()->HasSwitch("enable-gpu");
-  }
-
-  std::string GetSuffixForTestFlags() {
+  std::string GetSuffixForTestFlags() const {
     std::string suffix;
-    if (HasFlag(kUseGpu))
-      suffix += "_gpu";
+    // Note: Add "_gpu" tag for backwards-compatibility with existing
+    // Performance Dashboard timeseries data.
+    suffix += "_gpu";
     if (HasFlag(kSmallWindow))
       suffix += "_small";
     if (HasFlag(k24fps))
@@ -392,7 +377,7 @@
     return suffix;
   }
 
-  int getfps() {
+  int get_fps() const {
     if (HasFlag(k24fps))
       return 24;
     if (HasFlag(k30fps))
@@ -404,45 +389,38 @@
   }
 
   void SetUp() override {
-    LOG(ERROR) << __func__ << ": Starting...";
-    EnablePixelOutput();
-    if (!HasFlag(kUseGpu))
-      UseSoftwareCompositing();
-    LOG(ERROR) << __func__ << ": Doing normal SetUp()...";
-    extensions::ExtensionApiTest::SetUp();
-    LOG(ERROR) << __func__ << ": Completed.";
-  }
+    // Produce the full HTML test page with the barcode video embedded within
+    // (as a data URI).
+    const base::FilePath video_file =
+        GetApiTestDataDir()
+            .AppendASCII("cast_streaming")
+            .AppendASCII(
+                base::StringPrintf("test_video_%dfps.webm", get_fps()));
+    std::string file_contents;
+    const bool success = base::ReadFileToString(video_file, &file_contents);
+    CHECK(success) << "Failed to load video at: " << video_file.AsUTF8Unsafe();
+    std::string video_in_base64;
+    base::Base64Encode(file_contents, &video_in_base64);
+    test_page_html_ =
+        base::StrCat({"<html><body>\n"
+                      "<video width='100%' height='100%'>\n"
+                      "  <source src='data:video/webm;base64,",
+                      video_in_base64,
+                      "'>\n"
+                      "</video>\n"
+                      "</body></html>"});
 
-  void SetUpOnMainThread() override {
-    LOG(ERROR) << __func__ << ": Doing normal SetUpOnMainThread()...";
-    extensions::ExtensionApiTest::SetUpOnMainThread();
-    LOG(ERROR) << __func__ << ": Completed.";
+    TabCapturePerformanceTestBase::SetUp();
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    LOG(ERROR) << __func__ << ": Starting...";
-    // Some of the tests may launch http requests through JSON or AJAX
-    // which causes a security error (cross domain request) when the page
-    // is loaded from the local file system ( file:// ). The following switch
-    // fixes that error.
-    command_line->AppendSwitch(switches::kAllowFileAccessFromFiles);
-
     if (HasFlag(kSmallWindow)) {
       command_line->AppendSwitchASCII(switches::kWindowSize, "800,600");
     } else {
       command_line->AppendSwitchASCII(switches::kWindowSize, "2000,1500");
     }
 
-    if (!HasFlag(kUseGpu))
-      command_line->AppendSwitch(switches::kDisableGpu);
-
-    command_line->AppendSwitchASCII(
-        extensions::switches::kWhitelistedExtensionID,
-        kExtensionId);
-
-    LOG(ERROR) << __func__ << ": Doing normal SetUpCommandLine()...";
-    extensions::ExtensionApiTest::SetUpCommandLine(command_line);
-    LOG(ERROR) << __func__ << ": Completed.";
+    TabCapturePerformanceTestBase::SetUpCommandLine(command_line);
   }
 
   void GetTraceEvents(trace_analyzer::TraceAnalyzer* analyzer,
@@ -623,128 +601,139 @@
     return MeanAndError(deltas);
   }
 
-  void RunTest(const std::string& test_name) {
-    LOG(ERROR) << __func__ << ": Starting...";
+ protected:
+  // The complete HTML test web page without any external dependencies,
+  // including the entire barcode video as an embedded data URI. Populated in
+  // SetUp().
+  std::string test_page_html_;
 
-    if (HasFlag(kUseGpu) && !IsGpuAvailable()) {
-      LOG(WARNING) <<
-          "Test skipped: requires gpu. Pass --enable-gpu on the command "
-          "line if use of GPU is desired.";
-      return;
-    }
+  // While the source video frame rate may vary (24, 30, or 60 FPS), the maximum
+  // capture frame rate is always fixed at 30 FPS. This allows testing of the
+  // entire system when it is forced to perform a 60→30 frame rate conversion.
+  static constexpr int kMaxCaptureFrameRate = 30;
 
-    ASSERT_EQ(1,
-              (HasFlag(k24fps) ? 1 : 0) +
-              (HasFlag(k30fps) ? 1 : 0) +
-              (HasFlag(k60fps) ? 1 : 0));
-
-    net::IPEndPoint receiver_end_point = media::cast::test::GetFreeLocalPort();
-    LOG(ERROR) << __func__ << ": Got local UDP port for testing: "
-               << receiver_end_point.ToString();
-
-    // Start the in-process receiver that examines audio/video for the expected
-    // test patterns.
-    base::TimeDelta delta = base::TimeDelta::FromSeconds(0);
-    if (HasFlag(kFastClock)) {
-      delta = base::TimeDelta::FromSeconds(10);
-    }
-    if (HasFlag(kSlowClock)) {
-      delta = base::TimeDelta::FromSeconds(-10);
-    }
-    scoped_refptr<media::cast::StandaloneCastEnvironment> cast_environment(
-        new SkewedCastEnvironment(delta));
-    TestPatternReceiver* const receiver =
-        new TestPatternReceiver(cast_environment, receiver_end_point);
-    LOG(ERROR) << __func__ << ": Starting receiver...";
-    receiver->Start();
-
-    LOG(ERROR) << __func__ << ": Creating UDPProxy...";
-    std::unique_ptr<media::cast::test::UDPProxy> udp_proxy;
-    if (HasFlag(kProxyWifi) || HasFlag(kProxySlow) || HasFlag(kProxyBad)) {
-      net::IPEndPoint proxy_end_point = media::cast::test::GetFreeLocalPort();
-      if (HasFlag(kProxyWifi)) {
-        udp_proxy = media::cast::test::UDPProxy::Create(
-            proxy_end_point, receiver_end_point,
-            media::cast::test::WifiNetwork(), media::cast::test::WifiNetwork(),
-            nullptr);
-      } else if (HasFlag(kProxySlow)) {
-        udp_proxy = media::cast::test::UDPProxy::Create(
-            proxy_end_point, receiver_end_point,
-            media::cast::test::SlowNetwork(), media::cast::test::SlowNetwork(),
-            nullptr);
-      } else if (HasFlag(kProxyBad)) {
-        udp_proxy = media::cast::test::UDPProxy::Create(
-            proxy_end_point, receiver_end_point,
-            media::cast::test::BadNetwork(), media::cast::test::BadNetwork(),
-            nullptr);
-      }
-      receiver_end_point = proxy_end_point;
-    }
-
-    LOG(ERROR) << __func__ << ": Starting tracing...";
-    std::string json_events;
-    ASSERT_TRUE(tracing::BeginTracing("gpu.capture,cast_perf_test"));
-    const std::string page_url = base::StringPrintf(
-        "performance%d.html?port=%d&autoThrottling=%s&aesKey=%s&aesIvMask=%s",
-        getfps(), receiver_end_point.port(),
-        HasFlag(kAutoThrottling) ? "true" : "false",
-        base::HexEncode(kAesKey, sizeof(kAesKey)).c_str(),
-        base::HexEncode(kAesIvMask, sizeof(kAesIvMask)).c_str());
-    LOG(ERROR) << __func__ << ": Running extension subtest...";
-    ASSERT_TRUE(RunExtensionSubtest("cast_streaming", page_url)) << message_;
-    LOG(ERROR) << __func__ << ": Extension subtest finished. Ending tracing...";
-    ASSERT_TRUE(tracing::EndTracing(&json_events));
-    LOG(ERROR) << __func__ << ": Stopping receiver...";
-    receiver->Stop();
-
-    // Stop all threads, removes the need for synchronization when analyzing
-    // the data.
-    LOG(ERROR) << __func__ << ": Shutting-down CastEnvironment...";
-    cast_environment->Shutdown();
-    LOG(ERROR) << __func__ << ": Analyzing...";
-    std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer;
-    analyzer.reset(trace_analyzer::TraceAnalyzer::Create(json_events));
-    analyzer->AssociateAsyncBeginEndEvents();
-
-    // This prints out the average time between capture events.
-    // Depending on the test, the capture frame rate is capped (e.g., at 30fps,
-    // this score cannot get any better than 33.33 ms). However, the measurement
-    // is important since it provides a valuable check that capture can keep up
-    // with the content's framerate.
-    MeanAndError capture_data = AnalyzeTraceDistance(analyzer.get(), "Capture");
-    // Lower is better.
-    capture_data.Print(test_name,
-                       GetSuffixForTestFlags(),
-                       "time_between_captures",
-                       "ms");
-
-    receiver->Analyze(test_name, GetSuffixForTestFlags());
-
-    AnalyzeLatency(test_name, analyzer.get());
-    LOG(ERROR) << __func__ << ": Completed.";
-  }
+  // Naming of performance measurement written to stdout.
+  static const char kTestName[];
 };
 
+// static
+const char CastV2PerformanceTest::kTestName[] = "CastV2Performance";
+
 }  // namespace
 
 IN_PROC_BROWSER_TEST_P(CastV2PerformanceTest, Performance) {
-  LOG(ERROR) << __func__ << ": Test procedure started.";
-  RunTest("CastV2Performance");
-  LOG(ERROR) << __func__ << ": Completed.";
+  net::IPEndPoint receiver_end_point = media::cast::test::GetFreeLocalPort();
+  VLOG(1) << "Got local UDP port for testing: "
+          << receiver_end_point.ToString();
+
+  // Start the in-process receiver that examines audio/video for the expected
+  // test patterns.
+  base::TimeDelta delta = base::TimeDelta::FromSeconds(0);
+  if (HasFlag(kFastClock)) {
+    delta = base::TimeDelta::FromSeconds(10);
+  }
+  if (HasFlag(kSlowClock)) {
+    delta = base::TimeDelta::FromSeconds(-10);
+  }
+  scoped_refptr<media::cast::StandaloneCastEnvironment> cast_environment(
+      new SkewedCastEnvironment(delta));
+  TestPatternReceiver* const receiver =
+      new TestPatternReceiver(cast_environment, receiver_end_point);
+  receiver->Start();
+
+  // Create a proxy for the UDP packets that simulates certain network
+  // environments.
+  std::unique_ptr<media::cast::test::UDPProxy> udp_proxy;
+  if (HasFlag(kProxyWifi) || HasFlag(kProxySlow) || HasFlag(kProxyBad)) {
+    net::IPEndPoint proxy_end_point = media::cast::test::GetFreeLocalPort();
+    if (HasFlag(kProxyWifi)) {
+      udp_proxy = media::cast::test::UDPProxy::Create(
+          proxy_end_point, receiver_end_point, media::cast::test::WifiNetwork(),
+          media::cast::test::WifiNetwork(), nullptr);
+    } else if (HasFlag(kProxySlow)) {
+      udp_proxy = media::cast::test::UDPProxy::Create(
+          proxy_end_point, receiver_end_point, media::cast::test::SlowNetwork(),
+          media::cast::test::SlowNetwork(), nullptr);
+    } else if (HasFlag(kProxyBad)) {
+      udp_proxy = media::cast::test::UDPProxy::Create(
+          proxy_end_point, receiver_end_point, media::cast::test::BadNetwork(),
+          media::cast::test::BadNetwork(), nullptr);
+    }
+    receiver_end_point = proxy_end_point;
+  }
+
+  // Load the extension and test page, and tell the extension to start tab
+  // capture + Cast Streaming.
+  LoadExtension(GetApiTestDataDir()
+                    .AppendASCII("cast_streaming")
+                    .AppendASCII("perftest_extension"));
+  NavigateToTestPage(test_page_html_);
+  const base::Value response = SendMessageToExtension(base::StringPrintf(
+      "{start:true, enableAutoThrottling:%s, maxFrameRate:%d, recvPort:%d,"
+      " aesKey:'%s', aesIvMask:'%s'}",
+      HasFlag(kAutoThrottling) ? "true" : "false", kMaxCaptureFrameRate,
+      receiver_end_point.port(),
+      base::HexEncode(kAesKey, sizeof(kAesKey)).c_str(),
+      base::HexEncode(kAesIvMask, sizeof(kAesIvMask)).c_str()));
+  const std::string* reason = response.FindStringKey("reason");
+  ASSERT_TRUE(response.FindBoolKey("success").value_or(false))
+      << (reason ? *reason : std::string("<MISSING REASON>"));
+
+  // Now that capture has started, start playing the barcode video in the test
+  // page.
+  const std::string javascript_to_play_video(
+      "new Promise((resolve) => {\n"
+      "  const video = document.getElementsByTagName('video')[0];\n"
+      "  video.addEventListener('playing', () => { resolve(true); });\n"
+      "  video.play();\n"
+      "})");
+  LOG(INFO) << "Starting playback of barcode video...";
+  ASSERT_EQ(true, content::EvalJs(
+                      browser()->tab_strip_model()->GetActiveWebContents(),
+                      javascript_to_play_video));
+
+  // Observe the running browser for a while, collecting a trace.
+  const std::string json_events = TraceAndObserve("gpu.capture,cast_perf_test");
+
+  // Shut down the receiver and all the CastEnvironment threads.
+  VLOG(1) << "Shutting-down receiver and CastEnvironment...";
+  receiver->Stop();
+  cast_environment->Shutdown();
+
+  VLOG(2) << "Dump of trace events (trace_events.json.gz.b64):\n"
+          << MakeBase64EncodedGZippedString(json_events);
+
+  VLOG(1) << "Analyzing trace events...";
+  std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer;
+  analyzer.reset(trace_analyzer::TraceAnalyzer::Create(json_events));
+  analyzer->AssociateAsyncBeginEndEvents();
+
+  // This prints out the average time between capture events.
+  // Depending on the test, the capture frame rate is capped (e.g., at 30fps,
+  // this score cannot get any better than 33.33 ms). However, the measurement
+  // is important since it provides a valuable check that capture can keep up
+  // with the content's framerate.
+  MeanAndError capture_data = AnalyzeTraceDistance(analyzer.get(), "Capture");
+  // Lower is better.
+  capture_data.Print(kTestName, GetSuffixForTestFlags(),
+                     "time_between_captures", "ms");
+
+  receiver->Analyze(kTestName, GetSuffixForTestFlags());
+
+  AnalyzeLatency(kTestName, analyzer.get());
 }
 
 // Note: First argument is optional and intentionally left blank.
 // (it's a prefix for the generated test cases)
-INSTANTIATE_TEST_CASE_P(
-    ,
-    CastV2PerformanceTest,
-    testing::Values(kUseGpu | k24fps,
-                    kUseGpu | k30fps,
-                    kUseGpu | k60fps,
-                    kUseGpu | k30fps | kProxyWifi,
-                    kUseGpu | k30fps | kProxyBad,
-                    kUseGpu | k30fps | kSlowClock,
-                    kUseGpu | k30fps | kFastClock,
-                    kUseGpu | k30fps | kProxyWifi | kAutoThrottling,
-                    kUseGpu | k30fps | kProxySlow | kAutoThrottling,
-                    kUseGpu | k30fps | kProxyBad | kAutoThrottling));
+INSTANTIATE_TEST_CASE_P(,
+                        CastV2PerformanceTest,
+                        testing::Values(k24fps,
+                                        k30fps,
+                                        k60fps,
+                                        k30fps | kProxyWifi,
+                                        k30fps | kProxyBad,
+                                        k30fps | kSlowClock,
+                                        k30fps | kFastClock,
+                                        k30fps | kProxyWifi | kAutoThrottling,
+                                        k30fps | kProxySlow | kAutoThrottling,
+                                        k30fps | kProxyBad | kAutoThrottling));
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_performance_test_base.cc b/chrome/browser/extensions/api/tab_capture/tab_capture_performance_test_base.cc
new file mode 100644
index 0000000..3d6e530
--- /dev/null
+++ b/chrome/browser/extensions/api/tab_capture/tab_capture_performance_test_base.cc
@@ -0,0 +1,217 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/api/tab_capture/tab_capture_performance_test_base.h"
+
+#include <stdint.h>
+
+#include <cmath>
+
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/unpacked_installer.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/base/tracing.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/test/browser_test_utils.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/test_extension_registry_observer.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/switches.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "third_party/zlib/google/compression_utils.h"
+#include "ui/gl/gl_switches.h"
+
+TabCapturePerformanceTestBase::TabCapturePerformanceTestBase() = default;
+
+TabCapturePerformanceTestBase::~TabCapturePerformanceTestBase() = default;
+
+void TabCapturePerformanceTestBase::SetUp() {
+  // Because screen capture is involved, require pixel output.
+  EnablePixelOutput();
+
+  InProcessBrowserTest::SetUp();
+}
+
+void TabCapturePerformanceTestBase::SetUpOnMainThread() {
+  InProcessBrowserTest::SetUpOnMainThread();
+
+  host_resolver()->AddRule("*", "127.0.0.1");
+  embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+      &TabCapturePerformanceTestBase::HandleRequest, base::Unretained(this)));
+  const bool did_start = embedded_test_server()->Start();
+  CHECK(did_start);
+}
+
+void TabCapturePerformanceTestBase::SetUpCommandLine(
+    base::CommandLine* command_line) {
+  // Note: The naming "kUseGpuInTests" is very misleading. It actually means
+  // "don't use a software OpenGL implementation." Subclasses will either call
+  // UseSoftwareCompositing() to use Chrome's software compositor, or else they
+  // won't (which means use the default hardware-accelerated compositor).
+  command_line->AppendSwitch(switches::kUseGpuInTests);
+
+  command_line->AppendSwitchASCII(extensions::switches::kWhitelistedExtensionID,
+                                  kExtensionId);
+
+  InProcessBrowserTest::SetUpCommandLine(command_line);
+}
+
+void TabCapturePerformanceTestBase::LoadExtension(
+    const base::FilePath& unpacked_dir) {
+  CHECK(!extension_);
+
+  LOG(INFO) << "Loading extension...";
+  auto* const extension_registry =
+      extensions::ExtensionRegistry::Get(browser()->profile());
+  extensions::TestExtensionRegistryObserver registry_observer(
+      extension_registry);
+  auto* const extension_service =
+      extensions::ExtensionSystem::Get(browser()->profile())
+          ->extension_service();
+  extensions::UnpackedInstaller::Create(extension_service)->Load(unpacked_dir);
+  extension_ = registry_observer.WaitForExtensionReady();
+  CHECK(extension_);
+  CHECK_EQ(kExtensionId, extension_->id());
+}
+
+void TabCapturePerformanceTestBase::NavigateToTestPage(
+    const std::string& test_page_html_content) {
+  LOG(INFO) << "Navigating to test page...";
+  test_page_to_serve_ = test_page_html_content;
+  ui_test_utils::NavigateToURL(
+      browser(),
+      embedded_test_server()->GetURL(kTestWebPageHostname, kTestWebPagePath));
+}
+
+base::Value TabCapturePerformanceTestBase::SendMessageToExtension(
+    const std::string& json) {
+  CHECK(extension_);
+
+  const std::string javascript = base::StringPrintf(
+      "new Promise((resolve, reject) => {\n"
+      "  chrome.runtime.sendMessage(\n"
+      "      '%s',\n"
+      "      %s,\n"
+      "      response => {\n"
+      "        if (!response) {\n"
+      "          reject(chrome.runtime.lastError.message);\n"
+      "        } else {\n"
+      "          resolve(response);\n"
+      "        }\n"
+      "      });\n"
+      "})",
+      extension_->id().c_str(), json.c_str());
+  LOG(INFO) << "Sending message to extension: " << json;
+  auto* const web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  for (;;) {
+    const auto result = content::EvalJs(web_contents, javascript);
+    if (result.error.empty()) {
+      return result.value.Clone();
+    }
+    LOG(INFO) << "Race condition: Waiting for extension to come up, before "
+                 "'sendMessage' retry...";
+    ContinueBrowserFor(kSendMessageRetryPeriod);
+  }
+  NOTREACHED();
+  return base::Value();
+}
+
+std::string TabCapturePerformanceTestBase::TraceAndObserve(
+    const std::string& category_patterns) {
+  LOG(INFO) << "Starting tracing and running for "
+            << kObservationPeriod.InSecondsF() << " sec...";
+  std::string json_events;
+  bool success = tracing::BeginTracing(category_patterns);
+  CHECK(success);
+  ContinueBrowserFor(kObservationPeriod);
+  success = tracing::EndTracing(&json_events);
+  CHECK(success);
+  LOG(INFO) << "Observation period has completed. Ending tracing...";
+  return json_events;
+}
+
+// static
+base::FilePath TabCapturePerformanceTestBase::GetApiTestDataDir() {
+  base::FilePath dir;
+  const bool success = base::PathService::Get(chrome::DIR_TEST_DATA, &dir);
+  CHECK(success);
+  return dir.AppendASCII("extensions").AppendASCII("api_test");
+}
+
+// static
+std::string TabCapturePerformanceTestBase::MakeBase64EncodedGZippedString(
+    const std::string& input) {
+  std::string gzipped_input;
+  compression::GzipCompress(input, &gzipped_input);
+  std::string result;
+  base::Base64Encode(gzipped_input, &result);
+
+  // Break up the string with newlines to make it easier to handle in the
+  // console logs.
+  constexpr size_t kMaxLineWidth = 80;
+  std::string formatted_result;
+  formatted_result.reserve(result.size() + 1 + (result.size() / kMaxLineWidth));
+  for (std::string::size_type src_pos = 0; src_pos < result.size();
+       src_pos += kMaxLineWidth) {
+    formatted_result.append(result, src_pos, kMaxLineWidth);
+    formatted_result.append(1, '\n');
+  }
+  return formatted_result;
+}
+
+// static
+void TabCapturePerformanceTestBase::ContinueBrowserFor(
+    base::TimeDelta duration) {
+  base::RunLoop run_loop;
+  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE, run_loop.QuitClosure(), duration);
+  run_loop.Run();
+}
+
+std::unique_ptr<net::test_server::HttpResponse>
+TabCapturePerformanceTestBase::HandleRequest(
+    const net::test_server::HttpRequest& request) {
+  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+  response->set_content_type("text/html");
+  const GURL& url = request.GetURL();
+  if (url.path() == kTestWebPagePath) {
+    response->set_content(test_page_to_serve_);
+  } else {
+    response->set_code(net::HTTP_NOT_FOUND);
+  }
+  VLOG(1) << __func__ << ": request url=" << url.spec()
+          << ", response=" << response->code();
+  return response;
+}
+
+// static
+constexpr base::TimeDelta TabCapturePerformanceTestBase::kObservationPeriod;
+
+// static
+constexpr base::TimeDelta
+    TabCapturePerformanceTestBase::kSendMessageRetryPeriod;
+
+// static
+const char TabCapturePerformanceTestBase::kTestWebPageHostname[] =
+    "in-process-perf-test.chromium.org";
+
+// static
+const char TabCapturePerformanceTestBase::kTestWebPagePath[] =
+    "/test_page.html";
+
+// static
+const char TabCapturePerformanceTestBase::kExtensionId[] =
+    "ddchlicdkolnonkihahngkmmmjnjlkkf";
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_performance_test_base.h b/chrome/browser/extensions/api/tab_capture/tab_capture_performance_test_base.h
new file mode 100644
index 0000000..5f71057
--- /dev/null
+++ b/chrome/browser/extensions/api/tab_capture/tab_capture_performance_test_base.h
@@ -0,0 +1,120 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_TAB_CAPTURE_TAB_CAPTURE_PERFORMANCE_TEST_BASE_H_
+#define CHROME_BROWSER_EXTENSIONS_API_TAB_CAPTURE_TAB_CAPTURE_PERFORMANCE_TEST_BASE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "chrome/test/base/in_process_browser_test.h"
+
+namespace base {
+class CommandLine;
+class FilePath;
+}  // namespace base
+
+namespace extensions {
+class Extension;
+}
+
+namespace net {
+namespace test_server {
+struct HttpRequest;
+class HttpResponse;
+}  // namespace test_server
+}  // namespace net
+
+// Base class shared by TabCapturePerformanceTest and
+// CastV2StreamingPerformanceTest which includes common set-up and utilities.
+// This provides the facility for loading an extension that starts capture,
+// loading a test page containing content to be captured, and sending messages
+// to engage the extension.
+class TabCapturePerformanceTestBase : public InProcessBrowserTest {
+ public:
+  TabCapturePerformanceTestBase();
+  ~TabCapturePerformanceTestBase() override;
+
+  // SetUp overrides to enable pixel output, configure the embedded test server,
+  // whitelist the extension loaded by the tests.
+  void SetUp() override;
+  void SetUpOnMainThread() override;
+  void SetUpCommandLine(base::CommandLine* command_line) override;
+
+  // Returns the currently-loaded extension.
+  const extensions::Extension* extension() const { return extension_; }
+
+  // Loads an unpacked extension found at the given path. This may only be
+  // called once. This blocks until the extension is ready (but its background
+  // page might not have run yet!).
+  void LoadExtension(const base::FilePath& unpacked_dir);
+
+  // Navigate the current (only) browser tab to the test page. This will cause a
+  // resource request on the embedded test server (see HandleRequest()). This
+  // blocks until the load is complete.
+  void NavigateToTestPage(const std::string& test_page_html_content);
+
+  // Execute JavaScript in the test page, to send a message to the extension to
+  // do something (e.g., start tab capture of the test page), and returns the
+  // response value.
+  //
+  // There is a possible race condition addressed here: The Extensions component
+  // uses a specialized "serial delayed load queue" that makes it non-trivial to
+  // discover whether a background page has run yet. If the background page has
+  // not run yet, there would be nothing listening for the message. To mitigate
+  // this problem, a simple retry loop is used.
+  base::Value SendMessageToExtension(const std::string& json);
+
+  // Runs the browser for a while, with tracing enabled to collect events
+  // matching the given |category_patterns|, then returns the JSON events string
+  // returned by tracing::EndTracing().
+  std::string TraceAndObserve(const std::string& category_patterns);
+
+  // Returns the path ".../test/data/extensions/api_test/".
+  static base::FilePath GetApiTestDataDir();
+
+  // GzipCompresses the given |input| string, then Base64-encodes and formats to
+  // 80-char lines.
+  static std::string MakeBase64EncodedGZippedString(const std::string& input);
+
+  // Uses base::RunLoop to run the browser for the given |duration|.
+  static void ContinueBrowserFor(base::TimeDelta duration);
+
+ protected:
+  // After the page has loaded, this is how long the browser is run with trace
+  // event recording taking place.
+  static constexpr base::TimeDelta kObservationPeriod =
+      base::TimeDelta::FromSeconds(15);
+
+  // If sending a message to the extension fails, because the extension has not
+  // started its message listener yet, how long before the next retry?
+  static constexpr base::TimeDelta kSendMessageRetryPeriod =
+      base::TimeDelta::FromMilliseconds(250);
+
+  // Note: The hostname must match the pattern found in the Extension's manifest
+  // file, or it will not be able to send/receive messaging from the test web
+  // page (due to extension permissions).
+  static const char kTestWebPageHostname[];
+  static const char kTestWebPagePath[];
+
+  // The expected ID of the loaded extension.
+  static const char kExtensionId[];
+
+ private:
+  // Set to the test page that should be served by the next call to
+  // HandleRequest().
+  std::string test_page_to_serve_;
+
+  // Handles requests from the tab open in the browser. Called by the embedded
+  // test server (see SetUpOnMainThread()).
+  std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
+      const net::test_server::HttpRequest& request);
+
+  const extensions::Extension* extension_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(TabCapturePerformanceTestBase);
+};
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_TAB_CAPTURE_TAB_CAPTURE_PERFORMANCE_TEST_BASE_H_
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_performancetest.cc b/chrome/browser/extensions/api/tab_capture/tab_capture_performancetest.cc
index 9aadd4d..63f04e32 100644
--- a/chrome/browser/extensions/api/tab_capture/tab_capture_performancetest.cc
+++ b/chrome/browser/extensions/api/tab_capture/tab_capture_performancetest.cc
@@ -7,12 +7,12 @@
 #include "base/command_line.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/trace_event_analyzer.h"
-#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/api/tab_capture/tab_capture_performance_test_base.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/tab_helper.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
 #include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/test_launcher_utils.h"
 #include "chrome/test/base/test_switches.h"
 #include "chrome/test/base/tracing.h"
@@ -30,8 +30,6 @@
 
 namespace {
 
-constexpr char kExtensionId[] = "ddchlicdkolnonkihahngkmmmjnjlkkf";
-
 // Number of events to trim from the begining and end. These events don't
 // contribute anything toward stable measurements: A brief moment of startup
 // "jank" is acceptable, and shutdown may result in missing events (since
@@ -49,20 +47,17 @@
   kSmallWindow = 1 << 4,         // Window size: 1 = 800x600, 0 = 2000x1000
 };
 
-class TabCapturePerformanceTest : public extensions::ExtensionApiTest,
+class TabCapturePerformanceTest : public TabCapturePerformanceTestBase,
                                   public testing::WithParamInterface<int> {
  public:
-  TabCapturePerformanceTest() {}
+  TabCapturePerformanceTest() = default;
+  ~TabCapturePerformanceTest() override = default;
 
   bool HasFlag(TestFlags flag) const {
     return (GetParam() & flag) == flag;
   }
 
-  bool IsGpuAvailable() const {
-    return base::CommandLine::ForCurrentProcess()->HasSwitch("enable-gpu");
-  }
-
-  std::string GetSuffixForTestFlags() {
+  std::string GetSuffixForTestFlags() const {
     std::string suffix;
     if (HasFlag(kUseGpu))
       suffix += "_comp_gpu";
@@ -74,60 +69,51 @@
   }
 
   void SetUp() override {
-    EnablePixelOutput();
+    const base::FilePath test_file = GetApiTestDataDir()
+                                         .AppendASCII("tab_capture")
+                                         .AppendASCII("balls.html");
+    const bool success = base::ReadFileToString(test_file, &test_page_html_);
+    CHECK(success) << "Failed to load test page at: "
+                   << test_file.AsUTF8Unsafe();
+
     if (!HasFlag(kUseGpu))
       UseSoftwareCompositing();
-    extensions::ExtensionApiTest::SetUp();
+
+    TabCapturePerformanceTestBase::SetUp();
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    // Some of the tests may launch http requests through JSON or AJAX
-    // which causes a security error (cross domain request) when the page
-    // is loaded from the local file system ( file:// ). The following switch
-    // fixes that error.
-    command_line->AppendSwitch(switches::kAllowFileAccessFromFiles);
-
     if (HasFlag(kSmallWindow)) {
       command_line->AppendSwitchASCII(switches::kWindowSize, "800,600");
     } else {
       command_line->AppendSwitchASCII(switches::kWindowSize, "2000,1500");
     }
 
-    if (!HasFlag(kUseGpu))
-      command_line->AppendSwitch(switches::kDisableGpu);
-
-    command_line->AppendSwitchASCII(
-        extensions::switches::kWhitelistedExtensionID,
-        kExtensionId);
-
-    extensions::ExtensionApiTest::SetUpCommandLine(command_line);
+    TabCapturePerformanceTestBase::SetUpCommandLine(command_line);
   }
 
-  void FindEvents(trace_analyzer::TraceAnalyzer* analyzer,
-                  const std::string& event_name,
-                  trace_analyzer::TraceEventVector* events) {
+  static void GetTraceEvents(trace_analyzer::TraceAnalyzer* analyzer,
+                             const std::string& event_name,
+                             trace_analyzer::TraceEventVector* events) {
     trace_analyzer::Query query =
         trace_analyzer::Query::EventNameIs(event_name) &&
         (trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_BEGIN) ||
-         trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_COMPLETE) ||
          trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_BEGIN) ||
          trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_FLOW_BEGIN) ||
-         trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_INSTANT));
+         trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_INSTANT) ||
+         trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_COMPLETE));
     analyzer->FindEvents(query, events);
+    VLOG(0) << "Retrieved " << events->size() << " events for: " << event_name;
+    ASSERT_LT(2 * kTrimEvents + kMinDataPoints, events->size())
+        << "Not enough events of type " << event_name << " found for analysis.";
   }
 
   // Analyze and print the mean and stddev of how often events having the name
   // |event_name| occur.
   bool PrintRateResults(trace_analyzer::TraceAnalyzer* analyzer,
-                        const std::string& test_name,
                         const std::string& event_name) {
     trace_analyzer::TraceEventVector events;
-    FindEvents(analyzer, event_name, &events);
-    if (events.size() < (2 * kTrimEvents + kMinDataPoints)) {
-      LOG(ERROR) << "Not enough events of type " << event_name << " found ("
-                 << events.size() << ") for rate analysis.";
-      return false;
-    }
+    GetTraceEvents(analyzer, event_name, &events);
 
     // Ignore some events for startup/setup/caching/teardown.
     trace_analyzer::TraceEventVector rate_events(events.begin() + kTrimEvents,
@@ -141,7 +127,7 @@
     double std_dev_ms = stats.standard_deviation_us / 1000.0;
     std::string mean_and_error = base::StringPrintf("%f,%f", mean_ms,
                                                     std_dev_ms);
-    perf_test::PrintResultMeanAndError(test_name, GetSuffixForTestFlags(),
+    perf_test::PrintResultMeanAndError(kTestName, GetSuffixForTestFlags(),
                                        event_name, mean_and_error, "ms", true);
     return true;
   }
@@ -149,15 +135,9 @@
   // Analyze and print the mean and stddev of the amount of time between the
   // begin and end timestamps of each event having the name |event_name|.
   bool PrintLatencyResults(trace_analyzer::TraceAnalyzer* analyzer,
-                           const std::string& test_name,
                            const std::string& event_name) {
     trace_analyzer::TraceEventVector events;
-    FindEvents(analyzer, event_name, &events);
-    if (events.size() < (2 * kTrimEvents + kMinDataPoints)) {
-      LOG(ERROR) << "Not enough events of type " << event_name << " found ("
-                 << events.size() << ") for latency analysis.";
-      return false;
-    }
+    GetTraceEvents(analyzer, event_name, &events);
 
     // Ignore some events for startup/setup/caching/teardown.
     trace_analyzer::TraceEventVector events_to_analyze(
@@ -181,7 +161,7 @@
     const double std_dev_us =
         sqrt(std::max(0.0, count * sqr_sum - sum * sum)) / count;
     perf_test::PrintResultMeanAndError(
-        test_name, GetSuffixForTestFlags(), event_name + "Latency",
+        kTestName, GetSuffixForTestFlags(), event_name + "Latency",
         base::StringPrintf("%f,%f", mean_us / 1000.0, std_dev_us / 1000.0),
         "ms", true);
     return true;
@@ -190,15 +170,9 @@
   // Analyze and print the mean and stddev of how often events having the name
   // |event_name| are missing the success=true flag.
   bool PrintFailRateResults(trace_analyzer::TraceAnalyzer* analyzer,
-                            const std::string& test_name,
                             const std::string& event_name) {
     trace_analyzer::TraceEventVector events;
-    FindEvents(analyzer, event_name, &events);
-    if (events.size() < (2 * kTrimEvents + kMinDataPoints)) {
-      LOG(ERROR) << "Not enough events of type " << event_name << " found ("
-                 << events.size() << ") for fail rate analysis.";
-      return false;
-    }
+    GetTraceEvents(analyzer, event_name, &events);
 
     // Ignore some events for startup/setup/caching/teardown.
     trace_analyzer::TraceEventVector events_to_analyze(
@@ -230,61 +204,69 @@
       fail_percent *= fail_count / events_to_analyze.size();
     }
     perf_test::PrintResult(
-        test_name, GetSuffixForTestFlags(), event_name + "FailRate",
+        kTestName, GetSuffixForTestFlags(), event_name + "FailRate",
         base::StringPrintf("%f", fail_percent), "percent", true);
     return true;
   }
 
-  void RunTest(const std::string& test_name) {
-    if (HasFlag(kUseGpu) && !IsGpuAvailable()) {
-      LOG(WARNING) <<
-          "Test skipped: requires gpu. Pass --enable-gpu on the command "
-          "line if use of GPU is desired.";
-      return;
-    }
+ protected:
+  // The HTML test web page that draws animating balls continuously. Populated
+  // in SetUp().
+  std::string test_page_html_;
 
-    std::string json_events;
-    ASSERT_TRUE(tracing::BeginTracing("gpu,gpu.capture"));
-    std::string page = "performance.html";
-    page += HasFlag(kTestThroughWebRTC) ? "?WebRTC=1" : "?WebRTC=0";
-    page += "&fps=60";
-    ASSERT_TRUE(RunExtensionSubtest("tab_capture", page)) << message_;
-    ASSERT_TRUE(tracing::EndTracing(&json_events));
-    std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer;
-    analyzer.reset(trace_analyzer::TraceAnalyzer::Create(json_events));
-    analyzer->AssociateAsyncBeginEndEvents();
-
-    // The printed result will be the average time between composites in the
-    // renderer of the page being captured. This may not reach the full frame
-    // rate if the renderer cannot draw as fast as is desired.
-    //
-    // Note that any changes to drawing or compositing in the renderer,
-    // including changes to Blink (e.g., Canvas drawing), layout, etc.; will
-    // have an impact on this result.
-    EXPECT_TRUE(
-        PrintRateResults(analyzer.get(), test_name,
-                         "RenderWidget::DidCommitAndDrawCompositorFrame"));
-
-    // This prints out the average time between capture events in the browser
-    // process. This should roughly match the renderer's draw+composite rate.
-    EXPECT_TRUE(PrintRateResults(analyzer.get(), test_name, "Capture"));
-
-    // Analyze mean/stddev of the capture latency. This is a measure of how long
-    // each capture took, from initiation until read-back from the GPU into a
-    // media::VideoFrame was complete. Lower is better.
-    EXPECT_TRUE(PrintLatencyResults(analyzer.get(), test_name, "Capture"));
-
-    // Analyze percentage of failed captures. This measures how often captures
-    // were initiated, but not completed successfully. Lower is better, and zero
-    // is ideal.
-    EXPECT_TRUE(PrintFailRateResults(analyzer.get(), test_name, "Capture"));
-  }
+  // Naming of performance measurement written to stdout.
+  static const char kTestName[];
 };
 
+// static
+const char TabCapturePerformanceTest::kTestName[] = "TabCapturePerformance";
+
 }  // namespace
 
 IN_PROC_BROWSER_TEST_P(TabCapturePerformanceTest, Performance) {
-  RunTest("TabCapturePerformance");
+  // Load the extension and test page, and tell the extension to start tab
+  // capture.
+  LoadExtension(GetApiTestDataDir()
+                    .AppendASCII("tab_capture")
+                    .AppendASCII("perftest_extension"));
+  NavigateToTestPage(test_page_html_);
+  const base::Value response = SendMessageToExtension(
+      base::StringPrintf("{start:true, passThroughWebRTC:%s}",
+                         HasFlag(kTestThroughWebRTC) ? "true" : "false"));
+  const std::string* reason = response.FindStringKey("reason");
+  ASSERT_TRUE(response.FindBoolKey("success").value_or(false))
+      << (reason ? *reason : std::string("<MISSING REASON>"));
+
+  // Observe the running browser for a while, collecting a trace.
+  const std::string json_events = TraceAndObserve("gpu,gpu.capture");
+
+  std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer;
+  analyzer.reset(trace_analyzer::TraceAnalyzer::Create(json_events));
+  analyzer->AssociateAsyncBeginEndEvents();
+
+  // The printed result will be the average time between composites in the
+  // renderer of the page being captured. This may not reach the full frame
+  // rate if the renderer cannot draw as fast as is desired.
+  //
+  // Note that any changes to drawing or compositing in the renderer,
+  // including changes to Blink (e.g., Canvas drawing), layout, etc.; will
+  // have an impact on this result.
+  EXPECT_TRUE(PrintRateResults(
+      analyzer.get(), "RenderWidget::DidCommitAndDrawCompositorFrame"));
+
+  // This prints out the average time between capture events in the browser
+  // process. This should roughly match the renderer's draw+composite rate.
+  EXPECT_TRUE(PrintRateResults(analyzer.get(), "Capture"));
+
+  // Analyze mean/stddev of the capture latency. This is a measure of how long
+  // each capture took, from initiation until read-back from the GPU into a
+  // media::VideoFrame was complete. Lower is better.
+  EXPECT_TRUE(PrintLatencyResults(analyzer.get(), "Capture"));
+
+  // Analyze percentage of failed captures. This measures how often captures
+  // were initiated, but not completed successfully. Lower is better, and zero
+  // is ideal.
+  EXPECT_TRUE(PrintFailRateResults(analyzer.get(), "Capture"));
 }
 
 // Note: First argument is optional and intentionally left blank.
diff --git a/chrome/browser/extensions/content_capabilities_browsertest.cc b/chrome/browser/extensions/content_capabilities_browsertest.cc
index 284be119..89d1b0e 100644
--- a/chrome/browser/extensions/content_capabilities_browsertest.cc
+++ b/chrome/browser/extensions/content_capabilities_browsertest.cc
@@ -202,7 +202,9 @@
   // script without a user gesture.
   EXPECT_TRUE(
       CanWriteClipboard(extension.get(), GetTestURLFor("bar.example.com")));
-  if (!base::FeatureList::IsEnabled(features::kUserActivationV2)) {
+  if (!base::FeatureList::IsEnabled(features::kUserActivationV2) ||
+      base::FeatureList::IsEnabled(
+          features::kUserActivationSameOriginVisibility)) {
     EXPECT_TRUE(CanWriteClipboardInAboutBlankFrame(
         extension.get(), GetTestURLFor("bar.example.com")));
   } else {
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index b4a8819..76cc8a94 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1150,7 +1150,7 @@
   },
   {
     "name": "enable-drive-fs",
-    // "owners": [ "your-team" ],
+    "owners": [ "sammc", "dats" ],
     "expiry_milestone": 76
   },
   {
@@ -1189,6 +1189,11 @@
     "expiry_milestone": 76
   },
   {
+    "name": "enable-experimental-accessibility-language-detection",
+    "owners": [ "chrishall", "//ui/accessibility/OWNERS" ],
+    "expiry_milestone": 76
+  },
+  {
     "name": "enable-experimental-accessibility-switch-access",
     "owners": [ "anastasi@google.com", "dmazzoni", "dtseng" ],
     "expiry_milestone": 76
@@ -1465,7 +1470,7 @@
   },
   {
     "name": "enable-myfiles-volume",
-    // "owners": [ "your-team" ],
+    "owners": [ "lucmult", "noel" ],
     "expiry_milestone": 76
   },
   {
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 6070d92f..86cbbb8b 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -723,6 +723,12 @@
 const char kExperimentalAccessibilityLabelsDescription[] =
     "Enable additional features for image labels for accessibility.";
 
+const char kExperimentalAccessibilityLanguageDetectionName[] =
+    "Experimental accessibility language detection";
+const char kExperimentalAccessibilityLanguageDetectionDescription[] =
+    "Enable language detection for in-page content which is then exposed to "
+    "accessiblity technologies such as screen readers.";
+
 const char kExperimentalAccessibilitySwitchAccessName[] =
     "Experimental feature Switch Access";
 const char kExperimentalAccessibilitySwitchAccessDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 8275926e..da377e6 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -176,6 +176,9 @@
 extern const char kExperimentalAccessibilityLabelsName[];
 extern const char kExperimentalAccessibilityLabelsDescription[];
 
+extern const char kExperimentalAccessibilityLanguageDetectionName[];
+extern const char kExperimentalAccessibilityLanguageDetectionDescription[];
+
 extern const char kExperimentalAccessibilitySwitchAccessName[];
 extern const char kExperimentalAccessibilitySwitchAccessDescription[];
 
diff --git a/chrome/browser/previews/lazyload_browsertest.cc b/chrome/browser/previews/lazyload_browsertest.cc
deleted file mode 100644
index 1de73ea..0000000
--- a/chrome/browser/previews/lazyload_browsertest.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/run_loop.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
-#include "build/build_config.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "components/data_use_measurement/core/data_use_user_data.h"
-#include "content/public/common/content_features.h"
-#include "content/public/test/browser_test_base.h"
-#include "content/public/test/browser_test_utils.h"
-#include "net/test/embedded_test_server/embedded_test_server.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-class LazyLoadBrowserTest : public InProcessBrowserTest {
- protected:
-  void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(features::kLazyImageLoading);
-    InProcessBrowserTest::SetUp();
-  }
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_F(LazyLoadBrowserTest, CSSBackgroundImageDeferred) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  base::HistogramTester histogram_tester;
-  ui_test_utils::NavigateToURL(
-      browser(),
-      embedded_test_server()->GetURL("/lazyload/css-background-image.html"));
-
-  base::RunLoop().RunUntilIdle();
-  // Navigate away to finish the histogram recording.
-  ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
-
-  // Verify that nothing is recorded for the image bucket.
-  EXPECT_GE(0, histogram_tester.GetBucketCount(
-                   "DataUse.ContentType.UserTrafficKB",
-                   data_use_measurement::DataUseUserData::IMAGE));
-}
-
-#if defined(OS_CHROMEOS) || defined(OS_WIN)
-// Disable the tests flaky in Windows, chromeOS
-#define MAYBE_CSSBackgroundImageLoadedWhenScrolled \
-  DISABLED_CSSBackgroundImageLoadedWhenScrolled
-#else
-#define MAYBE_CSSBackgroundImageLoadedWhenScrolled \
-  CSSBackgroundImageLoadedWhenScrolled
-#endif
-
-IN_PROC_BROWSER_TEST_F(LazyLoadBrowserTest,
-                       MAYBE_CSSBackgroundImageLoadedWhenScrolled) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  base::HistogramTester histogram_tester;
-
-  // Simulate scrolling by loading the anchor section where images are located.
-  ui_test_utils::NavigateToURL(
-      browser(), embedded_test_server()->GetURL(
-                     "/lazyload/css-background-image.html#images"));
-
-  base::RunLoop().RunUntilIdle();
-  // Navigate away to finish the histogram recording.
-  ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
-
-  // Verify that the image bucket has substantial kilobytes recorded.
-  EXPECT_LE(35 /* kb */, histogram_tester.GetBucketCount(
-                             "DataUse.ContentType.UserTrafficKB",
-                             data_use_measurement::DataUseUserData::IMAGE));
-}
diff --git a/chrome/browser/resources/bookmarks/README.md b/chrome/browser/resources/bookmarks/README.md
index 44f0f1e4..c6c83b16 100644
--- a/chrome/browser/resources/bookmarks/README.md
+++ b/chrome/browser/resources/bookmarks/README.md
@@ -1,4 +1,4 @@
-# Material Design Bookmark Manager
+# Bookmark Manager
 
 The bookmark manager (BMM) is a WebUI surface with a large amount of
 functionality for managing bookmarks across a relatively simple UI. This
diff --git a/chrome/browser/resources/bookmarks/types.js b/chrome/browser/resources/bookmarks/types.js
index 66d7f9d..0983d50 100644
--- a/chrome/browser/resources/bookmarks/types.js
+++ b/chrome/browser/resources/bookmarks/types.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 /**
- * @fileoverview Closure typedefs for MD Bookmarks.
+ * @fileoverview Closure typedefs for Bookmarks.
  */
 
 /**
diff --git a/chrome/browser/resources/cast/cast.html b/chrome/browser/resources/cast/cast.html
index e7afdd9..4510540e 100644
--- a/chrome/browser/resources/cast/cast.html
+++ b/chrome/browser/resources/cast/cast.html
@@ -1,5 +1,5 @@
 <!DOCTYPE HTML>
-<html i18n-values="dir:textdirection;lang:language">
+<html dir="$i18n{textdirection}" lang="$i18n{language}">
 <head>
   <meta charset="utf-8">
   <title>Google Cast</title>
diff --git a/chrome/browser/resources/chromeos/account_manager_welcome.css b/chrome/browser/resources/chromeos/account_manager_welcome.css
new file mode 100644
index 0000000..75086d7
--- /dev/null
+++ b/chrome/browser/resources/chromeos/account_manager_welcome.css
@@ -0,0 +1,46 @@
+/* Copyright (c) 2019 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+body {
+  padding-inline-end: 4em;
+  padding-inline-start: 4em;
+}
+
+h1 {
+  font-family: 'Google Sans', Helvetica, Roboto, sans-serif;
+  font-weight: normal;
+}
+
+p {
+  font-family: 'Google Sans', Helvetica, Roboto, sans-serif;
+  white-space: pre-line;
+}
+
+#google-logo {
+  height: 30px;
+  width: 30px;
+}
+
+#welcome-image {
+  display: block;
+  margin-inline-end: auto;
+  margin-inline-start: auto;
+}
+
+#ok-button-container {
+  position: relative;
+}
+
+#ok-button {
+  background-color: var(--google-blue-600);
+  bottom: 0;
+  color: white;
+  position: absolute;
+  right: 0;
+}
+
+html[dir='rtl'] #ok-button {
+  left: 0;
+  right: auto;
+}
diff --git a/chrome/browser/resources/chromeos/account_manager_welcome.html b/chrome/browser/resources/chromeos/account_manager_welcome.html
new file mode 100644
index 0000000..ef31103b
--- /dev/null
+++ b/chrome/browser/resources/chromeos/account_manager_welcome.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html i18n-values="dir:textdirection;lang:language">
+<head>
+  <meta charset="utf-8">
+  <link rel="import" href="chrome://resources/html/polymer.html">
+  <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
+  <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
+  <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
+
+  <link rel="stylesheet" href="account_manager_welcome.css">
+  <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
+
+  <script src="chrome://resources/js/cr.js"></script>
+  <script src="chrome://resources/js/load_time_data.js"></script>
+  <script src="chrome://resources/js/util.js"></script>
+  <script src="strings.js"></script>
+  <script src="account_manager_welcome.js"></script>
+</head>
+<body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+  <img id="google-logo" src="googleg.svg">
+  <h1 i18n-content="welcomeTitle"></h1>
+  <p i18n-content="welcomeMessage"></p>
+  <img id="welcome-image"
+       srcset="account_manager_welcome_1x.png 1x,
+               account_manager_welcome_2x.png 2x">
+
+  <div id="ok-button-container">
+    <paper-button raised id="ok-button" i18n-content="okButton"></paper-button>
+  </div>
+
+  <script src="chrome://resources/js/i18n_template.js"></script>
+</body>
+</html>
diff --git a/chrome/browser/resources/chromeos/account_manager_welcome.js b/chrome/browser/resources/chromeos/account_manager_welcome.js
new file mode 100644
index 0000000..4168a1f
--- /dev/null
+++ b/chrome/browser/resources/chromeos/account_manager_welcome.js
@@ -0,0 +1,23 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('account_manager_welcome', function() {
+  'use strict';
+
+  function initialize() {
+    $('ok-button').addEventListener('click', closeDialog);
+  }
+
+  function closeDialog() {
+    chrome.send('closeDialog');
+  }
+
+  return {
+    initialize: initialize,
+    closeDialog: closeDialog,
+  };
+});
+
+document.addEventListener(
+    'DOMContentLoaded', account_manager_welcome.initialize);
diff --git a/chrome/browser/resources/chromeos/account_manager_welcome_1x.png b/chrome/browser/resources/chromeos/account_manager_welcome_1x.png
new file mode 100644
index 0000000..1dda30db
--- /dev/null
+++ b/chrome/browser/resources/chromeos/account_manager_welcome_1x.png
Binary files differ
diff --git a/chrome/browser/resources/chromeos/account_manager_welcome_2x.png b/chrome/browser/resources/chromeos/account_manager_welcome_2x.png
new file mode 100644
index 0000000..edbacc93
--- /dev/null
+++ b/chrome/browser/resources/chromeos/account_manager_welcome_2x.png
Binary files differ
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
index 125d457..dcf68f9 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
@@ -72,7 +72,6 @@
   this.addListener_(EventType.BLUR, this.onBlur);
   this.addListener_(
       EventType.CHECKED_STATE_CHANGED, this.onCheckedStateChanged);
-  this.addListener_(EventType.CHILDREN_CHANGED, this.onChildrenChanged);
   this.addListener_(
       EventType.DOCUMENT_SELECTION_CHANGED, this.onDocumentSelectionChanged);
   this.addListener_(EventType.EXPANDED_CHANGED, this.onEventIfInRange);
@@ -188,14 +187,17 @@
     if (prev.contentEquals(cursors.Range.fromNode(evt.target)) ||
         evt.target.state.focused) {
       var prevTarget = this.lastAttributeTarget_;
-      this.lastAttributeTarget_ = evt.target;
-      var prevOutput = this.lastAttributeOutput_;
-      this.lastAttributeOutput_ = new Output().withRichSpeechAndBraille(
-          cursors.Range.fromNode(evt.target), prev, Output.EventType.NAVIGATE);
 
-      if (evt.target == prevTarget && prevOutput &&
-          prevOutput.equals(this.lastAttributeOutput_))
+      // Re-target to active descendant if it exists.
+      var prevOutput = this.lastAttributeOutput_;
+      this.lastAttributeTarget_ = evt.target.activeDescendant || evt.target;
+      this.lastAttributeOutput_ = new Output().withRichSpeechAndBraille(
+          cursors.Range.fromNode(this.lastAttributeTarget_), prev,
+          Output.EventType.NAVIGATE);
+      if (this.lastAttributeTarget_ == prevTarget && prevOutput &&
+          prevOutput.equals(this.lastAttributeOutput_)) {
         return;
+      }
 
       // If the target or an ancestor is controlled by another control, we may
       // want to delay the output.
@@ -230,8 +232,11 @@
   onAriaAttributeChanged: function(evt) {
     if (evt.target.state.editable)
       return;
-    // Only report attribute changes on menu list items if it is selected.
-    if (evt.target.role == RoleType.MENU_LIST_OPTION && !evt.target.selected)
+
+    // Only report attribute changes on some *Option roles if it is selected.
+    if ((evt.target.role == RoleType.MENU_LIST_OPTION ||
+         evt.target.role == RoleType.LIST_BOX_OPTION) &&
+        !evt.target.selected)
       return;
 
     this.onEventIfInRange(evt);
@@ -318,12 +323,11 @@
   onActiveDescendantChanged: function(evt) {
     if (!evt.target.activeDescendant || !evt.target.state.focused)
       return;
-    var prevRange = ChromeVoxState.instance.currentRange;
-    var range = cursors.Range.fromNode(evt.target.activeDescendant);
-    ChromeVoxState.instance.setCurrentRange(range);
-    new Output()
-        .withRichSpeechAndBraille(range, prevRange, Output.EventType.NAVIGATE)
-        .go();
+
+    // Various events might come before a key press (which forces flushed
+    // speech) and this handler. Force output to be at least category flushed.
+    Output.forceModeForNextSpeechUtterance(cvox.QueueMode.CATEGORY_FLUSH);
+    this.onEventIfInRange(evt);
   },
 
   /**
@@ -364,27 +368,6 @@
   /**
    * @param {!AutomationEvent} evt
    */
-  onChildrenChanged: function(evt) {
-    var curRange = ChromeVoxState.instance.currentRange;
-
-    // views::TextField blinks by making its cursor view alternate between
-    // visible and not visible. This results in a children changed event on its
-    // parent (the text field itself). In general, text field feedback should be
-    // given within text field specific events.
-    if (evt.target.role == RoleType.TEXT_FIELD)
-      return;
-
-    // Always refresh the braille contents.
-    if (curRange && curRange.equals(cursors.Range.fromNode(evt.target))) {
-      new Output()
-          .withBraille(curRange, curRange, Output.EventType.NAVIGATE)
-          .go();
-    }
-  },
-
-  /**
-   * @param {!AutomationEvent} evt
-   */
   onDocumentSelectionChanged: function(evt) {
     var anchor = evt.target.anchorObject;
 
diff --git a/chrome/browser/resources/chromeos/googleg.svg b/chrome/browser/resources/chromeos/googleg.svg
new file mode 100644
index 0000000..6a5a473
--- /dev/null
+++ b/chrome/browser/resources/chromeos/googleg.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 104 104" width="104" height="104">
+    <path fill="#4285F4" d="M101.93 53.18c0-3.89-.38-7.61-1.04-11.18H52v19.98h28.08c-1.09 6.77-4.79 12.55-10.48 16.38v13.09h16.86c9.82-9.06 15.47-22.44 15.47-38.27z"/>
+    <path fill="#34A853" d="M52 104c14.03 0 25.85-4.61 34.46-12.55L69.6 78.36c-4.64 3.12-10.61 4.96-17.6 4.96-13.53 0-25.01-9.13-29.11-21.43H5.54v13.46C14.1 92.34 31.68 104 52 104z"/>
+    <path fill="#FBBC05" d="M22.89 61.89c-1.04-3.12-1.61-6.45-1.61-9.89 0-3.44.57-6.76 1.61-9.89V28.65H5.54C2 35.67 0 43.6 0 52s2 16.33 5.54 23.35l17.35-13.46z"/>
+    <path fill="#EA4335" d="M52 20.68c7.64 0 14.49 2.63 19.89 7.77l14.9-14.9C77.78 5.15 66.03 0 52 0 31.68 0 14.1 11.66 5.54 28.65l17.35 13.46c4.1-12.3 15.58-21.43 29.11-21.43z"/>
+    <path fill="none" d="M0 0h104v104H0z"/>
+</svg>
diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd
index 03694db..67606ba 100644
--- a/chrome/browser/resources/component_extension_resources.grd
+++ b/chrome/browser/resources/component_extension_resources.grd
@@ -30,8 +30,8 @@
         <include name="IDR_BRAILLE_IME_MAIN_JS" file="chromeos/braille_ime/main.js" type="BINDATA" />
       </if>
 
-      <!-- Material Design Bookmarks -->
-      <include name="IDR_COMPONENT_MD_BOOKMARKS_BOOKMARKS_HTML" file="bookmarks/bookmarks.html" type="BINDATA" />
+      <!-- Bookmarks -->
+      <include name="IDR_COMPONENT_BOOKMARKS_BOOKMARKS_HTML" file="bookmarks/bookmarks.html" type="BINDATA" />
       <!-- Gaia auth extension -->
       <include name="IDR_GAIA_AUTH_SUCCESS" file="gaia_auth/success.html" allowexternalscript="true" type="BINDATA" />
       <!-- Hangout Services extension, included in Google Chrome builds only. -->
diff --git a/chrome/browser/resources/local_ntp/constants.css b/chrome/browser/resources/local_ntp/constants.css
new file mode 100644
index 0000000..72e81b87
--- /dev/null
+++ b/chrome/browser/resources/local_ntp/constants.css
@@ -0,0 +1,50 @@
+/* Copyright 2019 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+html {
+  /* Material Design colors. Keep in sync with ui/gfx/color_palette.h. */
+
+  --dark-mode-bg-rgb: 50, 54, 57;
+
+  /* Google Grey */
+  --GG050-rgb: 248, 249, 250;
+  --GG100-rgb: 241, 243, 244;
+  --GG200-rgb: 232, 234, 237;
+  --GG300-rgb: 218, 220, 224;
+  --GG400-rgb: 189, 193, 198;
+  --GG500-rgb: 154, 160, 166;
+  --GG600-rgb: 128, 134, 139;
+  --GG700-rgb: 95, 99, 104;
+  --GG800-rgb: 60, 64, 67;
+  --GG900-rgb: 32, 33, 36;
+
+  /* Google Blue */
+  --GB050-rgb: 232, 240, 254;
+  --GB100-rgb: 210, 227, 252;
+  --GB200-rgb: 174, 203, 250;
+  --GB300-rgb: 138, 180, 248;
+  --GB400-rgb: 102, 157, 246;
+  --GB500-rgb: 66, 133, 244;
+  --GB600-rgb: 26, 115, 232;
+  --GB700-rgb: 25, 103, 210;
+  --GB800-rgb: 24, 90, 188;
+  --GB900-rgb: 23, 78, 166;
+  --GB400-dark-rgb: 107, 165, 237;
+  --GB600-dark-rgb: 37, 129, 223;
+
+  /* Google Red */
+  --GR050-rgb: 252, 142, 230;
+  --GR100-rgb: 250, 210, 207;
+  --GR200-rgb: 246, 174, 169;
+  --GR300-rgb: 242, 139, 130;
+  --GR400-rgb: 238, 103, 92;
+  --GR500-rgb: 234, 67, 53;
+  --GR600-rgb: 217, 48, 37;
+  --GR700-rgb: 197, 34, 31;
+  --GR800-rgb: 179, 20, 18;
+  --GR900-rgb: 165, 14, 14;
+  --GR500-dark-rgb: 230, 106, 94;
+  --GR600-dark-rgb: 211, 59, 48;
+  --GR800-dark-rgb: 180, 27, 26;
+}
diff --git a/chrome/browser/resources/local_ntp/custom_backgrounds.css b/chrome/browser/resources/local_ntp/custom_backgrounds.css
index 89527d1..80da01e 100644
--- a/chrome/browser/resources/local_ntp/custom_backgrounds.css
+++ b/chrome/browser/resources/local_ntp/custom_backgrounds.css
@@ -32,11 +32,11 @@
 }
 
 #edit-bg:hover {
-  background-color: rgba(32, 33, 36, .1);
+  background-color: rgba(var(--GG900-rgb), .1);
 }
 
 #edit-bg:active {
-  background-color: rgba(32, 33, 36, .16);
+  background-color: rgba(var(--GG900-rgb), .16);
 }
 
 .non-white-bg #edit-bg:hover {
@@ -74,7 +74,8 @@
   border-radius: 8px;
   border-width: thin;
   bottom: 44px;
-  box-shadow: 0 1px 3px 0 rgba(60, 64, 67, 0.3), 0 4px 8px 3px rgba(60, 64, 67, 0.15);
+  box-shadow: 0 1px 3px 0 rgba(var(--GG800-rgb), 0.3),
+      0 4px 8px 3px rgba(var(--GG800-rgb), 0.15);
   left: auto;
   padding: 0;
   position: fixed;
@@ -92,7 +93,7 @@
 }
 
 #edit-bg-title {
-  color: rgb(32, 33, 36);
+  color: rgb(var(--GG900-rgb));
   font-family: 'Roboto', arial, sans-serif;
   font-size: 15px;
   height: 30px;
@@ -104,7 +105,7 @@
 }
 
 .bg-option {
-  color: rgb(60, 64, 67);
+  color: rgb(var(--GG800-rgb));
   font-family: 'Roboto', arial, sans-serif;
   font-size: 13px;
   height: 40px;
@@ -112,7 +113,7 @@
 }
 
 .bg-option:hover {
-  background-color: rgb(241, 243, 244);
+  background-color: rgb(var(--GG100-rgb));
 }
 
 .bg-option-img {
@@ -151,23 +152,23 @@
 }
 
 #edit-bg-upload-image .bg-option-img {
-  background: rgb(241, 243, 244) url(icons/upload.svg) no-repeat center;
+  background: rgb(var(--GG100-rgb)) url(icons/upload.svg) no-repeat center;
   border-radius: 50%;
 }
 
 #edit-bg-divider {
-  border-bottom: 1px solid rgb(232, 234, 237);
+  border-bottom: 1px solid rgb(var(--GG200-rgb));
   margin: 8px 0;
   width: 100%;
 }
 
 #custom-links-restore-default .bg-option-img {
-  background: rgb(241, 243, 244) url(icons/link_gray.svg) no-repeat center;
+  background: rgb(var(--GG100-rgb)) url(icons/link_gray.svg) no-repeat center;
   border-radius: 50%;
 }
 
 #edit-bg-restore-default .bg-option-img {
-  background: rgb(241, 243, 244) url(icons/chrome.svg)  no-repeat center;
+  background: rgb(var(--GG100-rgb)) url(icons/chrome.svg)  no-repeat center;
   border-radius: 50%;
 }
 
@@ -199,7 +200,8 @@
   border: none;
   border-radius: 8px;
   bottom: 0;
-  box-shadow: 0 1px 3px 0 rgba(60, 64, 67, 0.3), 0 4px 8px 3px rgba(60, 64, 67, 0.15);
+  box-shadow: 0 1px 3px 0 rgba(var(--GG800-rgb), 0.3),
+      0 4px 8px 3px rgba(var(--GG800-rgb), 0.15);
   font-family: 'Roboto', arial, sans-serif;
   height: 400px;
   left: 0;
@@ -247,7 +249,7 @@
 }
 
 #bg-sel-title-bar {
-  border-bottom: 1px solid rgb(232, 234, 237);
+  border-bottom: 1px solid rgb(var(--GG200-rgb));
   font-size: 15px;
   height: 51px;
   line-height: 52px;
@@ -275,7 +277,7 @@
 
 #bg-sel-back-circle:active,
 #bg-sel-back-circle:focus {
-  background: rgb(218, 220, 224);
+  background: rgb(var(--GG300-rgb));
   background-position: center;
   background-size: 36px 36px;
 }
@@ -315,7 +317,7 @@
 }
 
 #bg-sel-title {
-  color: rgb(32, 33, 36);
+  color: rgb(var(--GG900-rgb));
   display: inline-block;
   height: 20px;
   line-height: 20px;
@@ -340,9 +342,9 @@
 }
 
 #bg-sel-footer {
-  border-top: 1px solid rgba(232, 234, 237, 1);
+  border-top: 1px solid rgb(var(--GG200-rgb));
   bottom: 0;
-  color: rgb(60, 64, 67);
+  color: rgb(var(--GG800-rgb));
   height: 64px;
   padding-left: 0;
   position: absolute;
@@ -379,8 +381,8 @@
 }
 
 #bg-sel-footer-done {
-  background-color: rgb(241, 243, 244);
-  color: rgb(128, 134, 139);
+  background-color: rgb(var(--GG100-rgb));
+  color: rgb(var(--GG600-rgb));
   margin-right: 16px;
 }
 
@@ -391,8 +393,8 @@
 
 #bg-sel-footer-cancel {
   background-color: white;
-  border: 1px solid rgb(218, 220, 224);
-  color: rgb(26, 115, 232);
+  border: 1px solid rgb(var(--GG300-rgb));
+  color: rgb(var(--GB600-rgb));
   margin-right: 8px;
 }
 
@@ -401,31 +403,31 @@
 }
 
 #bg-sel-footer-cancel:hover {
-  background-color: rgba(66, 133, 244, 0.04);
-  border-color: rgb(210, 227, 252);
+  background-color: rgba(var(--GB500-rgb), 0.04);
+  border-color: rgb(var(--GB100-rgb));
 }
 
 #bg-sel-footer-cancel:active {
   background-color: white;
   border-color: white;
-  box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.3),
-  0 3px 6px 2px rgba(60, 64, 67, 0.15);
+  box-shadow: 0 1px 2px 0 rgba(var(--GG800-rgb), 0.3),
+      0 3px 6px 2px rgba(var(--GG800-rgb), 0.15);
 }
 
 #bg-sel-footer-done:not(:disabled) {
-  background-color: rgb(26, 115, 232);
+  background-color: rgb(var(--GB600-rgb));
   color: white;
 }
 
 #bg-sel-footer-done:hover:not(:disabled) {
   background-color: rgb(41, 123, 231);
-  box-shadow: 0 1px 2px 0 rgba(66, 133, 244, 0.3),
-      0 1px 3px 1px rgba(66, 133, 244, 0.15);
+  box-shadow: 0 1px 2px 0 rgba(var(--GB500-rgb), 0.3),
+      0 1px 3px 1px rgba(var(--GB500-rgb), 0.15);
 }
 
 #bg-sel-footer-done:active:not(:disabled) {
-  box-shadow: 0 1px 2px 0 rgba(66, 133, 244, 0.3),
-      0 3px 6px 2px rgba(66, 133, 244, 0.15);
+  box-shadow: 0 1px 2px 0 rgba(var(--GB500-rgb), 0.3),
+      0 3px 6px 2px rgba(var(--GB500-rgb), 0.15);
 }
 
 #bg-sel-footer-toggle-text {
@@ -445,7 +447,7 @@
 }
 
 .bg-sel-tile-bg {
-  background-color: rgb(241, 243, 244);
+  background-color: rgb(var(--GG100-rgb));
   background-size: cover;
   display: inline-block;
   height: 117px;
@@ -483,7 +485,7 @@
 }
 
 .selected-border {
-  border: 2px solid rgba(26, 115, 232, .4);
+  border: 2px solid rgba(var(--GB600-rgb), .4);
   border-radius: 4px;
   box-sizing: border-box;
   height: 100%;
@@ -522,7 +524,7 @@
 }
 
 .bg-sel-tile-title {
-  background-color: rgba(32, 33, 36, 0.71);
+  background-color: rgba(var(--GG900-rgb), 0.71);
   bottom: 0;
   box-sizing: border-box;
   color: #FFF;
@@ -613,7 +615,7 @@
 }
 
 #custom-bg-attr.attr-link:hover {
-  background: rgba(32, 33, 36, .1);
+  background: rgba(var(--GG900-rgb), .1);
 }
 
 .attr1.attr-link,
diff --git a/chrome/browser/resources/local_ntp/custom_links_edit.css b/chrome/browser/resources/local_ntp/custom_links_edit.css
index cb87556..a32dcea 100644
--- a/chrome/browser/resources/local_ntp/custom_links_edit.css
+++ b/chrome/browser/resources/local_ntp/custom_links_edit.css
@@ -15,8 +15,8 @@
   border: none;
   border-radius: 8px;
   bottom: 0;
-  box-shadow:
-      0 1px 3px 0 rgba(60, 64, 67, 0.3), 0 4px 8px 3px rgba(60, 64, 67, 0.15);
+  box-shadow: 0 1px 3px 0 rgba(var(--GG800-rgb), 0.3),
+      0 4px 8px 3px rgba(var(--GG800-rgb), 0.15);
   font-family: 'Roboto', arial, sans-serif;
   margin: auto;
   min-width: 320px;
@@ -34,7 +34,7 @@
 }
 
 #dialog-title {
-  color: rgb(32, 33, 36);
+  color: rgb(var(--GG900-rgb));
   font-family: 'Roboto', arial, sans-serif;
   font-size: 15px;
   line-height: 24px;
@@ -46,7 +46,7 @@
 }
 
 .field-title {
-  color: rgb(95, 99, 104);
+  color: rgb(var(--GG700-rgb));
   font-size: 10px;
   font-weight: 500;
   margin-bottom: 4px;
@@ -57,11 +57,11 @@
 }
 
 input {
-  background-color: rgb(241, 243, 244);
+  background-color: rgb(var(--GG100-rgb));
   border: none;
   border-radius: 4px;
-  caret-color: rgb(26, 115, 232);
-  color: rgb(32, 33, 36);
+  caret-color: rgb(var(--GB600-rgb));
+  color: rgb(var(--GG900-rgb));
   font-family: 'Roboto', arial, sans-serif;
   font-size: 13px;
   height: 32px;
@@ -77,11 +77,11 @@
 }
 
 input::placeholder {
-  color: rgba(32, 33, 36, 0.38);
+  color: rgba(var(--GG900-rgb), 0.38);
 }
 
 .underline {
-  border-bottom: 2px solid rgb(26, 115, 232);
+  border-bottom: 2px solid rgb(var(--GB600-rgb));
   bottom: 0;
   box-sizing: border-box;
   left: 0;
@@ -100,11 +100,11 @@
 }
 
 .field-title.focused {
-  color: rgb(26, 115, 232);
+  color: rgb(var(--GB600-rgb));
 }
 
 .error-msg {
-  color: rgb(217, 48, 37);
+  color: rgb(var(--GR600-rgb));
   display: none;
   font-size: 10px;
   font-weight: 400;
@@ -112,7 +112,7 @@
 }
 
 .invalid label {
-  color: rgb(217, 48, 37);
+  color: rgb(var(--GR600-rgb));
 }
 
 .invalid .error-msg {
@@ -120,7 +120,7 @@
 }
 
 .invalid .underline {
-  border-color: rgb(217, 48, 37);
+  border-color: rgb(var(--GR600-rgb));
   opacity: 1;
   transition: width 180ms ease-out, opacity 120ms ease-in;
   width: 100%;
@@ -159,47 +159,47 @@
 }
 
 button.primary {
-  background-color: rgb(26, 115, 232);
+  background-color: rgb(var(--GB600-rgb));
   color: white;
 }
 
 button.primary:disabled {
-  background-color: rgb(241, 243, 244);
-  color: rgb(128, 134, 139);
+  background-color: rgb(var(--GG100-rgb));
+  color: rgb(var(--GG600-rgb));
 }
 
 button.primary:hover:not(:disabled) {
   background-color: rgb(41, 123, 231);
-  box-shadow: 0 1px 2px 0 rgba(66, 133, 244, 0.3),
-      0 1px 3px 1px rgba(66, 133, 244, 0.15);
+  box-shadow: 0 1px 2px 0 rgba(var(--GB500-rgb), 0.3),
+      0 1px 3px 1px rgba(var(--GB500-rgb), 0.15);
 }
 
 button.primary:active:not(:disabled) {
-  box-shadow: 0 1px 2px 0 rgba(66, 133, 244, 0.3),
-      0 3px 6px 2px rgba(66, 133, 244, 0.15);
+  box-shadow: 0 1px 2px 0 rgba(var(--GB500-rgb), 0.3),
+      0 3px 6px 2px rgba(var(--GB500-rgb), 0.15);
 }
 
 button.secondary {
   background-color: white;
-  border: 1px solid rgb(218, 220, 224);
-  color: rgb(26, 115, 232);
+  border: 1px solid rgb(var(--GG300-rgb));
+  color: rgb(var(--GB600-rgb));
 }
 
 button.secondary:disabled {
-  border-color: rgb(241, 243, 244);
-  color: rgb(128, 134, 139);
+  border-color: rgb(var(--GG100-rgb));
+  color: rgb(var(--GG600-rgb));
 }
 
 button.secondary:hover:not(:disabled) {
-  background-color: rgba(66, 133, 244, 0.04);
-  border-color: rgb(210, 227, 252);
+  background-color: rgba(var(--GB500-rgb), 0.04);
+  border-color: rgb(var(--GB100-rgb));
 }
 
 button.secondary:active:not(:disabled) {
   background-color: white;
   border-color: white;
-  box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.3),
-      0 3px 6px 2px rgba(60, 64, 67, 0.15);
+  box-shadow: 0 1px 2px 0 rgba(var(--GG800-rgb), 0.3),
+      0 3px 6px 2px rgba(var(--GG800-rgb), 0.15);
 }
 
 html:not([dir=rtl]) #cancel {
diff --git a/chrome/browser/resources/local_ntp/custom_links_edit.html b/chrome/browser/resources/local_ntp/custom_links_edit.html
index e2b6a3ac..bba8c4d 100644
--- a/chrome/browser/resources/local_ntp/custom_links_edit.html
+++ b/chrome/browser/resources/local_ntp/custom_links_edit.html
@@ -7,6 +7,7 @@
   <base target="_top">
   <meta charset="utf-8">
   <link rel="stylesheet" type="text/css" href="animations.css">
+  <link rel="stylesheet" type="text/css" href="constants.css">
   <link rel="stylesheet" type="text/css" href="edit.css">
   <script src="utils.js"></script>
   <script src="animations.js"></script>
diff --git a/chrome/browser/resources/local_ntp/local_ntp.css b/chrome/browser/resources/local_ntp/local_ntp.css
index 242f282..9ef38758 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.css
+++ b/chrome/browser/resources/local_ntp/local_ntp.css
@@ -123,7 +123,7 @@
 }
 
 #fakebox {
-  background-color: rgb(241, 243, 244);
+  background-color: rgb(var(--GG100-rgb));
   border-radius: 22px;
   cursor: text;
   font-size: 18px;
@@ -138,7 +138,7 @@
 }
 
 #fakebox:hover {
-  background-color: rgb(232, 234, 237);
+  background-color: rgb(var(--GG200-rgb));
 }
 
 .non-google-page #fakebox-container {
@@ -165,7 +165,7 @@
 
 #fakebox-text {
   bottom: 4px;
-  color: rgb(128, 134, 139);
+  color: rgb(var(--GG600-rgb));
   font-family: 'Roboto', arial, sans-serif;
   font-size: 14px;
   left: 0;
@@ -290,7 +290,7 @@
   -webkit-mask-position: 3px 3px;
   -webkit-mask-repeat: no-repeat;
   -webkit-mask-size: 10px 10px;
-  background-color: rgba(90,90,90,0.7);
+  background-color: rgba(90, 90, 90, 0.7);
   cursor: pointer;
   display: inline-block;
   filter: var(--theme-filter, 'none');
@@ -307,11 +307,11 @@
 }
 
 #mv-notice-x:hover {
-  background-color: rgba(90,90,90,1.0);
+  background-color: rgba(90, 90, 90, 1.0);
 }
 
 #mv-notice-x:active {
-  background-color: rgb(66,133,244);
+  background-color: rgb(var(--GB500-rgb));
 }
 
 .md-icons #mv-notice-x {
@@ -359,7 +359,7 @@
 
 .md-icons #mv-notice {
   background-color: white;
-  border: 1px solid rgb(218, 220, 224);
+  border: 1px solid rgb(var(--GG300-rgb));
   /* Necessary for a "pill" shape. Using 50% creates an oval. */
   border-radius: 16px;
   font-family: 'Roboto', arial, sans-serif;
@@ -379,7 +379,7 @@
 }
 
 .md-icons #mv-notice span {
-  color: rgb(95,99,104);
+  color: rgb(var(--GG700-rgb));
   height: auto;
   line-height: 32px;
   vertical-align: unset;
@@ -397,7 +397,7 @@
 .md-icons #mv-notice-links span {
   /* Necessary for a "pill" shape. Using 50% creates an oval. */
   border-radius: 16px;
-  color: rgb(26, 115, 232);
+  color: rgb(var(--GB600-rgb));
   margin-inline-start: 0;
   padding: 0 16px;
   position: relative;
@@ -410,7 +410,7 @@
 
 .md-icons #mv-notice-links span:hover,
 .md-icons #mv-notice-links span:active {
-  background-color: rgba(26,115,232, 0.1);
+  background-color: rgba(var(--GB600-rgb), 0.1);
   text-decoration: none;
   transition: background-color 200ms;
 }
@@ -556,7 +556,7 @@
 }
 
 input:checked + .toggle {
-  background-color: rgba(26, 115, 232, .5);
+  background-color: rgba(var(--GB600-rgb), .5);
   box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.4);
 }
 
@@ -566,7 +566,7 @@
 
 input:checked + .toggle::before {
   -webkit-transform: translateX(26px);
-  background-color: rgb(26, 115, 232);
+  background-color: rgb(var(--GB600-rgb));
   transform: translateX(26px);
 }
 
@@ -585,10 +585,10 @@
 
 #error-notice {
   background-color: white;
-  border: 1px solid rgb(218, 220, 224);
+  border: 1px solid rgb(var(--GG300-rgb));
   /* Necessary for a "pill" shape. Using 50% creates an oval. */
   border-radius: 16px;
-  color: rgb(217, 48, 37);
+  color: rgb(var(--GR600-rgb));
   display: flex;
   font-family: 'Roboto', arial, sans-serif;
   font-size: 12px;
@@ -640,7 +640,7 @@
 #error-notice-link {
   /* Necessary for a "pill" shape. Using 50% creates an oval. */
   border-radius: 16px;
-  color: rgb(26, 115, 232);
+  color: rgb(var(--GB600-rgb));
   cursor: pointer;
   display: none;
   outline: none;
@@ -659,7 +659,7 @@
 
 #error-notice-link:hover,
 #error-notice-link:active {
-  background-color: rgba(26,115,232, 0.1);
+  background-color: rgba(var(--GB600-rgb), 0.1);
   text-decoration: none;
   transition: background-color 200ms;
 }
@@ -680,10 +680,10 @@
 
 #promo > div {
   background-color: #FFF;
-  border: 1px solid rgb(218, 220, 224);
+  border: 1px solid rgb(var(--GG300-rgb));
   border-radius: 16px;
   box-sizing: border-box;
-  color: rgb(95, 99, 104);
+  color: rgb(var(--GG700-rgb));
   display: inline-block;
   font-family: 'Roboto', arial, sans-serif;
   font-size: 12px;
diff --git a/chrome/browser/resources/local_ntp/local_ntp.html b/chrome/browser/resources/local_ntp/local_ntp.html
index 92d45cf..073e394 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.html
+++ b/chrome/browser/resources/local_ntp/local_ntp.html
@@ -5,6 +5,7 @@
      found in the LICENSE file. -->
 <head>
   <link rel="stylesheet" href="chrome-search://local-ntp/animations.css"></link>
+  <link rel="stylesheet" href="chrome-search://local-ntp/constants.css"></link>
   <link rel="stylesheet" href="chrome-search://local-ntp/custom-backgrounds.css"></link>
   <link rel="stylesheet" href="chrome-search://local-ntp/doodles.css"></link>
   <link rel="stylesheet" href="chrome-search://local-ntp/local-ntp.css"></link>
diff --git a/chrome/browser/resources/local_ntp/most_visited_single.css b/chrome/browser/resources/local_ntp/most_visited_single.css
index dfb0df76..a224468 100644
--- a/chrome/browser/resources/local_ntp/most_visited_single.css
+++ b/chrome/browser/resources/local_ntp/most_visited_single.css
@@ -24,19 +24,6 @@
   --md-title-height: 24px;
   --md-title-max-height: 28px;
 
-  /* Material Design colors */
-  --dark-mode-bg-rgb: 50, 54, 57;
-  --GG900-rgb: 32, 33, 36;
-  --GG800-rgb: 60, 64, 67;
-  --GG700-rgb: 95, 99, 104;
-  --GG600-rgb: 128, 134, 139;
-  --GG500-rgb: 154, 160, 166;
-  --GG400-rgb: 189, 193, 198;
-  --GG300-rgb: 218, 220, 224;
-  --GG200-rgb: 232, 234, 237;
-  --GG100-rgb: 241, 243, 244;
-  --GG50-rgb: 248, 249, 250;
-
   /* May be overridden by themes (on the body element). */
   --tile-title-color: #323232;
 }
@@ -255,7 +242,7 @@
 
 /* Apply when a custom background is set. */
 body.dark-theme .md-tile-container:not(.reorder) .md-title {
-  color: rgb(var(--GG50-rgb));
+  color: rgb(var(--GG050-rgb));
   filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.35));
 }
 
diff --git a/chrome/browser/resources/local_ntp/most_visited_single.html b/chrome/browser/resources/local_ntp/most_visited_single.html
index 4d8b174b..92bf1458 100644
--- a/chrome/browser/resources/local_ntp/most_visited_single.html
+++ b/chrome/browser/resources/local_ntp/most_visited_single.html
@@ -6,6 +6,7 @@
 <head>
   <base target="_top">
   <meta charset="utf-8">
+  <link rel="stylesheet" type="text/css" href="constants.css">
   <link rel="stylesheet" type="text/css" href="single.css">
   <script src="utils.js"></script>
   <script src="single.js"></script>
diff --git a/chrome/browser/resources/local_ntp_resources.grd b/chrome/browser/resources/local_ntp_resources.grd
index 3eae537..a7828b51 100644
--- a/chrome/browser/resources/local_ntp_resources.grd
+++ b/chrome/browser/resources/local_ntp_resources.grd
@@ -15,6 +15,7 @@
       <include name="IDR_CUSTOM_LINKS_EDIT_CSS" file="local_ntp\custom_links_edit.css" type="BINDATA" />
       <include name="IDR_CUSTOM_LINKS_EDIT_JS" file="local_ntp\custom_links_edit.js" type="BINDATA" />
       <include name="IDR_CUSTOM_LINKS_EDIT_MENU_SVG" file="local_ntp\icons\edit_menu.svg" type="BINDATA" />
+      <include name="IDR_LOCAL_NTP_CONSTANTS_CSS" file="local_ntp\constants.css" type="BINDATA" />
       <include name="IDR_LOCAL_NTP_ANIMATIONS_CSS" file="local_ntp\animations.css" flattenhtml="true" type="BINDATA" />
       <include name="IDR_LOCAL_NTP_ANIMATIONS_JS" file="local_ntp\animations.js" flattenhtml="true" type="BINDATA" />
       <include name="IDR_LOCAL_NTP_CSS" file="local_ntp\local_ntp.css" flattenhtml="true" type="BINDATA" />
diff --git a/chrome/browser/resources/omnibox/omnibox.html b/chrome/browser/resources/omnibox/omnibox.html
index c604afdb..60b35f52 100644
--- a/chrome/browser/resources/omnibox/omnibox.html
+++ b/chrome/browser/resources/omnibox/omnibox.html
@@ -149,6 +149,14 @@
             </span>
           </label>
         </div>
+        <div class="row">
+          <label class="checkbox-container">
+            <input id="elide-cells" type="checkbox" accesskey="s">
+            <span>
+              Elide Cell<span class="accesskey">s</span>
+            </span>
+          </label>
+        </div>
       </div>
 
       <div class="top-column">
diff --git a/chrome/browser/resources/omnibox/omnibox_input.js b/chrome/browser/resources/omnibox/omnibox_input.js
index 72b1063..0c82596 100644
--- a/chrome/browser/resources/omnibox/omnibox_input.js
+++ b/chrome/browser/resources/omnibox/omnibox_input.js
@@ -22,6 +22,7 @@
  *   showIncompleteResults: boolean,
  *   showDetails: boolean,
  *   showAllProviders: boolean,
+ *   elideCells: boolean,
  * }}
  */
 let DisplayInputs;
@@ -66,6 +67,7 @@
     ['#show-incomplete-results',
      '#show-details',
      '#show-all-providers',
+     '#elide-cells',
     ]
         .forEach(
             query => this.$$(query).addEventListener(
@@ -207,6 +209,7 @@
       showIncompleteResults: this.$$('#show-incomplete-results').checked,
       showDetails: this.$$('#show-details').checked,
       showAllProviders: this.$$('#show-all-providers').checked,
+      elideCells: this.$$('#elide-cells').checked,
     };
   }
 
@@ -216,6 +219,7 @@
         displayInputs.showIncompleteResults;
     this.$$('#show-details').checked = displayInputs.showDetails;
     this.$$('#show-all-providers').checked = displayInputs.showAllProviders;
+    this.$$('#elide-cells').checked = displayInputs.elideCells;
   }
 
   /** @private */
@@ -279,6 +283,7 @@
       showIncompleteResults: false,
       showDetails: false,
       showAllProviders: true,
+      elideCells: true,
     };
   }
 }
diff --git a/chrome/browser/resources/omnibox/omnibox_output.js b/chrome/browser/resources/omnibox/omnibox_output.js
index ed4dbef..9dacef75 100644
--- a/chrome/browser/resources/omnibox/omnibox_output.js
+++ b/chrome/browser/resources/omnibox/omnibox_output.js
@@ -48,6 +48,7 @@
     updateDisplayInputs(displayInputs) {
       this.displayInputs_ = displayInputs;
       this.updateVisibility_();
+      this.updateEliding_();
     }
 
     /** @param {string} filterText */
@@ -146,6 +147,13 @@
     }
 
     /** @private */
+    updateEliding_() {
+      this.resultsGroups_.forEach(
+          resultsGroup =>
+              resultsGroup.updateEliding(this.displayInputs_.elideCells));
+    }
+
+    /** @private */
     updateFilterHighlights_() {
       this.autocompleteMatches.forEach(match => match.filter(this.filterText_));
     }
@@ -299,6 +307,12 @@
           match => match.updateVisibility(showDetails));
     }
 
+    /** @param {boolean} elideCells */
+    updateEliding(elideCells) {
+      this.autocompleteMatches.forEach(
+          match => match.updateEliding(elideCells));
+    }
+
     /**
      * @private
      * @return {boolean}
@@ -457,6 +471,12 @@
       });
     }
 
+    /** @param {boolean} elideCells */
+    updateEliding(elideCells) {
+      Object.values(this.properties)
+          .forEach(property => property.classList.toggle('elided', elideCells));
+    }
+
     /** @param {string} filterText */
     filter(filterText) {
       this.classList.remove('filtered-highlighted');
diff --git a/chrome/browser/resources/omnibox/output_results_group.css b/chrome/browser/resources/omnibox/output_results_group.css
index 275aab3c..ebc1f06 100644
--- a/chrome/browser/resources/omnibox/output_results_group.css
+++ b/chrome/browser/resources/omnibox/output_results_group.css
@@ -52,7 +52,7 @@
   overflow: hidden;
 }
 
-.body td:not(:hover) {
+.body td.elided:not(:hover) {
   white-space: nowrap;
 }
 
diff --git a/chrome/browser/resources/predictors/predictors.html b/chrome/browser/resources/predictors/predictors.html
index 34b7a0e..731baa20 100644
--- a/chrome/browser/resources/predictors/predictors.html
+++ b/chrome/browser/resources/predictors/predictors.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html i18n-values="dir:textdirection;lang:language">
+<html dir="$i18n{textdirection}" lang="$i18n{language}">
 <head>
   <meta charset="utf-8">
   <title>Predictors</title>
diff --git a/chrome/browser/resources/set_as_default_browser.html b/chrome/browser/resources/set_as_default_browser.html
index 92865ee5..8f65526 100644
--- a/chrome/browser/resources/set_as_default_browser.html
+++ b/chrome/browser/resources/set_as_default_browser.html
@@ -2,15 +2,13 @@
 <!-- Copyright (c) 2012 The Chromium Authors. All rights reserved.
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
-<html i18n-values="dir:textdirection;lang:language">
+<html dir="$i18n{textdirection}" lang="$i18n{language}">
 <head>
 <meta charset="utf-8">
-<title i18n-content="page-title"></title>
+<title>$i18n{pageTitle}</title>
 <script src="chrome://resources/js/cr.js"></script>
-<script src="chrome://resources/js/load_time_data.js"></script>
 <script src="chrome://resources/js/util.js"></script>
 <script src="chrome://resources/js/cr/event_target.js"></script>
-<script src="strings.js"></script>
 <script src="set_as_default_browser.js"></script>
 <link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
 <link rel="stylesheet" href="chrome://resources/css/widgets.css">
@@ -21,21 +19,21 @@
   <div id="metro-setup-overlay" class="page">
     <div>
       <div class="content-area">
-        <h1 i18n-content="flowTitle"></h1>
-        <h2 i18n-content="flowDescription"></h2>
+        <h1>$i18n{flowTitle}</h1>
+        <h2>$i18n{flowDescription}</h2>
       </div>
       <div id="metro-action-box" class="content-area">
         <div class="button-strip">
-          <button id="launch-button" class="custom-appearance"
-              i18n-content="flowNext"></button>
+          <button id="launch-button" class="custom-appearance">
+            $i18n{flowNext}
+          </button>
         </div>
       </div>
     </div>
   </div>
   <div id="chrome-logo-box">
-    <img src="chrome-logo-faded.png" i18n-values="alt:chromeLogoString">
+    <img src="chrome-logo-faded.png" alt="$i18n{chromeLogoString}">
   </div>
 </div>
 </body>
-<script src="chrome://resources/js/i18n_template.js"></script>
 </html>
diff --git a/chrome/browser/resources/settings/autofill_page/autofill_section.js b/chrome/browser/resources/settings/autofill_page/autofill_section.js
index 25bd705..b4d4d46 100644
--- a/chrome/browser/resources/settings/autofill_page/autofill_section.js
+++ b/chrome/browser/resources/settings/autofill_page/autofill_section.js
@@ -141,6 +141,9 @@
 
     // Listen for changes.
     this.autofillManager_.addAddressListChangedListener(setAddressesListener);
+
+    // Record that the user opened the address settings.
+    chrome.metricsPrivate.recordUserAction('AutofillAddressesViewed');
   },
 
   /** @override */
diff --git a/chrome/browser/resources/settings/autofill_page/payments_section.js b/chrome/browser/resources/settings/autofill_page/payments_section.js
index 1ac858d..495f065 100644
--- a/chrome/browser/resources/settings/autofill_page/payments_section.js
+++ b/chrome/browser/resources/settings/autofill_page/payments_section.js
@@ -265,6 +265,9 @@
         this.handleSyncStatus_.bind(this));
     this.addWebUIListener(
         'sync-status-changed', this.handleSyncStatus_.bind(this));
+
+    // Record that the user opened the payments settings.
+    chrome.metricsPrivate.recordUserAction('AutofillCreditCardsViewed');
   },
 
   /** @override */
diff --git a/chrome/browser/resources/settings/multidevice_page/multidevice_constants.js b/chrome/browser/resources/settings/multidevice_page/multidevice_constants.js
index ee6377c0..f60c837 100644
--- a/chrome/browser/resources/settings/multidevice_page/multidevice_constants.js
+++ b/chrome/browser/resources/settings/multidevice_page/multidevice_constants.js
@@ -76,6 +76,7 @@
  *   instantTetheringState: !settings.MultiDeviceFeatureState,
  *   messagesState: !settings.MultiDeviceFeatureState,
  *   smartLockState: !settings.MultiDeviceFeatureState,
+ *   isAndroidSmsPairingComplete: boolean
  * }}
  */
 let MultiDevicePageContentData;
diff --git a/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html b/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html
index 9880744..f93b7cd9 100644
--- a/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html
+++ b/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.html
@@ -85,9 +85,10 @@
               feature="[[MultiDeviceFeature.MESSAGES]]"
               page-content-data="[[pageContentData]]">
             <template is="dom-if"
-                if="[[doesAndroidMessagesRequireSetup_(pageContentData)]]"
+                if="[[doesAndroidMessagesRequireSetUp_(pageContentData)]]"
                 restamp>
-              <paper-button on-click="handleAndroidMessagesButtonClick_"
+              <paper-button disabled$="[[!isSuiteOn(pageContentData)]]"
+                on-click="handleAndroidMessagesButtonClick_"
                   slot="feature-controller">
                 $i18n{multideviceSetupButton}
               </paper-button>
diff --git a/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.js b/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.js
index 9579bd23..990c459 100644
--- a/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.js
+++ b/chrome/browser/resources/settings/multidevice_page/multidevice_subpage.js
@@ -128,8 +128,10 @@
    * @return {boolean}
    * @private
    */
-  doesAndroidMessagesRequireSetup_: function() {
-    return this.getFeatureState(settings.MultiDeviceFeature.MESSAGES) ==
-        settings.MultiDeviceFeatureState.FURTHER_SETUP_REQUIRED;
+  doesAndroidMessagesRequireSetUp_: function() {
+    // The pairing state is preferred over the FeatureState here since
+    // FeatureState.UNAVAILABLE_SUITE_DISABLED is returned when the suite is
+    // disabled, regardless if Messages requires further setup.
+    return !this.pageContentData.isAndroidSmsPairingComplete;
   },
 });
diff --git a/chrome/browser/resources/signin/signin_error/signin_error.html b/chrome/browser/resources/signin/signin_error/signin_error.html
index 37a5c21..b540038 100644
--- a/chrome/browser/resources/signin/signin_error/signin_error.html
+++ b/chrome/browser/resources/signin/signin_error/signin_error.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html i18n-values="dir:textdirection;lang:language" dir="$i18n{textdirection}">
+<html dir="$i18n{textdirection}" lang="$i18n{language}">
   <head>
     <meta charset="utf-8">
     <link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html">
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.js b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.js
index da6586a0..bb8aa03 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.js
@@ -87,7 +87,15 @@
     if (status.isDefault) {
       this.browserProxy_.recordSuccessfullySetDefault();
       this.finished_();
+      return;
     }
+
+    // <if expr="is_macosx">
+    // On Mac OS, we do not get a notification when the default browser changes.
+    // This will fake the notification.
+    window.setTimeout(
+        () => this.browserProxy_.requestDefaultBrowserState(), 100);
+    // </if>
   },
 
   /** @private */
diff --git a/chrome/browser/search/local_ntp_source.cc b/chrome/browser/search/local_ntp_source.cc
index 366724d..7bdfab4 100644
--- a/chrome/browser/search/local_ntp_source.cc
+++ b/chrome/browser/search/local_ntp_source.cc
@@ -111,6 +111,7 @@
 } kResources[] = {
     {"animations.css", IDR_LOCAL_NTP_ANIMATIONS_CSS, "text/css"},
     {"animations.js", IDR_LOCAL_NTP_ANIMATIONS_JS, "application/javascript"},
+    {"constants.css", IDR_LOCAL_NTP_CONSTANTS_CSS, "text/css"},
     {"custom-backgrounds.css", IDR_LOCAL_NTP_CUSTOM_BACKGROUNDS_CSS,
      "text/css"},
     {"custom-backgrounds.js", IDR_LOCAL_NTP_CUSTOM_BACKGROUNDS_JS,
diff --git a/chrome/browser/search/most_visited_iframe_source.cc b/chrome/browser/search/most_visited_iframe_source.cc
index e5d47db..eda19c26 100644
--- a/chrome/browser/search/most_visited_iframe_source.cc
+++ b/chrome/browser/search/most_visited_iframe_source.cc
@@ -39,6 +39,7 @@
 // Used in the single-iframe version and the edit custom links dialog iframe.
 const char kAnimationsCSSPath[] = "/animations.css";
 const char kAnimationsJSPath[] = "/animations.js";
+const char kConstantsCSSPath[] = "/constants.css";
 const char kLocalNTPUtilsJSPath[] = "/utils.js";
 
 }  // namespace
@@ -104,6 +105,8 @@
     SendResource(IDR_CUSTOM_LINKS_ADD_WHITE_SVG, callback);
   } else if (path == kEditMenuSvgPath) {
     SendResource(IDR_CUSTOM_LINKS_EDIT_MENU_SVG, callback);
+  } else if (path == kConstantsCSSPath) {
+    SendResource(IDR_LOCAL_NTP_CONSTANTS_CSS, callback);
   } else if (path == kAnimationsCSSPath) {
     SendResource(IDR_LOCAL_NTP_ANIMATIONS_CSS, callback);
   } else if (path == kAnimationsJSPath) {
@@ -122,6 +125,6 @@
          path == kCommonCSSPath || path == kEditHTMLPath ||
          path == kEditCSSPath || path == kEditJSPath || path == kAddSvgPath ||
          path == kAddWhiteSvgPath || path == kEditMenuSvgPath ||
-         path == kAnimationsCSSPath || path == kAnimationsJSPath ||
-         path == kLocalNTPUtilsJSPath;
+         path == kConstantsCSSPath || path == kAnimationsCSSPath ||
+         path == kAnimationsJSPath || path == kLocalNTPUtilsJSPath;
 }
diff --git a/chrome/browser/signin/dice_browsertest.cc b/chrome/browser/signin/dice_browsertest.cc
index 9bd1ef1..18d9da54 100644
--- a/chrome/browser/signin/dice_browsertest.cc
+++ b/chrome/browser/signin/dice_browsertest.cc
@@ -451,10 +451,10 @@
 
     GetIdentityManager()->AddObserver(this);
     // Wait for the token service to be ready.
-    if (!identity::AreAllCredentialsLoaded(GetIdentityManager())) {
+    if (!GetIdentityManager()->AreRefreshTokensLoaded()) {
       WaitForClosure(&tokens_loaded_quit_closure_);
     }
-    ASSERT_TRUE(identity::AreAllCredentialsLoaded(GetIdentityManager()));
+    ASSERT_TRUE(GetIdentityManager()->AreRefreshTokensLoaded());
 
     AccountReconcilor* reconcilor =
         AccountReconcilorFactory::GetForProfile(browser()->profile());
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 48f33a86..1a8c448 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1082,6 +1082,10 @@
       "webui/app_management/app_management_shelf_delegate_chromeos.h",
       "webui/app_management/app_management_ui.cc",
       "webui/app_management/app_management_ui.h",
+      "webui/bookmarks/bookmarks_message_handler.cc",
+      "webui/bookmarks/bookmarks_message_handler.h",
+      "webui/bookmarks/bookmarks_ui.cc",
+      "webui/bookmarks/bookmarks_ui.h",
       "webui/browsing_history_handler.cc",
       "webui/browsing_history_handler.h",
       "webui/chrome_web_contents_handler.cc",
@@ -1113,10 +1117,6 @@
       "webui/management_ui.h",
       "webui/management_ui_handler.cc",
       "webui/management_ui_handler.h",
-      "webui/bookmarks/bookmarks_message_handler.cc",
-      "webui/bookmarks/bookmarks_message_handler.h",
-      "webui/bookmarks/bookmarks_ui.cc",
-      "webui/bookmarks/bookmarks_ui.h",
       "webui/md_downloads/downloads_list_tracker.cc",
       "webui/md_downloads/downloads_list_tracker.h",
       "webui/md_downloads/md_downloads_dom_handler.cc",
@@ -1508,6 +1508,10 @@
       "views/touch_selection_menu_chromeos.h",
       "views/touch_selection_menu_runner_chromeos.cc",
       "views/touch_selection_menu_runner_chromeos.h",
+      "webui/chromeos/account_manager_welcome_dialog.cc",
+      "webui/chromeos/account_manager_welcome_dialog.h",
+      "webui/chromeos/account_manager_welcome_ui.cc",
+      "webui/chromeos/account_manager_welcome_ui.h",
       "webui/chromeos/assistant_optin/assistant_optin_ui.cc",
       "webui/chromeos/assistant_optin/assistant_optin_ui.h",
       "webui/chromeos/assistant_optin/assistant_optin_utils.cc",
diff --git a/chrome/browser/ui/tabs/tab_menu_model.cc b/chrome/browser/ui/tabs/tab_menu_model.cc
index f6fe2d9..150a1fe 100644
--- a/chrome/browser/ui/tabs/tab_menu_model.cc
+++ b/chrome/browser/ui/tabs/tab_menu_model.cc
@@ -43,6 +43,14 @@
                              IDS_TAB_CXMENU_ADD_TAB_TO_EXISTING_GROUP,
                              add_to_existing_group_submenu_.get());
     }
+
+    for (size_t index = 0; index < affected_indices.size(); index++) {
+      if (tab_strip->GetTabGroupForTab(affected_indices[index]) != nullptr) {
+        AddItemWithStringId(TabStripModel::CommandRemoveFromGroup,
+                            IDS_TAB_CXMENU_REMOVE_TAB_FROM_GROUP);
+        break;
+      }
+    }
   }
   AddSeparator(ui::NORMAL_SEPARATOR);
   AddItemWithStringId(TabStripModel::CommandReload, IDS_TAB_CXMENU_RELOAD);
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc
index c6d6d4f..f37427b 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -1044,6 +1044,25 @@
   return new_indices;
 }
 
+void TabStripModel::RemoveFromGroup(const std::vector<int>& indices) {
+  // Remove each tab from the group it's in, if any. Go from right to left
+  // since tabs may move to the right when ungrouped.
+  for (int i = indices.size() - 1; i >= 0; i--) {
+    const TabGroupData* group = GetTabGroupForTab(indices[i]);
+    if (group == nullptr)
+      continue;
+    // Move the tab until it's the rightmost tab in its group
+    int stepsToMove = 0;
+    while (ContainsIndex(indices[i] + stepsToMove + 1) &&
+           GetTabGroupForTab(indices[i] + stepsToMove + 1) == group) {
+      stepsToMove++;
+    }
+    MoveWebContentsAt(indices[i], indices[i] + stepsToMove, false);
+
+    contents_data_[indices[i] + stepsToMove]->set_group(nullptr);
+  }
+}
+
 // Context menu functions.
 bool TabStripModel::IsContextMenuCommandEnabled(
     int context_index,
@@ -1110,6 +1129,9 @@
     case CommandAddToExistingGroup:
       return true;
 
+    case CommandRemoveFromGroup:
+      return true;
+
     default:
       NOTREACHED();
   }
@@ -1265,6 +1287,13 @@
       break;
     }
 
+    case CommandRemoveFromGroup: {
+      base::RecordAction(UserMetricsAction("TabContextMenu_RemoveFromGroup"));
+      std::vector<int> indices = GetIndicesForCommand(context_index);
+      RemoveFromGroup(indices);
+      break;
+    }
+
     default:
       NOTREACHED();
   }
diff --git a/chrome/browser/ui/tabs/tab_strip_model.h b/chrome/browser/ui/tabs/tab_strip_model.h
index f4e8b20..3626719b 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.h
+++ b/chrome/browser/ui/tabs/tab_strip_model.h
@@ -345,17 +345,25 @@
 
   // Create a new tab group and add the set of tabs pointed to be |indices| to
   // it. Pins all of the tabs if any of them were pinned, and reorders the tabs
-  // so they are contiguous and do not split an existing group in half. This
-  // feature is in development and gated behind a feature flag.
-  // https://crbug.com/915956.
+  // so they are contiguous and do not split an existing group in half.
+  // |indices| must be sorted in ascending order. This feature is in development
+  // and gated behind a feature flag. https://crbug.com/915956.
   void AddToNewGroup(const std::vector<int>& indices);
 
   // Add the set of tabs pointed to by |indices| to the tab group |group|. The
   // tabs take on the pinnedness of the tabs already in the group, and are moved
-  // to immediately follow the tabs already in the group.
+  // to immediately follow the tabs already in the group. |indices| must be
+  // sorted in ascending order. This feature is in development and gated behind
+  // a feature flag. https://crbug.com/915956.
   void AddToExistingGroup(const std::vector<int>& indices,
                           const TabGroupData* group);
 
+  // Removes the set of tabs pointed to by |indices| from the the groups they
+  // are in, if any. The tabs are moved out of the group if necessary. |indices|
+  // must be sorted in ascending order. This feature is in development and gated
+  // behind a feature flag. https://crbug.com/915956.
+  void RemoveFromGroup(const std::vector<int>& indices);
+
   // View API //////////////////////////////////////////////////////////////////
 
   // Context menu functions. Tab groups uses command ids following CommandLast
@@ -376,6 +384,7 @@
     CommandBookmarkAllTabs,
     CommandAddToNewGroup,
     CommandAddToExistingGroup,
+    CommandRemoveFromGroup,
     CommandLast
   };
 
diff --git a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
index e6f725a..0b43d7e 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
@@ -2927,3 +2927,107 @@
   strip.ActivateTabAt(0, true);
   strip.CloseAllTabs();
 }
+
+TEST_F(TabStripModelTest, RemoveTabFromGroupNoopForUngroupedTab) {
+  TabStripDummyDelegate delegate;
+  TabStripModel strip(&delegate, profile());
+  strip.AppendWebContents(CreateWebContents(), false);
+
+  strip.RemoveFromGroup({0});
+
+  strip.ActivateTabAt(0, true);
+  strip.CloseAllTabs();
+}
+
+TEST_F(TabStripModelTest, RemoveTabFromGroup) {
+  TabStripDummyDelegate delegate;
+  TabStripModel strip(&delegate, profile());
+  strip.AppendWebContents(CreateWebContents(), false);
+  strip.AddToNewGroup({0});
+
+  strip.RemoveFromGroup({0});
+
+  EXPECT_EQ(strip.GetTabGroupForTab(0), nullptr);
+
+  strip.ActivateTabAt(0, true);
+  strip.CloseAllTabs();
+}
+
+TEST_F(TabStripModelTest, RemoveTabFromGroupReorders) {
+  TabStripDummyDelegate delegate;
+  TabStripModel strip(&delegate, profile());
+  strip.AppendWebContents(CreateWebContents(), false);
+  strip.AppendWebContents(CreateWebContents(), false);
+  std::vector<WebContents*> orig{strip.GetWebContentsAt(0),
+                                 strip.GetWebContentsAt(1)};
+  strip.AddToNewGroup({0, 1});
+
+  strip.RemoveFromGroup({0});
+
+  EXPECT_EQ(strip.GetTabGroupForTab(1), nullptr);
+  EXPECT_NE(strip.GetTabGroupForTab(0), nullptr);
+  EXPECT_EQ(strip.GetWebContentsAt(0), orig[1]);
+  EXPECT_EQ(strip.GetWebContentsAt(1), orig[0]);
+
+  strip.ActivateTabAt(0, true);
+  strip.CloseAllTabs();
+}
+
+TEST_F(TabStripModelTest, RemoveTabFromGroupMaintainsOrderOfSelectedTabs) {
+  TabStripDummyDelegate delegate;
+  TabStripModel strip(&delegate, profile());
+  strip.AppendWebContents(CreateWebContents(), false);
+  strip.AppendWebContents(CreateWebContents(), false);
+  strip.AppendWebContents(CreateWebContents(), false);
+  strip.AppendWebContents(CreateWebContents(), false);
+  std::vector<WebContents*> orig{
+      strip.GetWebContentsAt(0), strip.GetWebContentsAt(1),
+      strip.GetWebContentsAt(2), strip.GetWebContentsAt(3)};
+  strip.AddToNewGroup({0, 1, 2, 3});
+
+  strip.RemoveFromGroup({0, 2});
+
+  EXPECT_NE(strip.GetTabGroupForTab(0), nullptr);
+  EXPECT_NE(strip.GetTabGroupForTab(1), nullptr);
+  EXPECT_EQ(strip.GetTabGroupForTab(2), nullptr);
+  EXPECT_EQ(strip.GetTabGroupForTab(3), nullptr);
+  EXPECT_EQ(strip.GetWebContentsAt(0), orig[1]);
+  EXPECT_EQ(strip.GetWebContentsAt(1), orig[3]);
+  EXPECT_EQ(strip.GetWebContentsAt(2), orig[0]);
+  EXPECT_EQ(strip.GetWebContentsAt(3), orig[2]);
+
+  strip.ActivateTabAt(0, true);
+  strip.CloseAllTabs();
+}
+
+TEST_F(TabStripModelTest, RemoveTabFromGroupMixtureOfGroups) {
+  TabStripDummyDelegate delegate;
+  TabStripModel strip(&delegate, profile());
+  strip.AppendWebContents(CreateWebContents(), false);
+  strip.AppendWebContents(CreateWebContents(), false);
+  strip.AppendWebContents(CreateWebContents(), false);
+  strip.AppendWebContents(CreateWebContents(), false);
+  strip.AppendWebContents(CreateWebContents(), false);
+  std::vector<WebContents*> orig{
+      strip.GetWebContentsAt(0), strip.GetWebContentsAt(1),
+      strip.GetWebContentsAt(2), strip.GetWebContentsAt(3),
+      strip.GetWebContentsAt(4)};
+  strip.AddToNewGroup({0, 1});
+  strip.AddToNewGroup({2, 3});
+
+  strip.RemoveFromGroup({0, 3, 4});
+
+  EXPECT_NE(strip.GetTabGroupForTab(0), nullptr);
+  EXPECT_EQ(strip.GetTabGroupForTab(1), nullptr);
+  EXPECT_NE(strip.GetTabGroupForTab(2), nullptr);
+  EXPECT_EQ(strip.GetTabGroupForTab(3), nullptr);
+  EXPECT_EQ(strip.GetTabGroupForTab(4), nullptr);
+  EXPECT_EQ(strip.GetWebContentsAt(0), orig[1]);
+  EXPECT_EQ(strip.GetWebContentsAt(1), orig[0]);
+  EXPECT_EQ(strip.GetWebContentsAt(2), orig[2]);
+  EXPECT_EQ(strip.GetWebContentsAt(3), orig[3]);
+  EXPECT_EQ(strip.GetWebContentsAt(4), orig[4]);
+
+  strip.ActivateTabAt(0, true);
+  strip.CloseAllTabs();
+}
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.cc b/chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.cc
index 00e3a5c..655e819 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.cc
@@ -48,7 +48,7 @@
 // Generates a bookmark drag and drop chip image.
 class BookmarkDragImageSource : public gfx::CanvasImageSource {
  public:
-  // These DIP measurements come from the MD Bookmarks Drag Drop spec.
+  // These DIP measurements come from the Bookmarks Drag Drop spec.
   static constexpr int kContainerWidth = 172;
   static constexpr int kContainerHeight = 40;
   static constexpr int kContainerRadius = kContainerHeight / 2;
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
index 7c3dadb..5862e9f 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
@@ -66,10 +66,9 @@
 }
 
 void IconLabelBubbleView::SeparatorView::OnPaint(gfx::Canvas* canvas) {
-  const SkColor plain_text_color = owner_->GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_TextfieldDefaultColor);
+  const SkColor background_color = owner_->GetParentBackgroundColor();
   const SkColor separator_color =
-      SkColorSetA(color_utils::GetColorWithMaxContrast(plain_text_color), 0x66);
+      SkColorSetA(color_utils::GetColorWithMaxContrast(background_color), 0x69);
   const float x = GetLocalBounds().right() -
                   owner_->GetEndPaddingWithSeparator() -
                   1.0f / canvas->image_scale();
@@ -181,6 +180,11 @@
   label_->SetFontList(font_list);
 }
 
+SkColor IconLabelBubbleView::GetParentBackgroundColor() const {
+  return GetNativeTheme()->GetSystemColor(
+      ui::NativeTheme::kColorId_TextfieldDefaultBackground);
+}
+
 bool IconLabelBubbleView::ShouldShowSeparator() const {
   return ShouldShowLabel();
 }
@@ -405,11 +409,6 @@
     PreferredSizeChanged();
 }
 
-SkColor IconLabelBubbleView::GetParentBackgroundColor() const {
-  return GetNativeTheme()->GetSystemColor(
-      ui::NativeTheme::kColorId_TextfieldDefaultBackground);
-}
-
 gfx::Size IconLabelBubbleView::GetSizeForLabelWidth(int label_width) const {
   gfx::Size size(image_->GetPreferredSize());
   size.Enlarge(GetInsets().left() + GetWidthBetweenIconAndSeparator() +
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
index 95e2681..84982c1 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
@@ -82,6 +82,9 @@
   const views::ImageView* GetImageView() const { return image_; }
   views::ImageView* GetImageView() { return image_; }
 
+  // Returns the color of the IconLabelBubbleView's surrounding context.
+  SkColor GetParentBackgroundColor() const;
+
   // Exposed for testing.
   SeparatorView* separator_view() const { return separator_view_; }
 
@@ -155,8 +158,6 @@
 
   const gfx::FontList& font_list() const { return label_->font_list(); }
 
-  SkColor GetParentBackgroundColor() const;
-
   gfx::Size GetSizeForLabelWidth(int label_width) const;
 
   // Set up for icons that animate their labels in and then out.
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index 8ab5193..8fb246e5 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -54,6 +54,8 @@
 #include "ui/base/models/simple_menu_model.h"
 #include "ui/compositor/layer.h"
 #include "ui/events/event.h"
+#include "ui/gfx/animation/animation_delegate.h"
+#include "ui/gfx/animation/multi_animation.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/font_list.h"
 #include "ui/gfx/geometry/insets.h"
@@ -121,6 +123,52 @@
 
 }  // namespace
 
+// Animation chosen to match the default values in the edwardjung prototype.
+class OmniboxViewViews::PathFadeAnimation : public gfx::AnimationDelegate {
+ public:
+  PathFadeAnimation(OmniboxViewViews* view, SkColor starting_color)
+      : view_(view),
+        starting_color_(starting_color),
+        animation_(
+            {
+                gfx::MultiAnimation::Part(4000, gfx::Tween::ZERO),
+                gfx::MultiAnimation::Part(300, gfx::Tween::FAST_OUT_SLOW_IN),
+            },
+            gfx::MultiAnimation::GetDefaultTimerInterval()) {
+    DCHECK(view_);
+
+    animation_.set_delegate(this);
+    animation_.set_continuous(false);
+  }
+
+  // Starts the animation over |path_bounds|. The caller is responsible for
+  // calling Stop() if the text changes and |path_bounds| is no longer valid.
+  void Start(const gfx::Range& path_bounds) {
+    path_bounds_ = path_bounds;
+    animation_.Start();
+  }
+
+  void Stop() { animation_.Stop(); }
+
+  // gfx::AnimationDelegate:
+  void AnimationProgressed(const gfx::Animation* animation) override {
+    DCHECK(!view_->model()->user_input_in_progress());
+
+    SkColor color = gfx::Tween::ColorValueBetween(
+        animation->GetCurrentValue(), starting_color_, SK_ColorTRANSPARENT);
+    view_->ApplyColor(color, path_bounds_);
+  }
+
+ private:
+  // Non-owning pointer. |view_| must always outlive this class.
+  OmniboxViewViews* view_;
+  SkColor starting_color_;
+
+  // The path text range we are fading.
+  gfx::Range path_bounds_;
+
+  gfx::MultiAnimation animation_;
+};
 
 // OmniboxViewViews -----------------------------------------------------------
 
@@ -147,6 +195,15 @@
       scoped_template_url_service_observer_(this) {
   set_id(VIEW_ID_OMNIBOX);
   SetFontList(font_list);
+
+  if (base::FeatureList::IsEnabled(
+          omnibox::kHideSteadyStateUrlPathQueryAndRef)) {
+    // The animation only applies when the path is dimmed to begin with.
+    SkColor starting_color =
+        location_bar_view_->GetColor(OmniboxPart::LOCATION_BAR_TEXT_DIMMED);
+    path_fade_animation_ =
+        std::make_unique<PathFadeAnimation>(this, starting_color);
+  }
 }
 
 OmniboxViewViews::~OmniboxViewViews() {
@@ -259,14 +316,35 @@
   if (!location_bar_view_)
     return;
 
+  // Cancel any existing path fading animation. The path style will be reset
+  // in the following lines, so there should be no ill effects from cancelling
+  // the animation midway.
+  if (path_fade_animation_)
+    path_fade_animation_->Stop();
+
   // If the current contents is a URL, turn on special URL rendering mode in
   // RenderText.
   bool text_is_url = model()->CurrentTextIsURL();
   GetRenderText()->SetDirectionalityMode(
       text_is_url ? gfx::DIRECTIONALITY_AS_URL : gfx::DIRECTIONALITY_FROM_TEXT);
   SetStyle(gfx::STRIKE, false);
-  UpdateTextStyle(text(), text_is_url,
-                  model()->client()->GetSchemeClassifier());
+
+  base::string16 text = GetText();
+  bool path_eligible_for_fading = UpdateTextStyle(
+      text, text_is_url, model()->client()->GetSchemeClassifier());
+
+  // Only fade the path when everything but the host is de-emphasized.
+  if (path_fade_animation_ && path_eligible_for_fading && !HasFocus() &&
+      !model()->user_input_in_progress()) {
+    url::Component scheme, host;
+    AutocompleteInput::ParseForEmphasizeComponents(
+        text, model()->client()->GetSchemeClassifier(), &scheme, &host);
+    gfx::Range path_bounds(host.end(), text.size());
+
+    // Whenever the text changes, EmphasizeURLComponents is called again, and
+    // the animation is reset with a new |path_bounds|.
+    path_fade_animation_->Start(path_bounds);
+  }
 }
 
 void OmniboxViewViews::Update() {
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.h b/chrome/browser/ui/views/omnibox/omnibox_view_views.h
index f690819..0890b887 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.h
@@ -138,6 +138,8 @@
   FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsTest, FriendlyAccessibleLabel);
   FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsTest, DoNotNavigateOnDrop);
 
+  class PathFadeAnimation;
+
   enum class UnelisionGesture {
     HOME_KEY_PRESSED,
     MOUSE_RELEASE,
@@ -283,6 +285,9 @@
 
   std::unique_ptr<OmniboxPopupContentsView> popup_view_;
 
+  // Animation used to fade out the path under some elision settings.
+  std::unique_ptr<PathFadeAnimation> path_fade_animation_;
+
   security_state::SecurityLevel security_level_;
 
   // Selection persisted across temporary text changes, like popup suggestions.
diff --git a/chrome/browser/ui/webui/bookmarks/bookmarks_browsertest.cc b/chrome/browser/ui/webui/bookmarks/bookmarks_browsertest.cc
index 0da10a7..a2d09e1 100644
--- a/chrome/browser/ui/webui/bookmarks/bookmarks_browsertest.cc
+++ b/chrome/browser/ui/webui/bookmarks/bookmarks_browsertest.cc
@@ -11,35 +11,34 @@
 #include "components/bookmarks/common/bookmark_pref_names.h"
 #include "components/prefs/pref_service.h"
 
-MdBookmarksBrowserTest::MdBookmarksBrowserTest() {}
+BookmarksBrowserTest::BookmarksBrowserTest() {}
 
-MdBookmarksBrowserTest::~MdBookmarksBrowserTest() {}
+BookmarksBrowserTest::~BookmarksBrowserTest() {}
 
-void MdBookmarksBrowserTest::RegisterMessages() {
+void BookmarksBrowserTest::RegisterMessages() {
   web_ui()->RegisterMessageCallback(
       "testSetIncognito",
-      base::BindRepeating(
-          &MdBookmarksBrowserTest::HandleSetIncognitoAvailability,
-          base::Unretained(this)));
+      base::BindRepeating(&BookmarksBrowserTest::HandleSetIncognitoAvailability,
+                          base::Unretained(this)));
   web_ui()->RegisterMessageCallback(
       "testSetCanEdit",
-      base::BindRepeating(&MdBookmarksBrowserTest::HandleSetCanEditBookmarks,
+      base::BindRepeating(&BookmarksBrowserTest::HandleSetCanEditBookmarks,
                           base::Unretained(this)));
 }
 
-void MdBookmarksBrowserTest::SetIncognitoAvailability(int availability) {
+void BookmarksBrowserTest::SetIncognitoAvailability(int availability) {
   ASSERT_TRUE(availability >= 0 &&
               availability < IncognitoModePrefs::AVAILABILITY_NUM_TYPES);
   browser()->profile()->GetPrefs()->SetInteger(
       prefs::kIncognitoModeAvailability, availability);
 }
 
-void MdBookmarksBrowserTest::SetCanEditBookmarks(bool canEdit) {
+void BookmarksBrowserTest::SetCanEditBookmarks(bool canEdit) {
   browser()->profile()->GetPrefs()->SetBoolean(
       bookmarks::prefs::kEditBookmarksEnabled, canEdit);
 }
 
-void MdBookmarksBrowserTest::HandleSetIncognitoAvailability(
+void BookmarksBrowserTest::HandleSetIncognitoAvailability(
     const base::ListValue* args) {
   AllowJavascript();
 
@@ -54,7 +53,7 @@
   ResolveJavascriptCallback(*callback_id, base::Value());
 }
 
-void MdBookmarksBrowserTest::HandleSetCanEditBookmarks(
+void BookmarksBrowserTest::HandleSetCanEditBookmarks(
     const base::ListValue* args) {
   AllowJavascript();
 
@@ -69,6 +68,6 @@
   ResolveJavascriptCallback(*callback_id, base::Value());
 }
 
-content::WebUIMessageHandler* MdBookmarksBrowserTest::GetMockMessageHandler() {
+content::WebUIMessageHandler* BookmarksBrowserTest::GetMockMessageHandler() {
   return this;
 }
diff --git a/chrome/browser/ui/webui/bookmarks/bookmarks_browsertest.h b/chrome/browser/ui/webui/bookmarks/bookmarks_browsertest.h
index dc07976..d15170b 100644
--- a/chrome/browser/ui/webui/bookmarks/bookmarks_browsertest.h
+++ b/chrome/browser/ui/webui/bookmarks/bookmarks_browsertest.h
@@ -8,11 +8,11 @@
 #include "chrome/test/base/web_ui_browser_test.h"
 #include "content/public/browser/web_ui_message_handler.h"
 
-class MdBookmarksBrowserTest : public WebUIBrowserTest,
-                               public content::WebUIMessageHandler {
+class BookmarksBrowserTest : public WebUIBrowserTest,
+                             public content::WebUIMessageHandler {
  public:
-  MdBookmarksBrowserTest();
-  ~MdBookmarksBrowserTest() override;
+  BookmarksBrowserTest();
+  ~BookmarksBrowserTest() override;
 
   void SetIncognitoAvailability(int availability);
   void SetCanEditBookmarks(bool canEdit);
@@ -27,7 +27,7 @@
   // WebUIBrowserTest:
   content::WebUIMessageHandler* GetMockMessageHandler() override;
 
-  DISALLOW_COPY_AND_ASSIGN(MdBookmarksBrowserTest);
+  DISALLOW_COPY_AND_ASSIGN(BookmarksBrowserTest);
 };
 
 #endif  // CHROME_BROWSER_UI_WEBUI_BOOKMARKS_BOOKMARKS_BROWSERTEST_H_
diff --git a/chrome/browser/ui/webui/bookmarks/bookmarks_ui.cc b/chrome/browser/ui/webui/bookmarks/bookmarks_ui.cc
index 64b71a85..0f540e8 100644
--- a/chrome/browser/ui/webui/bookmarks/bookmarks_ui.cc
+++ b/chrome/browser/ui/webui/bookmarks/bookmarks_ui.cc
@@ -40,7 +40,7 @@
   source->AddString(message, str);
 }
 
-content::WebUIDataSource* CreateMdBookmarksUIHTMLSource(Profile* profile) {
+content::WebUIDataSource* CreateBookmarksUIHTMLSource(Profile* profile) {
   content::WebUIDataSource* source =
       content::WebUIDataSource::Create(chrome::kChromeUIBookmarksHost);
 
@@ -57,12 +57,11 @@
 
   // Localized strings (alphabetical order).
   AddLocalizedString(source, "addBookmarkTitle",
-                     IDS_MD_BOOKMARK_MANAGER_ADD_BOOKMARK_TITLE);
+                     IDS_BOOKMARK_MANAGER_ADD_BOOKMARK_TITLE);
   AddLocalizedString(source, "addFolderTitle",
-                     IDS_MD_BOOKMARK_MANAGER_ADD_FOLDER_TITLE);
+                     IDS_BOOKMARK_MANAGER_ADD_FOLDER_TITLE);
   AddLocalizedString(source, "cancel", IDS_CANCEL);
-  AddLocalizedString(source, "clearSearch",
-                     IDS_MD_BOOKMARK_MANAGER_CLEAR_SEARCH);
+  AddLocalizedString(source, "clearSearch", IDS_BOOKMARK_MANAGER_CLEAR_SEARCH);
   AddLocalizedString(source, "delete", IDS_DELETE);
   AddLocalizedString(source, "editBookmarkTitle", IDS_BOOKMARK_EDITOR_TITLE);
   AddLocalizedString(source, "editDialogInvalidUrl",
@@ -71,156 +70,144 @@
                      IDS_BOOKMARK_MANAGER_NAME_INPUT_PLACE_HOLDER);
   AddLocalizedString(source, "editDialogUrlInput",
                      IDS_BOOKMARK_MANAGER_URL_INPUT_PLACE_HOLDER);
-  AddLocalizedString(source, "emptyList", IDS_MD_BOOKMARK_MANAGER_EMPTY_LIST);
+  AddLocalizedString(source, "emptyList", IDS_BOOKMARK_MANAGER_EMPTY_LIST);
   AddLocalizedString(source, "emptyUnmodifiableList",
-                     IDS_MD_BOOKMARK_MANAGER_EMPTY_UNMODIFIABLE_LIST);
-  AddLocalizedString(source, "folderLabel",
-                     IDS_MD_BOOKMARK_MANAGER_FOLDER_LABEL);
+                     IDS_BOOKMARK_MANAGER_EMPTY_UNMODIFIABLE_LIST);
+  AddLocalizedString(source, "folderLabel", IDS_BOOKMARK_MANAGER_FOLDER_LABEL);
   AddLocalizedString(source, "itemsSelected",
-                     IDS_MD_BOOKMARK_MANAGER_ITEMS_SELECTED);
-  AddLocalizedString(source, "listAxLabel",
-                     IDS_MD_BOOKMARK_MANAGER_LIST_AX_LABEL);
+                     IDS_BOOKMARK_MANAGER_ITEMS_SELECTED);
+  AddLocalizedString(source, "listAxLabel", IDS_BOOKMARK_MANAGER_LIST_AX_LABEL);
   AddLocalizedString(source, "managedByOrg", IDS_MANAGED_BY_ORG_WITH_HYPERLINK);
   AddLocalizedString(source, "menuAddBookmark",
-                     IDS_MD_BOOKMARK_MANAGER_MENU_ADD_BOOKMARK);
+                     IDS_BOOKMARK_MANAGER_MENU_ADD_BOOKMARK);
   AddLocalizedString(source, "menuAddFolder",
-                     IDS_MD_BOOKMARK_MANAGER_MENU_ADD_FOLDER);
-  AddLocalizedString(source, "menuCopyURL",
-                     IDS_MD_BOOKMARK_MANAGER_MENU_COPY_URL);
+                     IDS_BOOKMARK_MANAGER_MENU_ADD_FOLDER);
+  AddLocalizedString(source, "menuCopyURL", IDS_BOOKMARK_MANAGER_MENU_COPY_URL);
   AddLocalizedString(source, "menuDelete", IDS_DELETE);
   AddLocalizedString(source, "menuEdit", IDS_EDIT);
-  AddLocalizedString(source, "menuExport", IDS_MD_BOOKMARK_MANAGER_MENU_EXPORT);
+  AddLocalizedString(source, "menuExport", IDS_BOOKMARK_MANAGER_MENU_EXPORT);
   AddLocalizedString(source, "menuHelpCenter",
-                     IDS_MD_BOOKMARK_MANAGER_MENU_HELP_CENTER);
-  AddLocalizedString(source, "menuImport", IDS_MD_BOOKMARK_MANAGER_MENU_IMPORT);
+                     IDS_BOOKMARK_MANAGER_MENU_HELP_CENTER);
+  AddLocalizedString(source, "menuImport", IDS_BOOKMARK_MANAGER_MENU_IMPORT);
   AddLocalizedString(source, "menuOpenAllNewTab",
-                     IDS_MD_BOOKMARK_MANAGER_MENU_OPEN_ALL);
+                     IDS_BOOKMARK_MANAGER_MENU_OPEN_ALL);
   AddLocalizedString(source, "menuOpenAllNewWindow",
-                     IDS_MD_BOOKMARK_MANAGER_MENU_OPEN_ALL_NEW_WINDOW);
+                     IDS_BOOKMARK_MANAGER_MENU_OPEN_ALL_NEW_WINDOW);
   AddLocalizedString(source, "menuOpenAllIncognito",
-                     IDS_MD_BOOKMARK_MANAGER_MENU_OPEN_ALL_INCOGNITO);
+                     IDS_BOOKMARK_MANAGER_MENU_OPEN_ALL_INCOGNITO);
   AddLocalizedString(source, "menuOpenNewTab",
-                     IDS_MD_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_TAB);
+                     IDS_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_TAB);
   AddLocalizedString(source, "menuOpenNewWindow",
-                     IDS_MD_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_WINDOW);
+                     IDS_BOOKMARK_MANAGER_MENU_OPEN_IN_NEW_WINDOW);
   AddLocalizedString(source, "menuOpenIncognito",
-                     IDS_MD_BOOKMARK_MANAGER_MENU_OPEN_INCOGNITO);
-  AddLocalizedString(source, "menuRename", IDS_MD_BOOKMARK_MANAGER_MENU_RENAME);
+                     IDS_BOOKMARK_MANAGER_MENU_OPEN_INCOGNITO);
+  AddLocalizedString(source, "menuRename", IDS_BOOKMARK_MANAGER_MENU_RENAME);
   AddLocalizedString(source, "menuShowInFolder",
-                     IDS_MD_BOOKMARK_MANAGER_MENU_SHOW_IN_FOLDER);
-  AddLocalizedString(source, "menuSort", IDS_MD_BOOKMARK_MANAGER_MENU_SORT);
+                     IDS_BOOKMARK_MANAGER_MENU_SHOW_IN_FOLDER);
+  AddLocalizedString(source, "menuSort", IDS_BOOKMARK_MANAGER_MENU_SORT);
   AddLocalizedString(source, "moreActionsButtonTitle",
-                     IDS_MD_BOOKMARK_MANAGER_MORE_ACTIONS);
+                     IDS_BOOKMARK_MANAGER_MORE_ACTIONS);
   AddLocalizedString(source, "moreActionsButtonAxLabel",
-                     IDS_MD_BOOKMARK_MANAGER_MORE_ACTIONS_AX_LABEL);
+                     IDS_BOOKMARK_MANAGER_MORE_ACTIONS_AX_LABEL);
   AddLocalizedString(source, "noSearchResults", IDS_SEARCH_NO_RESULTS);
   AddLocalizedString(source, "openDialogBody",
                      IDS_BOOKMARK_BAR_SHOULD_OPEN_ALL);
   AddLocalizedString(source, "openDialogConfirm",
-                     IDS_MD_BOOKMARK_MANAGER_OPEN_DIALOG_CONFIRM);
+                     IDS_BOOKMARK_MANAGER_OPEN_DIALOG_CONFIRM);
   AddLocalizedString(source, "openDialogTitle",
-                     IDS_MD_BOOKMARK_MANAGER_OPEN_DIALOG_TITLE);
+                     IDS_BOOKMARK_MANAGER_OPEN_DIALOG_TITLE);
   AddLocalizedString(source, "organizeButtonTitle",
                      IDS_BOOKMARK_MANAGER_ORGANIZE_MENU);
   AddLocalizedString(source, "renameFolderTitle",
-                     IDS_MD_BOOKMARK_MANAGER_FOLDER_RENAME_TITLE);
+                     IDS_BOOKMARK_MANAGER_FOLDER_RENAME_TITLE);
   AddLocalizedString(source, "searchPrompt",
                      IDS_BOOKMARK_MANAGER_SEARCH_BUTTON);
   AddLocalizedString(source, "sidebarAxLabel",
-                     IDS_MD_BOOKMARK_MANAGER_SIDEBAR_AX_LABEL);
+                     IDS_BOOKMARK_MANAGER_SIDEBAR_AX_LABEL);
   AddLocalizedString(source, "sidebarNodeCollapseAxLabel",
-                     IDS_MD_BOOKMARK_MANAGER_SIDEBAR_NODE_COLLAPSE_AX_LABEL);
+                     IDS_BOOKMARK_MANAGER_SIDEBAR_NODE_COLLAPSE_AX_LABEL);
   AddLocalizedString(source, "sidebarNodeExpandAxLabel",
-                     IDS_MD_BOOKMARK_MANAGER_SIDEBAR_NODE_EXPAND_AX_LABEL);
+                     IDS_BOOKMARK_MANAGER_SIDEBAR_NODE_EXPAND_AX_LABEL);
   AddLocalizedString(source, "searchCleared", IDS_SEARCH_CLEARED);
   AddLocalizedString(source, "searchResults", IDS_SEARCH_RESULTS);
   AddLocalizedString(source, "saveEdit", IDS_SAVE);
-  AddLocalizedString(source, "title", IDS_MD_BOOKMARK_MANAGER_TITLE);
+  AddLocalizedString(source, "title", IDS_BOOKMARK_MANAGER_TITLE);
   AddLocalizedString(source, "toastFolderSorted",
-                     IDS_MD_BOOKMARK_MANAGER_TOAST_FOLDER_SORTED);
+                     IDS_BOOKMARK_MANAGER_TOAST_FOLDER_SORTED);
   AddLocalizedString(source, "toastItemCopied",
-                     IDS_MD_BOOKMARK_MANAGER_TOAST_ITEM_COPIED);
+                     IDS_BOOKMARK_MANAGER_TOAST_ITEM_COPIED);
   AddLocalizedString(source, "toastItemDeleted",
-                     IDS_MD_BOOKMARK_MANAGER_TOAST_ITEM_DELETED);
+                     IDS_BOOKMARK_MANAGER_TOAST_ITEM_DELETED);
   AddLocalizedString(source, "toastUrlCopied",
-                     IDS_MD_BOOKMARK_MANAGER_TOAST_URL_COPIED);
+                     IDS_BOOKMARK_MANAGER_TOAST_URL_COPIED);
   AddLocalizedString(source, "undo", IDS_BOOKMARK_BAR_UNDO);
 
   // Resources.
   source->AddResourcePath("images/folder_open.svg",
-                          IDR_MD_BOOKMARKS_IMAGES_FOLDER_OPEN_SVG);
-  source->AddResourcePath("images/folder.svg",
-                          IDR_MD_BOOKMARKS_IMAGES_FOLDER_SVG);
+                          IDR_BOOKMARKS_IMAGES_FOLDER_OPEN_SVG);
+  source->AddResourcePath("images/folder.svg", IDR_BOOKMARKS_IMAGES_FOLDER_SVG);
 #if BUILDFLAG(OPTIMIZE_WEBUI)
-  source->AddResourcePath("crisper.js", IDR_MD_BOOKMARKS_CRISPER_JS);
+  source->AddResourcePath("crisper.js", IDR_BOOKMARKS_CRISPER_JS);
   source->SetDefaultResource(
       base::FeatureList::IsEnabled(features::kWebUIPolymer2)
-          ? IDR_MD_BOOKMARKS_VULCANIZED_P2_HTML
-          : IDR_MD_BOOKMARKS_VULCANIZED_HTML);
+          ? IDR_BOOKMARKS_VULCANIZED_P2_HTML
+          : IDR_BOOKMARKS_VULCANIZED_HTML);
   source->UseGzip(base::BindRepeating([](const std::string& path) {
     return path != "images/folder_open.svg" && path != "images/folder.svg";
   }));
 #else
-  source->AddResourcePath("actions.html", IDR_MD_BOOKMARKS_ACTIONS_HTML);
-  source->AddResourcePath("actions.js", IDR_MD_BOOKMARKS_ACTIONS_JS);
-  source->AddResourcePath("api_listener.html",
-                          IDR_MD_BOOKMARKS_API_LISTENER_HTML);
-  source->AddResourcePath("api_listener.js", IDR_MD_BOOKMARKS_API_LISTENER_JS);
-  source->AddResourcePath("app.html", IDR_MD_BOOKMARKS_APP_HTML);
-  source->AddResourcePath("app.js", IDR_MD_BOOKMARKS_APP_JS);
+  source->AddResourcePath("actions.html", IDR_BOOKMARKS_ACTIONS_HTML);
+  source->AddResourcePath("actions.js", IDR_BOOKMARKS_ACTIONS_JS);
+  source->AddResourcePath("api_listener.html", IDR_BOOKMARKS_API_LISTENER_HTML);
+  source->AddResourcePath("api_listener.js", IDR_BOOKMARKS_API_LISTENER_JS);
+  source->AddResourcePath("app.html", IDR_BOOKMARKS_APP_HTML);
+  source->AddResourcePath("app.js", IDR_BOOKMARKS_APP_JS);
   source->AddResourcePath("command_manager.html",
-                          IDR_MD_BOOKMARKS_COMMAND_MANAGER_HTML);
+                          IDR_BOOKMARKS_COMMAND_MANAGER_HTML);
   source->AddResourcePath("command_manager.js",
-                          IDR_MD_BOOKMARKS_COMMAND_MANAGER_JS);
-  source->AddResourcePath("constants.html", IDR_MD_BOOKMARKS_CONSTANTS_HTML);
-  source->AddResourcePath("constants.js", IDR_MD_BOOKMARKS_CONSTANTS_JS);
-  source->AddResourcePath("debouncer.html", IDR_MD_BOOKMARKS_DEBOUNCER_HTML);
-  source->AddResourcePath("debouncer.js", IDR_MD_BOOKMARKS_DEBOUNCER_JS);
+                          IDR_BOOKMARKS_COMMAND_MANAGER_JS);
+  source->AddResourcePath("constants.html", IDR_BOOKMARKS_CONSTANTS_HTML);
+  source->AddResourcePath("constants.js", IDR_BOOKMARKS_CONSTANTS_JS);
+  source->AddResourcePath("debouncer.html", IDR_BOOKMARKS_DEBOUNCER_HTML);
+  source->AddResourcePath("debouncer.js", IDR_BOOKMARKS_DEBOUNCER_JS);
   source->AddResourcePath("dialog_focus_manager.html",
-                          IDR_MD_BOOKMARKS_DIALOG_FOCUS_MANAGER_HTML);
+                          IDR_BOOKMARKS_DIALOG_FOCUS_MANAGER_HTML);
   source->AddResourcePath("dialog_focus_manager.js",
-                          IDR_MD_BOOKMARKS_DIALOG_FOCUS_MANAGER_JS);
-  source->AddResourcePath("dnd_manager.html",
-                          IDR_MD_BOOKMARKS_DND_MANAGER_HTML);
-  source->AddResourcePath("dnd_manager.js", IDR_MD_BOOKMARKS_DND_MANAGER_JS);
-  source->AddResourcePath("edit_dialog.html",
-                          IDR_MD_BOOKMARKS_EDIT_DIALOG_HTML);
-  source->AddResourcePath("edit_dialog.js", IDR_MD_BOOKMARKS_EDIT_DIALOG_JS);
-  source->AddResourcePath("folder_node.html",
-                          IDR_MD_BOOKMARKS_FOLDER_NODE_HTML);
-  source->AddResourcePath("folder_node.js", IDR_MD_BOOKMARKS_FOLDER_NODE_JS);
-  source->AddResourcePath("item.html", IDR_MD_BOOKMARKS_ITEM_HTML);
-  source->AddResourcePath("item.js", IDR_MD_BOOKMARKS_ITEM_JS);
-  source->AddResourcePath("list.html", IDR_MD_BOOKMARKS_LIST_HTML);
-  source->AddResourcePath("list.js", IDR_MD_BOOKMARKS_LIST_JS);
+                          IDR_BOOKMARKS_DIALOG_FOCUS_MANAGER_JS);
+  source->AddResourcePath("dnd_manager.html", IDR_BOOKMARKS_DND_MANAGER_HTML);
+  source->AddResourcePath("dnd_manager.js", IDR_BOOKMARKS_DND_MANAGER_JS);
+  source->AddResourcePath("edit_dialog.html", IDR_BOOKMARKS_EDIT_DIALOG_HTML);
+  source->AddResourcePath("edit_dialog.js", IDR_BOOKMARKS_EDIT_DIALOG_JS);
+  source->AddResourcePath("folder_node.html", IDR_BOOKMARKS_FOLDER_NODE_HTML);
+  source->AddResourcePath("folder_node.js", IDR_BOOKMARKS_FOLDER_NODE_JS);
+  source->AddResourcePath("item.html", IDR_BOOKMARKS_ITEM_HTML);
+  source->AddResourcePath("item.js", IDR_BOOKMARKS_ITEM_JS);
+  source->AddResourcePath("list.html", IDR_BOOKMARKS_LIST_HTML);
+  source->AddResourcePath("list.js", IDR_BOOKMARKS_LIST_JS);
   source->AddResourcePath("mouse_focus_behavior.html",
-                          IDR_MD_BOOKMARKS_MOUSE_FOCUS_BEHAVIOR_HTML);
+                          IDR_BOOKMARKS_MOUSE_FOCUS_BEHAVIOR_HTML);
   source->AddResourcePath("mouse_focus_behavior.js",
-                          IDR_MD_BOOKMARKS_MOUSE_FOCUS_BEHAVIOR_JS);
-  source->AddResourcePath("reducers.html", IDR_MD_BOOKMARKS_REDUCERS_HTML);
-  source->AddResourcePath("reducers.js", IDR_MD_BOOKMARKS_REDUCERS_JS);
-  source->AddResourcePath("router.html", IDR_MD_BOOKMARKS_ROUTER_HTML);
-  source->AddResourcePath("router.js", IDR_MD_BOOKMARKS_ROUTER_JS);
-  source->AddResourcePath("shared_style.html",
-                          IDR_MD_BOOKMARKS_SHARED_STYLE_HTML);
-  source->AddResourcePath("shared_vars.html",
-                          IDR_MD_BOOKMARKS_SHARED_VARS_HTML);
-  source->AddResourcePath("store.html", IDR_MD_BOOKMARKS_STORE_HTML);
-  source->AddResourcePath("store.js", IDR_MD_BOOKMARKS_STORE_JS);
-  source->AddResourcePath("store_client.html",
-                          IDR_MD_BOOKMARKS_STORE_CLIENT_HTML);
-  source->AddResourcePath("store_client.js", IDR_MD_BOOKMARKS_STORE_CLIENT_JS);
-  source->AddResourcePath("strings.html", IDR_MD_BOOKMARKS_STRINGS_HTML);
+                          IDR_BOOKMARKS_MOUSE_FOCUS_BEHAVIOR_JS);
+  source->AddResourcePath("reducers.html", IDR_BOOKMARKS_REDUCERS_HTML);
+  source->AddResourcePath("reducers.js", IDR_BOOKMARKS_REDUCERS_JS);
+  source->AddResourcePath("router.html", IDR_BOOKMARKS_ROUTER_HTML);
+  source->AddResourcePath("router.js", IDR_BOOKMARKS_ROUTER_JS);
+  source->AddResourcePath("shared_style.html", IDR_BOOKMARKS_SHARED_STYLE_HTML);
+  source->AddResourcePath("shared_vars.html", IDR_BOOKMARKS_SHARED_VARS_HTML);
+  source->AddResourcePath("store.html", IDR_BOOKMARKS_STORE_HTML);
+  source->AddResourcePath("store.js", IDR_BOOKMARKS_STORE_JS);
+  source->AddResourcePath("store_client.html", IDR_BOOKMARKS_STORE_CLIENT_HTML);
+  source->AddResourcePath("store_client.js", IDR_BOOKMARKS_STORE_CLIENT_JS);
+  source->AddResourcePath("strings.html", IDR_BOOKMARKS_STRINGS_HTML);
   source->AddResourcePath("toast_manager.html",
-                          IDR_MD_BOOKMARKS_TOAST_MANAGER_HTML);
-  source->AddResourcePath("toast_manager.js",
-                          IDR_MD_BOOKMARKS_TOAST_MANAGER_JS);
-  source->AddResourcePath("toolbar.html", IDR_MD_BOOKMARKS_TOOLBAR_HTML);
-  source->AddResourcePath("toolbar.js", IDR_MD_BOOKMARKS_TOOLBAR_JS);
-  source->AddResourcePath("util.html", IDR_MD_BOOKMARKS_UTIL_HTML);
-  source->AddResourcePath("util.js", IDR_MD_BOOKMARKS_UTIL_JS);
+                          IDR_BOOKMARKS_TOAST_MANAGER_HTML);
+  source->AddResourcePath("toast_manager.js", IDR_BOOKMARKS_TOAST_MANAGER_JS);
+  source->AddResourcePath("toolbar.html", IDR_BOOKMARKS_TOOLBAR_HTML);
+  source->AddResourcePath("toolbar.js", IDR_BOOKMARKS_TOOLBAR_JS);
+  source->AddResourcePath("util.html", IDR_BOOKMARKS_UTIL_HTML);
+  source->AddResourcePath("util.js", IDR_BOOKMARKS_UTIL_JS);
 
-  source->SetDefaultResource(IDR_MD_BOOKMARKS_BOOKMARKS_HTML);
+  source->SetDefaultResource(IDR_BOOKMARKS_BOOKMARKS_HTML);
 #endif
 
   source->SetJsonPath("strings.js");
@@ -230,20 +217,20 @@
 
 }  // namespace
 
-MdBookmarksUI::MdBookmarksUI(content::WebUI* web_ui) : WebUIController(web_ui) {
+BookmarksUI::BookmarksUI(content::WebUI* web_ui) : WebUIController(web_ui) {
   // Set up the chrome://bookmarks/ source.
   Profile* profile = Profile::FromWebUI(web_ui);
-  auto* source = CreateMdBookmarksUIHTMLSource(profile);
+  auto* source = CreateBookmarksUIHTMLSource(profile);
   DarkModeHandler::Initialize(web_ui, source);
   content::WebUIDataSource::Add(profile, source);
 
   auto plural_string_handler = std::make_unique<PluralStringHandler>();
   plural_string_handler->AddLocalizedString(
-      "listChanged", IDS_MD_BOOKMARK_MANAGER_FOLDER_LIST_CHANGED);
+      "listChanged", IDS_BOOKMARK_MANAGER_FOLDER_LIST_CHANGED);
   plural_string_handler->AddLocalizedString(
-      "toastItemsDeleted", IDS_MD_BOOKMARK_MANAGER_TOAST_ITEMS_DELETED);
+      "toastItemsDeleted", IDS_BOOKMARK_MANAGER_TOAST_ITEMS_DELETED);
   plural_string_handler->AddLocalizedString(
-      "toastItemsCopied", IDS_MD_BOOKMARK_MANAGER_TOAST_ITEMS_COPIED);
+      "toastItemsCopied", IDS_BOOKMARK_MANAGER_TOAST_ITEMS_COPIED);
   web_ui->AddMessageHandler(std::move(plural_string_handler));
 
   web_ui->AddMessageHandler(std::make_unique<BookmarksMessageHandler>());
@@ -251,7 +238,7 @@
 }
 
 // static
-base::RefCountedMemory* MdBookmarksUI::GetFaviconResourceBytes(
+base::RefCountedMemory* BookmarksUI::GetFaviconResourceBytes(
     ui::ScaleFactor scale_factor) {
   return ui::ResourceBundle::GetSharedInstance().LoadDataResourceBytesForScale(
       IDR_BOOKMARKS_FAVICON, scale_factor);
diff --git a/chrome/browser/ui/webui/bookmarks/bookmarks_ui.h b/chrome/browser/ui/webui/bookmarks/bookmarks_ui.h
index 92ce12b..23e4ea5e 100644
--- a/chrome/browser/ui/webui/bookmarks/bookmarks_ui.h
+++ b/chrome/browser/ui/webui/bookmarks/bookmarks_ui.h
@@ -13,15 +13,15 @@
 class RefCountedMemory;
 }
 
-class MdBookmarksUI : public content::WebUIController {
+class BookmarksUI : public content::WebUIController {
  public:
-  explicit MdBookmarksUI(content::WebUI* web_ui);
+  explicit BookmarksUI(content::WebUI* web_ui);
 
   static base::RefCountedMemory* GetFaviconResourceBytes(
       ui::ScaleFactor scale_factor);
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(MdBookmarksUI);
+  DISALLOW_COPY_AND_ASSIGN(BookmarksUI);
 };
 
 #endif  // CHROME_BROWSER_UI_WEBUI_BOOKMARKS_BOOKMARKS_UI_H_
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index efb27ee..fe425d5 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/search/suggestions/suggestions_ui.h"
 #include "chrome/browser/ui/webui/about_ui.h"
 #include "chrome/browser/ui/webui/bluetooth_internals/bluetooth_internals_ui.h"
+#include "chrome/browser/ui/webui/chromeos/account_manager_welcome_ui.h"
 #include "chrome/browser/ui/webui/components_ui.h"
 #include "chrome/browser/ui/webui/constrained_web_dialog_ui.h"
 #include "chrome/browser/ui/webui/crashes_ui.h"
@@ -449,7 +450,7 @@
   }
   // Bookmarks are part of NTP on Android.
   if (url.host_piece() == chrome::kChromeUIBookmarksHost)
-    return &NewWebUI<MdBookmarksUI>;
+    return &NewWebUI<BookmarksUI>;
   // Downloads list on Android uses the built-in download manager.
   if (url.host_piece() == chrome::kChromeUIDownloadsHost)
     return &NewWebUI<MdDownloadsUI>;
@@ -480,6 +481,8 @@
     return &NewWebUI<SetAsDefaultBrowserUI>;
 #endif
 #if defined(OS_CHROMEOS)
+  if (url.host_piece() == chrome::kChromeUIAccountManagerWelcomeHost)
+    return &NewWebUI<chromeos::AccountManagerWelcomeUI>;
   if (url.host_piece() == chrome::kChromeUIBluetoothPairingHost)
     return &NewWebUI<chromeos::BluetoothPairingDialogUI>;
   if (url.host_piece() == chrome::kChromeUICertificateManagerHost)
@@ -862,7 +865,7 @@
 
   // Bookmarks are part of NTP on Android.
   if (page_url.host_piece() == chrome::kChromeUIBookmarksHost)
-    return MdBookmarksUI::GetFaviconResourceBytes(scale_factor);
+    return BookmarksUI::GetFaviconResourceBytes(scale_factor);
 
   // Android uses the native download manager.
   if (page_url.host_piece() == chrome::kChromeUIDownloadsHost)
diff --git a/chrome/browser/ui/webui/chromeos/account_manager_welcome_dialog.cc b/chrome/browser/ui/webui/chromeos/account_manager_welcome_dialog.cc
new file mode 100644
index 0000000..65a7f34
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/account_manager_welcome_dialog.cc
@@ -0,0 +1,75 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/chromeos/account_manager_welcome_dialog.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/settings_window_manager_chromeos.h"
+#include "chrome/common/webui_url_constants.h"
+#include "ui/aura/window.h"
+#include "url/gurl.h"
+
+namespace chromeos {
+
+namespace {
+
+AccountManagerWelcomeDialog* g_dialog = nullptr;
+constexpr int kSigninDialogWidth = 600;
+constexpr int kSigninDialogHeight = 500;
+
+}  // namespace
+
+AccountManagerWelcomeDialog::AccountManagerWelcomeDialog()
+    : SystemWebDialogDelegate(GURL(chrome::kChromeUIAccountManagerWelcomeURL),
+                              base::string16() /* title */) {}
+
+AccountManagerWelcomeDialog::~AccountManagerWelcomeDialog() {
+  DCHECK_EQ(this, g_dialog);
+  g_dialog = nullptr;
+}
+
+// static
+bool AccountManagerWelcomeDialog::ShowIfRequired() {
+  if (g_dialog) {
+    // If the dialog is already being displayed, bring it to focus instead of
+    // creating another window.
+    g_dialog->dialog_window()->Focus();
+    return true;
+  }
+
+  // Will be deleted by |SystemWebDialogDelegate::OnDialogClosed|.
+  g_dialog = new AccountManagerWelcomeDialog();
+  g_dialog->ShowSystemDialog();
+
+  // TODO(sinhak): Store the number of times the welcome screen has been shown
+  // in Prefs and check against it before showing it again.
+  return true;
+}
+
+void AccountManagerWelcomeDialog::OnDialogClosed(
+    const std::string& json_retval) {
+  chrome::SettingsWindowManager::GetInstance()->ShowChromePageForProfile(
+      ProfileManager::GetActiveUserProfile(),
+      GURL("chrome://settings/accountManager"));
+
+  SystemWebDialogDelegate::OnDialogClosed(json_retval);
+}
+
+void AccountManagerWelcomeDialog::GetDialogSize(gfx::Size* size) const {
+  size->SetSize(kSigninDialogWidth, kSigninDialogHeight);
+}
+
+std::string AccountManagerWelcomeDialog::GetDialogArgs() const {
+  return std::string();
+}
+
+bool AccountManagerWelcomeDialog::ShouldShowDialogTitle() const {
+  return false;
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/account_manager_welcome_dialog.h b/chrome/browser/ui/webui/chromeos/account_manager_welcome_dialog.h
new file mode 100644
index 0000000..63e020d
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/account_manager_welcome_dialog.h
@@ -0,0 +1,38 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_ACCOUNT_MANAGER_WELCOME_DIALOG_H_
+#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_ACCOUNT_MANAGER_WELCOME_DIALOG_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "chrome/browser/ui/webui/chromeos/system_web_dialog_delegate.h"
+
+namespace chromeos {
+
+class AccountManagerWelcomeDialog : public SystemWebDialogDelegate {
+ public:
+  // Displays the Chrome OS Account Manager welcome screen, if it has not been
+  // shown "too many times" before. Returns true if the screen was displayed,
+  // false otherwise.
+  static bool ShowIfRequired();
+
+ protected:
+  AccountManagerWelcomeDialog();
+  ~AccountManagerWelcomeDialog() override;
+
+  // ui::SystemWebDialogDelegate overrides.
+  void OnDialogClosed(const std::string& json_retval) override;
+  void GetDialogSize(gfx::Size* size) const override;
+  std::string GetDialogArgs() const override;
+  bool ShouldShowDialogTitle() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AccountManagerWelcomeDialog);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_ACCOUNT_MANAGER_WELCOME_DIALOG_H_
diff --git a/chrome/browser/ui/webui/chromeos/account_manager_welcome_ui.cc b/chrome/browser/ui/webui/chromeos/account_manager_welcome_ui.cc
new file mode 100644
index 0000000..d5c803d
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/account_manager_welcome_ui.cc
@@ -0,0 +1,54 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/chromeos/account_manager_welcome_ui.h"
+
+#include "base/bind.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chrome/grit/browser_resources.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "ui/strings/grit/ui_strings.h"
+
+namespace chromeos {
+
+AccountManagerWelcomeUI::AccountManagerWelcomeUI(content::WebUI* web_ui)
+    : ui::WebDialogUI(web_ui), weak_factory_(this) {
+  content::WebUIDataSource* html_source = content::WebUIDataSource::Create(
+      chrome::kChromeUIAccountManagerWelcomeHost);
+
+  web_ui->RegisterMessageCallback(
+      "closeDialog", base::BindRepeating(&WebDialogUI::CloseDialog,
+                                         weak_factory_.GetWeakPtr()));
+
+  html_source->SetJsonPath("strings.js");
+
+  // Add localized strings.
+  html_source->AddLocalizedString("welcomeTitle",
+                                  IDS_ACCOUNT_MANAGER_WELCOME_TITLE);
+  html_source->AddLocalizedString("welcomeMessage",
+                                  IDS_ACCOUNT_MANAGER_WELCOME_TEXT);
+  html_source->AddLocalizedString("okButton", IDS_APP_OK);
+
+  // Add required resources.
+  html_source->AddResourcePath("account_manager_welcome.css",
+                               IDR_ACCOUNT_MANAGER_WELCOME_CSS);
+  html_source->AddResourcePath("account_manager_welcome.js",
+                               IDR_ACCOUNT_MANAGER_WELCOME_JS);
+  html_source->AddResourcePath("account_manager_welcome_1x.png",
+                               IDR_ACCOUNT_MANAGER_WELCOME_1X_PNG);
+  html_source->AddResourcePath("account_manager_welcome_2x.png",
+                               IDR_ACCOUNT_MANAGER_WELCOME_2X_PNG);
+  html_source->AddResourcePath("googleg.svg",
+                               IDR_ACCOUNT_MANAGER_WELCOME_GOOGLE_LOGO_SVG);
+  html_source->SetDefaultResource(IDR_ACCOUNT_MANAGER_WELCOME_HTML);
+
+  Profile* profile = Profile::FromWebUI(web_ui);
+  content::WebUIDataSource::Add(profile, html_source);
+}
+
+AccountManagerWelcomeUI::~AccountManagerWelcomeUI() = default;
+
+}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/account_manager_welcome_ui.h b/chrome/browser/ui/webui/chromeos/account_manager_welcome_ui.h
new file mode 100644
index 0000000..6f66e183
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/account_manager_welcome_ui.h
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_ACCOUNT_MANAGER_WELCOME_UI_H_
+#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_ACCOUNT_MANAGER_WELCOME_UI_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "ui/web_dialogs/web_dialog_ui.h"
+
+namespace chromeos {
+
+// For chrome:://account-manager-welcome
+class AccountManagerWelcomeUI : public ui::WebDialogUI {
+ public:
+  explicit AccountManagerWelcomeUI(content::WebUI* web_ui);
+  ~AccountManagerWelcomeUI() override;
+
+ private:
+  base::WeakPtrFactory<AccountManagerWelcomeUI> weak_factory_;
+  DISALLOW_COPY_AND_ASSIGN(AccountManagerWelcomeUI);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_ACCOUNT_MANAGER_WELCOME_UI_H_
diff --git a/chrome/browser/ui/webui/page_not_available_for_guest/page_not_available_for_guest_ui.cc b/chrome/browser/ui/webui/page_not_available_for_guest/page_not_available_for_guest_ui.cc
index 779533a..946af1c1 100644
--- a/chrome/browser/ui/webui/page_not_available_for_guest/page_not_available_for_guest_ui.cc
+++ b/chrome/browser/ui/webui/page_not_available_for_guest/page_not_available_for_guest_ui.cc
@@ -23,7 +23,7 @@
 
   base::string16 page_title;
   if (host_name == chrome::kChromeUIBookmarksHost)
-    page_title = l10n_util::GetStringUTF16(IDS_MD_BOOKMARK_MANAGER_TITLE);
+    page_title = l10n_util::GetStringUTF16(IDS_BOOKMARK_MANAGER_TITLE);
   else if (host_name == chrome::kChromeUIHistoryHost)
     page_title = l10n_util::GetStringUTF16(IDS_HISTORY_TITLE);
   else if (host_name == chrome::kChromeUIExtensionsHost)
diff --git a/chrome/browser/ui/webui/set_as_default_browser_ui_win.cc b/chrome/browser/ui/webui/set_as_default_browser_ui_win.cc
index c0977b81..c0999a2 100644
--- a/chrome/browser/ui/webui/set_as_default_browser_ui_win.cc
+++ b/chrome/browser/ui/webui/set_as_default_browser_ui_win.cc
@@ -71,7 +71,7 @@
 content::WebUIDataSource* CreateSetAsDefaultBrowserUIHTMLSource() {
   content::WebUIDataSource* data_source =
       content::WebUIDataSource::Create(chrome::kChromeUIMetroFlowHost);
-  data_source->AddLocalizedString("page-title", IDS_METRO_FLOW_TAB_TITLE);
+  data_source->AddLocalizedString("pageTitle", IDS_METRO_FLOW_TAB_TITLE);
   data_source->AddLocalizedString("flowTitle", IDS_METRO_FLOW_TITLE_SHORT);
   data_source->AddLocalizedString("flowDescription",
                                   IDS_METRO_FLOW_DESCRIPTION);
diff --git a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc
index 10f7cc69..bb93815 100644
--- a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc
@@ -8,6 +8,7 @@
 #include "base/bind_helpers.h"
 #include "base/logging.h"
 #include "base/values.h"
+#include "chrome/browser/chromeos/android_sms/android_sms_pairing_state_tracker_impl.h"
 #include "chrome/browser/chromeos/android_sms/android_sms_urls.h"
 #include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_factory.h"
 #include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_storage.h"
@@ -34,6 +35,7 @@
 const char kPageContentDataInstantTetheringStateKey[] = "instantTetheringState";
 const char kPageContentDataMessagesStateKey[] = "messagesState";
 const char kPageContentDataSmartLockStateKey[] = "smartLockState";
+const char kIsAndroidSmsPairingComplete[] = "isAndroidSmsPairingComplete";
 
 constexpr char kAndroidSmsInfoOriginKey[] = "origin";
 constexpr char kAndroidSmsInfoEnabledKey[] = "enabled";
@@ -51,11 +53,15 @@
 MultideviceHandler::MultideviceHandler(
     PrefService* prefs,
     multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
+    multidevice_setup::AndroidSmsPairingStateTracker*
+        android_sms_pairing_state_tracker,
     multidevice_setup::AndroidSmsAppHelperDelegate* android_sms_app_helper)
     : prefs_(prefs),
       multidevice_setup_client_(multidevice_setup_client),
+      android_sms_pairing_state_tracker_(android_sms_pairing_state_tracker),
       android_sms_app_helper_(android_sms_app_helper),
       multidevice_setup_observer_(this),
+      android_sms_pairing_state_tracker_observer_(this),
       callback_weak_ptr_factory_(this) {
   RegisterPrefChangeListeners();
 }
@@ -108,12 +114,18 @@
 void MultideviceHandler::OnJavascriptAllowed() {
   if (multidevice_setup_client_)
     multidevice_setup_observer_.Add(multidevice_setup_client_);
+
+  if (android_sms_pairing_state_tracker_)
+    android_sms_pairing_state_tracker_->AddObserver(this);
 }
 
 void MultideviceHandler::OnJavascriptDisallowed() {
   if (multidevice_setup_client_)
     multidevice_setup_observer_.Remove(multidevice_setup_client_);
 
+  if (android_sms_pairing_state_tracker_)
+    android_sms_pairing_state_tracker_->RemoveObserver(this);
+
   // Ensure that pending callbacks do not complete and cause JS to be evaluated.
   callback_weak_ptr_factory_.InvalidateWeakPtrs();
 }
@@ -132,6 +144,11 @@
   NotifyAndroidSmsInfoChange();
 }
 
+void MultideviceHandler::OnPairingStateChanged() {
+  UpdatePageContent();
+  NotifyAndroidSmsInfoChange();
+}
+
 void MultideviceHandler::UpdatePageContent() {
   std::unique_ptr<base::DictionaryValue> page_content_dictionary =
       GeneratePageContentDataDictionary();
@@ -331,6 +348,12 @@
                                        host_status_with_device.second->name());
   }
 
+  page_content_dictionary->SetBoolean(
+      kIsAndroidSmsPairingComplete,
+      android_sms_pairing_state_tracker_
+          ? android_sms_pairing_state_tracker_->IsAndroidSmsPairingComplete()
+          : false);
+
   return page_content_dictionary;
 }
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.h b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.h
index 6bef9c4d..64eb9ee6 100644
--- a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.h
@@ -8,6 +8,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observer.h"
+#include "chrome/browser/chromeos/android_sms/android_sms_service_factory.h"
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
 #include "chromeos/components/multidevice/remote_device_ref.h"
 #include "chromeos/services/multidevice_setup/public/cpp/multidevice_setup_client.h"
@@ -31,11 +32,14 @@
 // Chrome "Multidevice" (a.k.a. "Connected Devices") settings page UI handler.
 class MultideviceHandler
     : public ::settings::SettingsPageUIHandler,
-      public multidevice_setup::MultiDeviceSetupClient::Observer {
+      public multidevice_setup::MultiDeviceSetupClient::Observer,
+      public multidevice_setup::AndroidSmsPairingStateTracker::Observer {
  public:
   MultideviceHandler(
       PrefService* prefs,
       multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
+      multidevice_setup::AndroidSmsPairingStateTracker*
+          android_sms_pairing_state_tracker,
       multidevice_setup::AndroidSmsAppHelperDelegate* android_sms_app_helper);
   ~MultideviceHandler() override;
 
@@ -56,6 +60,9 @@
       const multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap&
           feature_states_map) override;
 
+  // multidevice_setup::AndroidSmsPairingStateTracker::Observer:
+  void OnPairingStateChanged() override;
+
   // Sends the most recent PageContentData dictionary to the WebUI page as an
   // update (e.g., not due to a getPageContent() request).
   void UpdatePageContent();
@@ -103,11 +110,16 @@
   GetFeatureStatesMap();
 
   multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client_;
+  multidevice_setup::AndroidSmsPairingStateTracker*
+      android_sms_pairing_state_tracker_;
   multidevice_setup::AndroidSmsAppHelperDelegate* android_sms_app_helper_;
 
   ScopedObserver<multidevice_setup::MultiDeviceSetupClient,
                  multidevice_setup::MultiDeviceSetupClient::Observer>
       multidevice_setup_observer_;
+  ScopedObserver<multidevice_setup::AndroidSmsPairingStateTracker,
+                 multidevice_setup::AndroidSmsPairingStateTracker::Observer>
+      android_sms_pairing_state_tracker_observer_;
 
   // Used to cancel callbacks when JavaScript becomes disallowed.
   base::WeakPtrFactory<MultideviceHandler> callback_weak_ptr_factory_;
diff --git a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc
index 545cbcd5..b3cd72ff 100644
--- a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/chromeos/android_sms/android_sms_urls.h"
 #include "chromeos/components/multidevice/remote_device_test_util.h"
 #include "chromeos/services/multidevice_setup/public/cpp/fake_android_sms_app_helper_delegate.h"
+#include "chromeos/services/multidevice_setup/public/cpp/fake_android_sms_pairing_state_tracker.h"
 #include "chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
 #include "components/content_settings/core/common/content_settings_pattern.h"
 #include "components/prefs/testing_pref_service.h"
@@ -27,9 +28,12 @@
   TestMultideviceHandler(
       PrefService* prefs,
       multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
+      multidevice_setup::AndroidSmsPairingStateTracker*
+          android_sms_pairing_state_tracker,
       multidevice_setup::AndroidSmsAppHelperDelegate* android_sms_app_helper)
       : MultideviceHandler(prefs,
                            multidevice_setup_client,
+                           android_sms_pairing_state_tracker,
                            std::move(android_sms_app_helper)) {}
   ~TestMultideviceHandler() override = default;
 
@@ -115,6 +119,8 @@
 
     fake_multidevice_setup_client_ =
         std::make_unique<multidevice_setup::FakeMultiDeviceSetupClient>();
+    fake_android_sms_pairing_state_tracker_ = std::make_unique<
+        multidevice_setup::FakeAndroidSmsPairingStateTracker>();
     fake_android_sms_app_helper_delegate_ =
         std::make_unique<multidevice_setup::FakeAndroidSmsAppHelperDelegate>();
 
@@ -122,6 +128,7 @@
 
     handler_ = std::make_unique<TestMultideviceHandler>(
         prefs_.get(), fake_multidevice_setup_client_.get(),
+        fake_android_sms_pairing_state_tracker_.get(),
         fake_android_sms_app_helper_delegate_.get());
     handler_->set_web_ui(test_web_ui_.get());
     handler_->RegisterMessages();
@@ -211,6 +218,22 @@
     VerifyPageContent(call_data.arg2());
   }
 
+  void SimulatePairingStateUpdate(bool is_android_sms_pairing_complete) {
+    size_t call_data_count_before_call = test_web_ui()->call_data().size();
+
+    fake_android_sms_pairing_state_tracker_->SetPairingComplete(
+        is_android_sms_pairing_complete);
+    EXPECT_EQ(call_data_count_before_call + 2u,
+              test_web_ui()->call_data().size());
+
+    const content::TestWebUI::CallData& call_data =
+        CallDataAtIndex(call_data_count_before_call);
+    EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
+    EXPECT_EQ("settings.updateMultidevicePageContentData",
+              call_data.arg1()->GetString());
+    VerifyPageContent(call_data.arg2());
+  }
+
   void CallRetryPendingHostSetup(bool success) {
     base::ListValue empty_args;
     test_web_ui()->HandleReceivedMessage("retryPendingHostSetup", &empty_args);
@@ -284,6 +307,8 @@
   std::unique_ptr<multidevice_setup::FakeMultiDeviceSetupClient>
       fake_multidevice_setup_client_;
   std::unique_ptr<TestMultideviceHandler> handler_;
+  std::unique_ptr<multidevice_setup::FakeAndroidSmsPairingStateTracker>
+      fake_android_sms_pairing_state_tracker_;
 
   multidevice_setup::MultiDeviceSetupClient::HostStatusWithDevice
       host_status_with_device_;
@@ -320,6 +345,8 @@
   feature_states_map[multidevice_setup::mojom::Feature::kBetterTogetherSuite] =
       multidevice_setup::mojom::FeatureState::kDisabledByUser;
   SimulateFeatureStatesUpdate(feature_states_map);
+
+  SimulatePairingStateUpdate(/*is_android_sms_pairing_complete=*/true);
 }
 
 TEST_F(MultideviceHandlerTest, RetryPendingHostSetup) {
diff --git a/chrome/browser/ui/webui/settings/md_settings_ui.cc b/chrome/browser/ui/webui/settings/md_settings_ui.cc
index 2f4fd75..b20663b 100644
--- a/chrome/browser/ui/webui/settings/md_settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_ui.cc
@@ -293,6 +293,9 @@
             chromeos::multidevice_setup::MultiDeviceSetupClientFactory::
                 GetForProfile(profile),
             android_sms_service
+                ? android_sms_service->android_sms_pairing_state_tracker()
+                : nullptr,
+            android_sms_service
                 ? android_sms_service->android_sms_app_helper_delegate()
                 : nullptr));
   }
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler.h b/chrome/browser/ui/webui/settings/site_settings_handler.h
index 88a2f65c..2092bce 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler.h
+++ b/chrome/browser/ui/webui/settings/site_settings_handler.h
@@ -117,6 +117,8 @@
   FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest, PatternsAndContentType);
   FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest, SessionOnlyException);
   FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest, ZoomLevels);
+  FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest,
+                           HandleClearEtldPlus1DataAndCookies);
 
   // Creates the CookiesTreeModel if necessary.
   void EnsureCookiesTreeModelCreated();
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
index 4a5140b..b1401658 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
@@ -385,6 +385,71 @@
     incognito_profile_ = nullptr;
   }
 
+  // TODO(https://crbug.com/835712): Currently only set up the cookies and local
+  // storage nodes, will update all other nodes in the future.
+  void SetUpCookiesTreeModel() {
+    scoped_refptr<MockBrowsingDataCookieHelper>
+        mock_browsing_data_cookie_helper;
+    scoped_refptr<MockBrowsingDataLocalStorageHelper>
+        mock_browsing_data_local_storage_helper;
+
+    mock_browsing_data_cookie_helper =
+        new MockBrowsingDataCookieHelper(profile());
+    mock_browsing_data_local_storage_helper =
+        new MockBrowsingDataLocalStorageHelper(profile());
+
+    auto container = std::make_unique<LocalDataContainer>(
+        mock_browsing_data_cookie_helper,
+        /*database_helper=*/nullptr, mock_browsing_data_local_storage_helper,
+        /*session_storage_helper=*/nullptr,
+        /*appcache_helper=*/nullptr,
+        /*indexed_db_helper=*/nullptr,
+        /*file_system_helper=*/nullptr,
+        /*quota_helper=*/nullptr,
+        /*service_worker_helper=*/nullptr,
+        /*data_shared_worker_helper=*/nullptr,
+        /*cache_storage_helper=*/nullptr,
+        /*flash_lso_helper=*/nullptr,
+        /*media_license_helper=*/nullptr);
+    auto mock_cookies_tree_model = std::make_unique<CookiesTreeModel>(
+        std::move(container), profile()->GetExtensionSpecialStoragePolicy());
+
+    mock_browsing_data_local_storage_helper->AddLocalStorageForOrigin(
+        GURL("https://www.example.com/"), 2);
+
+    mock_browsing_data_local_storage_helper->AddLocalStorageForOrigin(
+        GURL("https://www.google.com/"), 5);
+    mock_browsing_data_local_storage_helper->Notify();
+
+    mock_browsing_data_cookie_helper->AddCookieSamples(
+        GURL("http://example.com"), "A=1");
+    mock_browsing_data_cookie_helper->AddCookieSamples(
+        GURL("http://www.example.com/"), "B=1");
+    mock_browsing_data_cookie_helper->AddCookieSamples(
+        GURL("http://abc.example.com"), "C=1");
+    mock_browsing_data_cookie_helper->AddCookieSamples(
+        GURL("http://google.com"), "A=1");
+    mock_browsing_data_cookie_helper->AddCookieSamples(
+        GURL("http://google.com"), "B=1");
+    mock_browsing_data_cookie_helper->AddCookieSamples(
+        GURL("http://google.com.au"), "A=1");
+    mock_browsing_data_cookie_helper->Notify();
+
+    handler()->SetCookiesTreeModelForTesting(
+        std::move(mock_cookies_tree_model));
+  }
+
+  const base::ListValue* GetOnStorageFetchedSentListValue() {
+    handler()->ClearAllSitesMapForTesting();
+    handler()->OnStorageFetched();
+    const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
+    std::string callback_id;
+    data.arg1()->GetAsString(&callback_id);
+    const base::ListValue* storage_and_cookie_list;
+    data.arg2()->GetAsList(&storage_and_cookie_list);
+    return storage_and_cookie_list;
+  }
+
   // Content setting group name for the relevant ContentSettingsType.
   const std::string kNotifications;
   const std::string kCookies;
@@ -618,55 +683,9 @@
   run_loop.RunUntilIdle();
 }
 
-// TODO(https://crbug.com/835712): Currently only set up the cookies and local
-// storage nodes, will update all other nodes in the future.
 TEST_F(SiteSettingsHandlerTest, OnStorageFetched) {
-  scoped_refptr<MockBrowsingDataCookieHelper> mock_browsing_data_cookie_helper;
-  scoped_refptr<MockBrowsingDataLocalStorageHelper>
-      mock_browsing_data_local_storage_helper;
+  SetUpCookiesTreeModel();
 
-  mock_browsing_data_cookie_helper =
-      new MockBrowsingDataCookieHelper(profile());
-  mock_browsing_data_local_storage_helper =
-      new MockBrowsingDataLocalStorageHelper(profile());
-
-  auto container = std::make_unique<LocalDataContainer>(
-      mock_browsing_data_cookie_helper,
-      /*database_helper=*/nullptr, mock_browsing_data_local_storage_helper,
-      /*session_storage_helper=*/nullptr,
-      /*appcache_helper=*/nullptr,
-      /*indexed_db_helper=*/nullptr,
-      /*file_system_helper=*/nullptr,
-      /*quota_helper=*/nullptr,
-      /*service_worker_helper=*/nullptr,
-      /*data_shared_worker_helper=*/nullptr,
-      /*cache_storage_helper=*/nullptr,
-      /*flash_lso_helper=*/nullptr,
-      /*media_license_helper=*/nullptr);
-  auto mock_cookies_tree_model = std::make_unique<CookiesTreeModel>(
-      std::move(container), profile()->GetExtensionSpecialStoragePolicy());
-
-  mock_browsing_data_local_storage_helper->AddLocalStorageForOrigin(
-      GURL("https://www.example.com/"), 2);
-  mock_browsing_data_local_storage_helper->AddLocalStorageForOrigin(
-      GURL("https://www.google.com/"), 5);
-  mock_browsing_data_local_storage_helper->Notify();
-
-  mock_browsing_data_cookie_helper->AddCookieSamples(GURL("http://example.com"),
-                                                     "A=1");
-  mock_browsing_data_cookie_helper->AddCookieSamples(
-      GURL("http://www.example.com/"), "B=1");
-  mock_browsing_data_cookie_helper->AddCookieSamples(
-      GURL("http://abc.example.com"), "C=1");
-  mock_browsing_data_cookie_helper->AddCookieSamples(GURL("http://google.com"),
-                                                     "A=1");
-  mock_browsing_data_cookie_helper->AddCookieSamples(GURL("http://google.com"),
-                                                     "B=1");
-  mock_browsing_data_cookie_helper->AddCookieSamples(
-      GURL("http://google.com.au"), "A=1");
-  mock_browsing_data_cookie_helper->Notify();
-
-  handler()->SetCookiesTreeModelForTesting(std::move(mock_cookies_tree_model));
   handler()->ClearAllSitesMapForTesting();
 
   handler()->OnStorageFetched();
@@ -1678,4 +1697,52 @@
   }
 }
 
+TEST_F(SiteSettingsHandlerTest, HandleClearEtldPlus1DataAndCookies) {
+  SetUpCookiesTreeModel();
+
+  EXPECT_EQ(22, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
+
+  const base::ListValue* storage_and_cookie_list =
+      GetOnStorageFetchedSentListValue();
+  EXPECT_EQ(3U, storage_and_cookie_list->GetSize());
+  const base::DictionaryValue* site_group;
+  ASSERT_TRUE(storage_and_cookie_list->GetDictionary(0, &site_group));
+  std::string etld_plus1_string;
+  ASSERT_TRUE(site_group->GetString("etldPlus1", &etld_plus1_string));
+  ASSERT_EQ("example.com", etld_plus1_string);
+
+  base::ListValue args;
+  args.AppendString("example.com");
+  handler()->HandleClearEtldPlus1DataAndCookies(&args);
+  EXPECT_EQ(11, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
+
+  storage_and_cookie_list = GetOnStorageFetchedSentListValue();
+  EXPECT_EQ(2U, storage_and_cookie_list->GetSize());
+  ASSERT_TRUE(storage_and_cookie_list->GetDictionary(0, &site_group));
+  ASSERT_TRUE(site_group->GetString("etldPlus1", &etld_plus1_string));
+  ASSERT_EQ("google.com", etld_plus1_string);
+
+  args.Clear();
+  args.AppendString("google.com");
+
+  handler()->HandleClearEtldPlus1DataAndCookies(&args);
+
+  EXPECT_EQ(4, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
+
+  storage_and_cookie_list = GetOnStorageFetchedSentListValue();
+  EXPECT_EQ(1U, storage_and_cookie_list->GetSize());
+  ASSERT_TRUE(storage_and_cookie_list->GetDictionary(0, &site_group));
+  ASSERT_TRUE(site_group->GetString("etldPlus1", &etld_plus1_string));
+  ASSERT_EQ("google.com.au", etld_plus1_string);
+
+  args.Clear();
+  args.AppendString("google.com.au");
+
+  handler()->HandleClearEtldPlus1DataAndCookies(&args);
+
+  EXPECT_EQ(1, handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount());
+
+  storage_and_cookie_list = GetOnStorageFetchedSentListValue();
+  EXPECT_EQ(0U, storage_and_cookie_list->GetSize());
+}
 }  // namespace settings
diff --git a/chrome/browser/vr/browser_ui_interface.h b/chrome/browser/vr/browser_ui_interface.h
index 67dd786..b82d0ee 100644
--- a/chrome/browser/vr/browser_ui_interface.h
+++ b/chrome/browser/vr/browser_ui_interface.h
@@ -46,6 +46,8 @@
       const CapturingStateModel& potential_capturing) = 0;
   virtual void ShowExitVrPrompt(UiUnsupportedMode reason) = 0;
   virtual void SetSpeechRecognitionEnabled(bool enabled) = 0;
+  virtual void SetHasOrCanRequestRecordAudioPermission(
+      bool has_or_can_request_record_audio) = 0;
   virtual void SetRecognitionResult(const base::string16& result) = 0;
   virtual void OnSpeechRecognitionStateChanged(int new_state) = 0;
   virtual void SetOmniboxSuggestions(
diff --git a/chrome/browser/vr/model/model.cc b/chrome/browser/vr/model/model.cc
index 018234d..53700cda 100644
--- a/chrome/browser/vr/model/model.cc
+++ b/chrome/browser/vr/model/model.cc
@@ -95,7 +95,12 @@
   return get_last_opaque_mode() == kModeBrowsing;
 }
 
-bool Model::voice_search_enabled() const {
+bool Model::voice_search_available() const {
+  return speech.has_or_can_request_record_audio_permission && !incognito &&
+         !active_capturing.audio_capture_enabled;
+}
+
+bool Model::voice_search_active() const {
   return get_last_opaque_mode() == kModeVoiceSearch;
 }
 
diff --git a/chrome/browser/vr/model/model.h b/chrome/browser/vr/model/model.h
index 3a0917f..4a04fbe4 100644
--- a/chrome/browser/vr/model/model.h
+++ b/chrome/browser/vr/model/model.h
@@ -73,7 +73,8 @@
   bool has_mode_in_stack(UiMode mode) const;
   bool browsing_enabled() const;
   bool default_browsing_enabled() const;
-  bool voice_search_enabled() const;
+  bool voice_search_available() const;
+  bool voice_search_active() const;
   bool omnibox_editing_enabled() const;
   bool editing_enabled() const;
   bool fullscreen_enabled() const;
diff --git a/chrome/browser/vr/model/speech_recognition_model.h b/chrome/browser/vr/model/speech_recognition_model.h
index 88b86b9..603017f8e 100644
--- a/chrome/browser/vr/model/speech_recognition_model.h
+++ b/chrome/browser/vr/model/speech_recognition_model.h
@@ -12,7 +12,7 @@
 
 struct VR_BASE_EXPORT SpeechRecognitionModel {
   int speech_recognition_state = 0;
-  bool has_or_can_request_audio_permission = true;
+  bool has_or_can_request_record_audio_permission = true;
   base::string16 recognition_result;
 };
 
diff --git a/chrome/browser/vr/test/mock_browser_ui_interface.h b/chrome/browser/vr/test/mock_browser_ui_interface.h
index d313d816..8fdc301 100644
--- a/chrome/browser/vr/test/mock_browser_ui_interface.h
+++ b/chrome/browser/vr/test/mock_browser_ui_interface.h
@@ -35,6 +35,8 @@
                     const CapturingStateModel& potential_state));
   MOCK_METHOD1(ShowExitVrPrompt, void(UiUnsupportedMode reason));
   MOCK_METHOD1(SetSpeechRecognitionEnabled, void(bool enabled));
+  MOCK_METHOD1(SetHasOrCanRequestRecordAudioPermission,
+               void(bool has_or_can_request_record_audio_permission));
   MOCK_METHOD1(SetRecognitionResult, void(const base::string16& result));
   MOCK_METHOD1(OnSpeechRecognitionStateChanged, void(int new_state));
   void SetOmniboxSuggestions(
diff --git a/chrome/browser/vr/testapp/vr_test_context.cc b/chrome/browser/vr/testapp/vr_test_context.cc
index d22a69a..2edaa97b 100644
--- a/chrome/browser/vr/testapp/vr_test_context.cc
+++ b/chrome/browser/vr/testapp/vr_test_context.cc
@@ -482,7 +482,7 @@
 }
 
 void VrTestContext::CreateFakeVoiceSearchResult() {
-  if (!model_->voice_search_enabled())
+  if (!model_->voice_search_active())
     return;
   auto browser_ui = ui_->GetBrowserUiWeakPtr();
   browser_ui->SetRecognitionResult(
diff --git a/chrome/browser/vr/ui.cc b/chrome/browser/vr/ui.cc
index e641fc8..2949577 100644
--- a/chrome/browser/vr/ui.cc
+++ b/chrome/browser/vr/ui.cc
@@ -284,6 +284,12 @@
   model_->speech.recognition_result = result;
 }
 
+void Ui::SetHasOrCanRequestRecordAudioPermission(
+    bool const has_or_can_request_record_audio) {
+  model_->speech.has_or_can_request_record_audio_permission =
+      has_or_can_request_record_audio;
+}
+
 void Ui::OnSpeechRecognitionStateChanged(int new_state) {
   model_->speech.speech_recognition_state = new_state;
 }
@@ -578,8 +584,8 @@
 }
 
 void Ui::InitializeModel(const UiInitialState& ui_initial_state) {
-  model_->speech.has_or_can_request_audio_permission =
-      ui_initial_state.has_or_can_request_audio_permission;
+  model_->speech.has_or_can_request_record_audio_permission =
+      ui_initial_state.has_or_can_request_record_audio_permission;
   model_->ui_modes.clear();
   model_->push_mode(kModeBrowsing);
   if (ui_initial_state.in_web_vr) {
diff --git a/chrome/browser/vr/ui.h b/chrome/browser/vr/ui.h
index 8f9ee44..e3a12fc 100644
--- a/chrome/browser/vr/ui.h
+++ b/chrome/browser/vr/ui.h
@@ -102,6 +102,8 @@
   void ShowExitVrPrompt(UiUnsupportedMode reason) override;
   void SetSpeechRecognitionEnabled(bool enabled) override;
   void SetRecognitionResult(const base::string16& result) override;
+  void SetHasOrCanRequestRecordAudioPermission(
+      bool has_or_can_request_record_audio) override;
   void OnSpeechRecognitionStateChanged(int new_state) override;
   void SetOmniboxSuggestions(
       std::unique_ptr<OmniboxSuggestions> suggestions) override;
diff --git a/chrome/browser/vr/ui_initial_state.h b/chrome/browser/vr/ui_initial_state.h
index 2f64f894..377acf12 100644
--- a/chrome/browser/vr/ui_initial_state.h
+++ b/chrome/browser/vr/ui_initial_state.h
@@ -17,7 +17,7 @@
   bool in_web_vr = false;
   bool web_vr_autopresentation_expected = false;
   bool browsing_disabled = false;
-  bool has_or_can_request_audio_permission = true;
+  bool has_or_can_request_record_audio_permission = true;
   bool assets_supported = false;
   bool supports_selection = true;
   bool needs_keyboard_update = false;
diff --git a/chrome/browser/vr/ui_scene_creator.cc b/chrome/browser/vr/ui_scene_creator.cc
index 88bb156..a6875eb 100644
--- a/chrome/browser/vr/ui_scene_creator.cc
+++ b/chrome/browser/vr/ui_scene_creator.cc
@@ -627,7 +627,7 @@
       l10n_util::GetStringUTF16(IDS_VR_BUTTON_BACK), model, element_binding);
   back_button_label->AddBinding(VR_BIND_FUNC(
       bool, Model, model,
-      model->omnibox_editing_enabled() || model->voice_search_enabled(),
+      model->omnibox_editing_enabled() || model->voice_search_active(),
       UiElement, back_button_label.get(), SetVisible));
   callout_group->AddChild(std::move(back_button_label));
 
@@ -1731,7 +1731,7 @@
   speech_recognition_root->SetTransitionDuration(
       base::TimeDelta::FromMilliseconds(
           kSpeechRecognitionOpacityAnimationDurationMs));
-  VR_BIND_VISIBILITY(speech_recognition_root, model->voice_search_enabled());
+  VR_BIND_VISIBILITY(speech_recognition_root, model->voice_search_active());
 
   auto inner_circle = std::make_unique<Rect>();
   inner_circle->SetName(kSpeechRecognitionCircle);
@@ -2427,12 +2427,7 @@
   // TODO(crbug.com/834308): Refactor this element to be resized by a
   // fixed-width layout, rather than adjusting based on other elements.
   omnibox_text_field->AddBinding(std::make_unique<Binding<bool>>(
-      VR_BIND_LAMBDA(
-          [](Model* m) {
-            return m->speech.has_or_can_request_audio_permission &&
-                   !m->incognito && !m->active_capturing.audio_capture_enabled;
-          },
-          base::Unretained(model_)),
+      VR_BIND_LAMBDA(&Model::voice_search_available, base::Unretained(model_)),
       VR_BIND_LAMBDA(
           [](TextInput* e, const bool& mic_button_visible) {
             float width = kOmniboxWidthDMM - 2 * kOmniboxTextMarginDMM;
@@ -2537,19 +2532,13 @@
   mic_button->SetIconScaleFactor(kUrlBarButtonIconScaleFactor);
   mic_button->set_hover_offset(kUrlBarButtonHoverOffsetDMM);
   mic_button->set_corner_radius(kUrlBarItemCornerRadiusDMM);
-  VR_BIND_VISIBILITY(mic_button,
-                     model->speech.has_or_can_request_audio_permission &&
-                         !model->incognito &&
-                         !model->active_capturing.audio_capture_enabled);
+  VR_BIND_VISIBILITY(mic_button, model->voice_search_available());
   VR_BIND_BUTTON_COLORS(model_, mic_button.get(), &ColorScheme::url_bar_button,
                         &Button::SetButtonColors);
 
   auto mic_button_spacer =
       CreateSpacer(kOmniboxMicIconRightMarginDMM, kOmniboxHeightDMM);
-  VR_BIND_VISIBILITY(mic_button_spacer,
-                     model->speech.has_or_can_request_audio_permission &&
-                         !model->incognito &&
-                         !model->active_capturing.audio_capture_enabled);
+  VR_BIND_VISIBILITY(mic_button_spacer, model->voice_search_available());
 
   auto text_field_layout = Create<LinearLayout>(
       kOmniboxTextFieldLayout, kPhaseNone, LinearLayout::kRight);
diff --git a/chrome/browser/vr/ui_unittest.cc b/chrome/browser/vr/ui_unittest.cc
index cef39127..3c2c2d25 100644
--- a/chrome/browser/vr/ui_unittest.cc
+++ b/chrome/browser/vr/ui_unittest.cc
@@ -293,11 +293,11 @@
   CreateScene(kNotInWebVr);
 
   model_->push_mode(kModeEditingOmnibox);
-  model_->speech.has_or_can_request_audio_permission = true;
+  model_->speech.has_or_can_request_record_audio_permission = true;
   EXPECT_TRUE(OnBeginFrame());
   EXPECT_TRUE(IsVisible(kOmniboxVoiceSearchButton));
 
-  model_->speech.has_or_can_request_audio_permission = false;
+  model_->speech.has_or_can_request_record_audio_permission = false;
   EXPECT_TRUE(OnBeginFrame());
   EXPECT_FALSE(IsVisible(kOmniboxVoiceSearchButton));
 }
@@ -306,7 +306,7 @@
   CreateScene(kNotInWebVr);
 
   model_->push_mode(kModeEditingOmnibox);
-  model_->speech.has_or_can_request_audio_permission = true;
+  model_->speech.has_or_can_request_record_audio_permission = true;
   model_->active_capturing.audio_capture_enabled = false;
   EXPECT_TRUE(OnBeginFrame());
   EXPECT_TRUE(IsVisible(kOmniboxVoiceSearchButton));
diff --git a/chrome/browser/web_applications/web_app_provider_factory.cc b/chrome/browser/web_applications/web_app_provider_factory.cc
index 5be01cf3..d17cd91b 100644
--- a/chrome/browser/web_applications/web_app_provider_factory.cc
+++ b/chrome/browser/web_applications/web_app_provider_factory.cc
@@ -47,4 +47,10 @@
   return true;
 }
 
+content::BrowserContext* WebAppProviderFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  Profile* profile = Profile::FromBrowserContext(context);
+  return profile ? profile->GetOriginalProfile() : nullptr;
+}
+
 }  //  namespace web_app
diff --git a/chrome/browser/web_applications/web_app_provider_factory.h b/chrome/browser/web_applications/web_app_provider_factory.h
index d2de433..ec7dc86 100644
--- a/chrome/browser/web_applications/web_app_provider_factory.h
+++ b/chrome/browser/web_applications/web_app_provider_factory.h
@@ -37,6 +37,8 @@
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* context) const override;
   bool ServiceIsCreatedWithBrowserContext() const override;
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
 
   DISALLOW_COPY_AND_ASSIGN(WebAppProviderFactory);
 };
diff --git a/chrome/chrome_cleaner/chrome_utils/BUILD.gn b/chrome/chrome_cleaner/chrome_utils/BUILD.gn
index f7a99c0..85d9bf2 100644
--- a/chrome/chrome_cleaner/chrome_utils/BUILD.gn
+++ b/chrome/chrome_cleaner/chrome_utils/BUILD.gn
@@ -45,17 +45,31 @@
   ]
 }
 
+source_set("extension_file_logger_lib") {
+  sources = [
+    "extension_file_logger.cc",
+    "extension_file_logger.h",
+  ]
+  deps = [
+    "//base:base",
+    "//chrome/chrome_cleaner/os:common_os",
+  ]
+}
+
 source_set("unittest_sources") {
   testonly = true
 
   sources = [
+    "extension_file_logger_unittest.cc",
     "extensions_util_unittest.cc",
   ]
 
   deps = [
+    ":extension_file_logger_lib",
     ":extensions_util_lib",
     "//base:base",
     "//base/test:test_support",
+    "//chrome/chrome_cleaner/os:common_os",
     "//chrome/chrome_cleaner/parsers/json_parser",
     "//chrome/chrome_cleaner/test:test_extensions",
     "//chrome/chrome_cleaner/test:test_util",
diff --git a/chrome/chrome_cleaner/chrome_utils/extension_file_logger.cc b/chrome/chrome_cleaner/chrome_utils/extension_file_logger.cc
new file mode 100644
index 0000000..0d819fa5
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/extension_file_logger.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 "chrome/chrome_cleaner/chrome_utils/extension_file_logger.h"
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "chrome/chrome_cleaner/os/pre_fetched_paths.h"
+
+namespace chrome_cleaner {
+namespace {
+
+void LogFilesInPath(const base::FilePath& path,
+                    std::vector<internal::FileInformation>* files) {
+  if (!base::PathExists(path))
+    return;
+  base::FileEnumerator file_enumerator(path, /*recursive=*/true,
+                                       base::FileEnumerator::FileType::FILES);
+  base::FilePath file_path = file_enumerator.Next();
+  while (!file_path.empty()) {
+    internal::FileInformation file_information;
+    if (RetrieveFileInformation(file_path, /*include_details=*/true,
+                                &file_information)) {
+      files->push_back(file_information);
+    }
+
+    file_path = file_enumerator.Next();
+  }
+}
+
+}  // namespace
+
+ExtensionFileLogger::ExtensionFileLogger(const base::FilePath& user_data_path)
+    : user_data_path_(user_data_path) {}
+
+ExtensionFileLogger::~ExtensionFileLogger() = default;
+
+bool ExtensionFileLogger::GetExtensionFiles(
+    const base::string16& extension_id,
+    std::vector<internal::FileInformation>* files) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!initialized_)
+    CacheProfilesAndExtensions();
+
+  bool extension_found = false;
+
+  for (auto it = installed_extensions_.begin();
+       it != installed_extensions_.end(); it++) {
+    if (it->second.find(extension_id) != it->second.end()) {
+      extension_found = true;
+      LogFilesInPath(it->first.Append(extension_id), files);
+    }
+  }
+
+  return extension_found;
+}
+
+void ExtensionFileLogger::CacheExtensionIdsInProfile(
+    const base::FilePath& profile_path) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  base::FilePath profile_extensions_path = profile_path.Append(L"Extensions");
+  if (!base::PathExists(profile_extensions_path))
+    return;
+
+  base::FileEnumerator file_enumerator(
+      profile_extensions_path, /*recursive=*/false,
+      base::FileEnumerator::FileType::DIRECTORIES);
+
+  base::FilePath extension_path = file_enumerator.Next();
+
+  while (!extension_path.empty()) {
+    installed_extensions_[profile_extensions_path].insert(
+        extension_path.BaseName().value());
+    extension_path = file_enumerator.Next();
+  }
+}
+
+void ExtensionFileLogger::CacheProfilesAndExtensions() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  installed_extensions_.clear();
+
+  // Check all the directories, if one is not a profile then
+  // CacheExtensionIdsInProfile will return right away because there will
+  // be no Extensions folder.
+  base::FileEnumerator file_enumerator(
+      user_data_path_, /*recursive=*/false,
+      base::FileEnumerator::FileType::DIRECTORIES);
+  base::FilePath profile_path = file_enumerator.Next();
+
+  while (!profile_path.empty()) {
+    CacheExtensionIdsInProfile(profile_path);
+    profile_path = file_enumerator.Next();
+  }
+  initialized_ = true;
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/chrome_utils/extension_file_logger.h b/chrome/chrome_cleaner/chrome_utils/extension_file_logger.h
new file mode 100644
index 0000000..068e260
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/extension_file_logger.h
@@ -0,0 +1,55 @@
+// 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_CHROME_CLEANER_CHROME_UTILS_EXTENSION_FILE_LOGGER_H_
+#define CHROME_CHROME_CLEANER_CHROME_UTILS_EXTENSION_FILE_LOGGER_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/sequence_checker.h"
+#include "base/strings/string16.h"
+#include "chrome/chrome_cleaner/os/disk_util.h"
+
+namespace chrome_cleaner {
+
+typedef std::map<base::FilePath, std::set<base::string16>>
+    ExtensionsInProfilesMap;
+
+// Utility class to extract the file information of all the files of a given
+// extension.
+//
+// Please note that this class may have negative performance implications if
+// used in more than one place, please avoid creating multiple instances of it.
+class ExtensionFileLogger {
+ public:
+  explicit ExtensionFileLogger(const base::FilePath& user_data_path);
+  ~ExtensionFileLogger();
+
+  // Searches through all of the cached user's profiles for an extension with
+  // id |extension_id|. If one found, fills |files| with data about the
+  // extension's files.
+  // Returns false if it cannot find the extension folder.
+  //
+  // If two profiles have the same extension installed, both extension's files
+  // are going to be logged. This is intended since we are not sure if there
+  // are modified files in one of the extensions.
+  bool GetExtensionFiles(const base::string16& extension_id,
+                         std::vector<internal::FileInformation>* files);
+
+ private:
+  void CacheProfilesAndExtensions();
+  void CacheExtensionIdsInProfile(const base::FilePath& profile_path);
+
+  ExtensionsInProfilesMap installed_extensions_;
+  base::FilePath user_data_path_;
+  bool initialized_ = false;
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_CHROME_UTILS_EXTENSION_FILE_LOGGER_H_
diff --git a/chrome/chrome_cleaner/chrome_utils/extension_file_logger_unittest.cc b/chrome/chrome_cleaner/chrome_utils/extension_file_logger_unittest.cc
new file mode 100644
index 0000000..712b01f5
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/extension_file_logger_unittest.cc
@@ -0,0 +1,159 @@
+// 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/chrome_cleaner/chrome_utils/extension_file_logger.h"
+
+#include <vector>
+
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/strings/string16.h"
+#include "chrome/chrome_cleaner/os/disk_util.h"
+#include "chrome/chrome_cleaner/test/test_extensions.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_cleaner {
+
+namespace {
+
+const base::char16 kExtensionId1[] = L"extension1";
+const base::char16 kTestFileName1[] = L"file1.file";
+
+const base::char16 kExtensionId2[] = L"extension2";
+const base::char16 kTestFileName2[] = L"file2.file";
+
+const base::char16 kExtensionId3[] = L"extension3";
+const base::char16 kTestFileName3[] = L"file3.file";
+
+const base::char16 kExtensionId4[] = L"generic-extension-name";
+
+const base::char16 kExtensionId5[] = L"ghost-extension";
+
+}  // namespace
+
+class ExtensionFileLoggerTest : public testing::Test {
+ public:
+  void SetUp() override {
+    ASSERT_TRUE(fake_user_data_dir_.CreateUniqueTempDir());
+
+    ASSERT_TRUE(CreateProfileWithExtensionAndFiles(
+        fake_user_data_dir_.GetPath().Append(L"Default"), kExtensionId1,
+        {kTestFileName1}));
+
+    ASSERT_TRUE(CreateProfileWithExtensionAndFiles(
+        fake_user_data_dir_.GetPath().Append(L"Profile 1"), kExtensionId2,
+        {kTestFileName2}));
+
+    ASSERT_TRUE(CreateProfileWithExtensionAndFiles(
+        fake_user_data_dir_.GetPath().Append(L"Profile 2"), kExtensionId3,
+        {kTestFileName3}));
+
+    ASSERT_TRUE(CreateProfileWithExtensionAndFiles(
+        fake_user_data_dir_.GetPath().Append(L"Not Malicious"), kExtensionId3,
+        {kTestFileName3}));
+
+    ASSERT_TRUE(CreateProfileWithExtensionAndFiles(
+        fake_user_data_dir_.GetPath().Append(L"Profile N"), kExtensionId4,
+        {kTestFileName1, kTestFileName2, kTestFileName3}));
+
+    ASSERT_TRUE(CreateProfileWithExtensionAndFiles(
+        fake_user_data_dir_.GetPath().Append(L"Directory"), kExtensionId5, {}));
+
+    base::FilePath profile_without_extensions_folder =
+        fake_user_data_dir_.GetPath().Append(L"another dir");
+    ASSERT_TRUE(base::CreateDirectory(profile_without_extensions_folder));
+
+    base::FilePath folder_not_named_extensions =
+        profile_without_extensions_folder.Append(L"something");
+    ASSERT_TRUE(base::CreateDirectory(folder_not_named_extensions));
+
+    base::FilePath extension_path =
+        folder_not_named_extensions.Append(kExtensionId1);
+    ASSERT_TRUE(base::CreateDirectory(extension_path));
+
+    base::File extension_file(
+        extension_path.Append(kTestFileName1),
+        base::File::Flags::FLAG_CREATE | base::File::Flags::FLAG_READ);
+
+    ASSERT_TRUE(extension_file.IsValid());
+
+    file_logger_ =
+        std::make_unique<ExtensionFileLogger>(fake_user_data_dir_.GetPath());
+  }
+
+ protected:
+  base::ScopedTempDir fake_user_data_dir_;
+
+  std::unique_ptr<ExtensionFileLogger> file_logger_;
+};
+
+TEST_F(ExtensionFileLoggerTest, LogExtensionOnDefaultProfile) {
+  std::vector<internal::FileInformation> logged_files;
+
+  EXPECT_TRUE(file_logger_->GetExtensionFiles(kExtensionId1, &logged_files));
+
+  ASSERT_EQ(logged_files.size(), 1u);
+  EXPECT_EQ(base::FilePath(logged_files[0].path).BaseName().value(),
+            kTestFileName1);
+}
+
+TEST_F(ExtensionFileLoggerTest, LogMultipleExtensions) {
+  std::vector<internal::FileInformation> logged_files;
+
+  EXPECT_TRUE(file_logger_->GetExtensionFiles(kExtensionId1, &logged_files));
+  EXPECT_TRUE(file_logger_->GetExtensionFiles(kExtensionId2, &logged_files));
+
+  ASSERT_EQ(logged_files.size(), 2u);
+
+  EXPECT_EQ(base::FilePath(logged_files[0].path).BaseName().value(),
+            kTestFileName1);
+  EXPECT_EQ(base::FilePath(logged_files[1].path).BaseName().value(),
+            kTestFileName2);
+}
+
+TEST_F(ExtensionFileLoggerTest, LogExtensionOnTwoProfiles) {
+  std::vector<internal::FileInformation> logged_files;
+
+  EXPECT_TRUE(file_logger_->GetExtensionFiles(kExtensionId3, &logged_files));
+
+  ASSERT_EQ(logged_files.size(), 2u);
+
+  EXPECT_EQ(base::FilePath(logged_files[0].path).BaseName().value(),
+            kTestFileName3);
+  EXPECT_EQ(base::FilePath(logged_files[1].path).BaseName().value(),
+            kTestFileName3);
+}
+
+TEST_F(ExtensionFileLoggerTest, DoNotLogNonExistingExtension) {
+  std::vector<internal::FileInformation> logged_files;
+
+  EXPECT_FALSE(
+      file_logger_->GetExtensionFiles(L"RandomExtension", &logged_files));
+}
+
+TEST_F(ExtensionFileLoggerTest, LogExtensionWithMultipleFiles) {
+  std::vector<internal::FileInformation> logged_files;
+
+  EXPECT_TRUE(file_logger_->GetExtensionFiles(kExtensionId4, &logged_files));
+
+  ASSERT_EQ(logged_files.size(), 3u);
+
+  std::vector<base::string16> returned_paths = {
+      base::FilePath(logged_files[0].path).BaseName().value(),
+      base::FilePath(logged_files[1].path).BaseName().value(),
+      base::FilePath(logged_files[2].path).BaseName().value()};
+  EXPECT_THAT(returned_paths,
+              testing::UnorderedElementsAreArray(
+                  {kTestFileName1, kTestFileName2, kTestFileName3}));
+}
+
+TEST_F(ExtensionFileLoggerTest, LogExtensionWithoutFiles) {
+  std::vector<internal::FileInformation> logged_files;
+  EXPECT_TRUE(file_logger_->GetExtensionFiles(kExtensionId5, &logged_files));
+  EXPECT_EQ(logged_files.size(), 0u);
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/components/BUILD.gn b/chrome/chrome_cleaner/components/BUILD.gn
index 422e53f9..0d277f0 100644
--- a/chrome/chrome_cleaner/components/BUILD.gn
+++ b/chrome/chrome_cleaner/components/BUILD.gn
@@ -20,6 +20,7 @@
   deps = [
     "//base:base",
     "//chrome/chrome_cleaner/chrome_utils:chrome_util_lib",
+    "//chrome/chrome_cleaner/chrome_utils:extension_file_logger_lib",
     "//chrome/chrome_cleaner/chrome_utils:extensions_util_lib",
     "//chrome/chrome_cleaner/constants:common_strings",
     "//chrome/chrome_cleaner/constants:uws_id",
@@ -55,6 +56,7 @@
     "//base",
     "//base/test:test_support",
     "//chrome/chrome_cleaner/chrome_utils:chrome_util_lib",
+    "//chrome/chrome_cleaner/chrome_utils:extension_file_logger_lib",
     "//chrome/chrome_cleaner/constants:common_strings",
     "//chrome/chrome_cleaner/constants:uws_id",
     "//chrome/chrome_cleaner/http:mock_http_agent_factory",
@@ -65,6 +67,7 @@
     "//chrome/chrome_cleaner/parsers/json_parser",
     "//chrome/chrome_cleaner/test:test_branding_header",
     "//chrome/chrome_cleaner/test:test_component",
+    "//chrome/chrome_cleaner/test:test_extensions",
     "//chrome/chrome_cleaner/test:test_pup_data",
     "//chrome/chrome_cleaner/test:test_util",
     "//components/chrome_cleaner/public/constants",
diff --git a/chrome/chrome_cleaner/components/system_report_component.cc b/chrome/chrome_cleaner/components/system_report_component.cc
index fcdb5e8..de3b39df 100644
--- a/chrome/chrome_cleaner/components/system_report_component.cc
+++ b/chrome/chrome_cleaner/components/system_report_component.cc
@@ -41,6 +41,7 @@
 #include "base/win/scoped_variant.h"
 #include "base/win/windows_version.h"
 #include "chrome/chrome_cleaner/chrome_utils/chrome_util.h"
+#include "chrome/chrome_cleaner/chrome_utils/extension_file_logger.h"
 #include "chrome/chrome_cleaner/chrome_utils/extensions_util.h"
 #include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
 #include "chrome/chrome_cleaner/constants/common_registry_names.h"
@@ -683,18 +684,23 @@
   }
 }
 
-void ReportForcelistExtensions() {
+void ReportForcelistExtensions(ExtensionFileLogger* extension_file_logger) {
   std::vector<ExtensionPolicyRegistryEntry> policies;
   GetExtensionForcelistRegistryPolicies(&policies);
 
   for (const ExtensionPolicyRegistryEntry& policy : policies) {
+    std::vector<internal::FileInformation> extension_files;
+    extension_file_logger->GetExtensionFiles(policy.extension_id,
+                                             &extension_files);
+
     LoggingServiceAPI::GetInstance()->AddInstalledExtension(
-        policy.extension_id,
-        ExtensionInstallMethod::POLICY_EXTENSION_FORCELIST);
+        policy.extension_id, ExtensionInstallMethod::POLICY_EXTENSION_FORCELIST,
+        extension_files);
   }
 }
 
-void ReportInstalledExtensions(JsonParserAPI* json_parser) {
+void ReportInstalledExtensions(JsonParserAPI* json_parser,
+                               ExtensionFileLogger* extension_file_logger) {
   DCHECK(json_parser);
   // TODO(proberge): Temporarily allowing syncing to avoid crashes in debug
   // mode. This isn't catastrophic since the cleanup tool doesn't have a UI and
@@ -702,7 +708,7 @@
   base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK);
   base::ScopedAllowBaseSyncPrimitivesForTesting allow_sync;
 
-  ReportForcelistExtensions();
+  ReportForcelistExtensions(extension_file_logger);
 
   std::vector<ExtensionPolicyRegistryEntry> extension_settings_policies;
   WaitableEvent extension_settings_done(
@@ -734,23 +740,46 @@
   default_extensions_done.TimedWaitUntil(end_time);
 
   // Log extensions that were found
-  for (const ExtensionPolicyRegistryEntry& policy : extension_settings_policies)
-    LoggingServiceAPI::GetInstance()->AddInstalledExtension(
-        policy.extension_id, ExtensionInstallMethod::POLICY_EXTENSION_SETTINGS);
+  for (const ExtensionPolicyRegistryEntry& policy :
+       extension_settings_policies) {
+    std::vector<internal::FileInformation> extension_files;
+    extension_file_logger->GetExtensionFiles(policy.extension_id,
+                                             &extension_files);
 
-  for (const ExtensionPolicyFile& policy : master_preferences_policies)
     LoggingServiceAPI::GetInstance()->AddInstalledExtension(
-        policy.extension_id, ExtensionInstallMethod::POLICY_MASTER_PREFERENCES);
+        policy.extension_id, ExtensionInstallMethod::POLICY_EXTENSION_SETTINGS,
+        extension_files);
+  }
 
-  for (const ExtensionPolicyFile& policy : default_extension_policies)
+  for (const ExtensionPolicyFile& policy : master_preferences_policies) {
+    std::vector<internal::FileInformation> extension_files;
+    extension_file_logger->GetExtensionFiles(policy.extension_id,
+                                             &extension_files);
+
     LoggingServiceAPI::GetInstance()->AddInstalledExtension(
-        policy.extension_id, ExtensionInstallMethod::DEFAULT_APPS_EXTENSION);
+        policy.extension_id, ExtensionInstallMethod::POLICY_MASTER_PREFERENCES,
+        extension_files);
+  }
+
+  for (const ExtensionPolicyFile& policy : default_extension_policies) {
+    std::vector<internal::FileInformation> extension_files;
+    extension_file_logger->GetExtensionFiles(policy.extension_id,
+                                             &extension_files);
+
+    LoggingServiceAPI::GetInstance()->AddInstalledExtension(
+        policy.extension_id, ExtensionInstallMethod::DEFAULT_APPS_EXTENSION,
+        extension_files);
+  }
 }
 
 }  // namespace
 
 SystemReportComponent::SystemReportComponent(JsonParserAPI* json_parser)
-    : created_report_(false), json_parser_(json_parser) {}
+    : created_report_(false),
+      json_parser_(json_parser),
+      user_data_path_(
+          PreFetchedPaths::GetInstance()->GetLocalAppDataFolder().Append(
+              L"Google\\Chrome\\User Data")) {}
 
 void SystemReportComponent::PreScan() {}
 
@@ -785,11 +814,13 @@
   if (created_report_)
     return;
 
+  ExtensionFileLogger extension_file_logger(user_data_path_);
+
   // Don't collect a system report if logs won't be uploaded.
   if (!logging_service->uploads_enabled()) {
     // TODO(proberge): Remove this by EOQ4 2018.
     if (base::CommandLine::ForCurrentProcess()->HasSwitch(kDumpRawLogsSwitch)) {
-      ReportInstalledExtensions(json_parser_);
+      ReportInstalledExtensions(json_parser_, &extension_file_logger);
       created_report_ = true;
     }
 
@@ -825,9 +856,14 @@
   ReportInstalledPrograms();
   ReportLayeredServiceProviders();
   ReportProxySettingsInformation();
-  ReportInstalledExtensions(json_parser_);
+  ReportInstalledExtensions(json_parser_, &extension_file_logger);
 
   created_report_ = true;
 }
 
+void SystemReportComponent::SetUserDataPathForTesting(
+    const base::FilePath& test_user_data_path) {
+  user_data_path_ = test_user_data_path;
+}
+
 }  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/components/system_report_component.h b/chrome/chrome_cleaner/components/system_report_component.h
index e9d8366..1923f93 100644
--- a/chrome/chrome_cleaner/components/system_report_component.h
+++ b/chrome/chrome_cleaner/components/system_report_component.h
@@ -7,6 +7,7 @@
 
 #include <vector>
 
+#include "chrome/chrome_cleaner/chrome_utils/extension_file_logger.h"
 #include "chrome/chrome_cleaner/components/component_api.h"
 #include "chrome/chrome_cleaner/parsers/json_parser/json_parser_api.h"
 
@@ -29,10 +30,12 @@
 
   // Only exposed for tests.
   bool created_report() { return created_report_; }
+  void SetUserDataPathForTesting(const base::FilePath& test_user_data_path);
 
  private:
   bool created_report_;
   JsonParserAPI* json_parser_;
+  base::FilePath user_data_path_;
 };
 
 }  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/components/system_report_component_unittest.cc b/chrome/chrome_cleaner/components/system_report_component_unittest.cc
index 927c1607..1430ccc 100644
--- a/chrome/chrome_cleaner/components/system_report_component_unittest.cc
+++ b/chrome/chrome_cleaner/components/system_report_component_unittest.cc
@@ -30,6 +30,7 @@
 #include "chrome/chrome_cleaner/os/pre_fetched_paths.h"
 #include "chrome/chrome_cleaner/os/registry_util.h"
 #include "chrome/chrome_cleaner/parsers/json_parser/test_json_parser.h"
+#include "chrome/chrome_cleaner/test/test_extensions.h"
 #include "chrome/chrome_cleaner/test/test_file_util.h"
 #include "chrome/chrome_cleaner/test/test_pup_data.h"
 #include "chrome/chrome_cleaner/test/test_util.h"
@@ -74,48 +75,49 @@
 const ReportTestData kExtensionPolicyEmpty{
     HKEY_LOCAL_MACHINE, kChromePoliciesWhitelistKeyPath, L"test1", L""};
 
-constexpr base::char16 kTestExtensionId1[] =
+constexpr base::char16 kTestingExtensionId1[] =
     L"ababababcdcdcdcdefefefefghghghgh";
-constexpr base::char16 kTestExtensionId1WithUpdateUrl[] =
+constexpr base::char16 kTestingExtensionId1WithUpdateUrl[] =
     L"ababababcdcdcdcdefefefefghghghgh;https://clients2.google.com/service/"
     L"update2/crx";
-constexpr base::char16 kTestExtensionId2[] =
+constexpr base::char16 kTestingExtensionId2[] =
     L"aaaabbbbccccddddeeeeffffgggghhhh";
-constexpr base::char16 kTestExtensionId2WithUpdateUrl[] =
+constexpr base::char16 kTestingExtensionId2WithUpdateUrl[] =
     L"aaaabbbbccccddddeeeeffffgggghhhh;https://clients2.google.com/service/"
     L"update2/crx";
 
 const ReportTestData extension_policies[] = {
     {HKEY_LOCAL_MACHINE, kChromePoliciesWhitelistKeyPath, L"test1",
-     kTestExtensionId1},
+     kTestingExtensionId1},
     {HKEY_CURRENT_USER, kChromePoliciesWhitelistKeyPath, L"test2",
-     kTestExtensionId1},
+     kTestingExtensionId1},
     {HKEY_LOCAL_MACHINE, kChromePoliciesForcelistKeyPath, L"test3",
-     kTestExtensionId2WithUpdateUrl},
+     kTestingExtensionId2WithUpdateUrl},
     {HKEY_CURRENT_USER, kChromePoliciesForcelistKeyPath, L"test4",
-     kTestExtensionId2WithUpdateUrl},
+     kTestingExtensionId2WithUpdateUrl},
     {HKEY_LOCAL_MACHINE, kChromiumPoliciesWhitelistKeyPath, L"test5",
-     kTestExtensionId1},
+     kTestingExtensionId1},
     {HKEY_CURRENT_USER, kChromiumPoliciesWhitelistKeyPath, L"test6",
-     kTestExtensionId1},
+     kTestingExtensionId1},
     {HKEY_LOCAL_MACHINE, kChromiumPoliciesForcelistKeyPath, L"test7",
-     kTestExtensionId2WithUpdateUrl},
+     kTestingExtensionId2WithUpdateUrl},
     {HKEY_CURRENT_USER, kChromiumPoliciesForcelistKeyPath, L"test8",
-     kTestExtensionId2WithUpdateUrl},
+     kTestingExtensionId2WithUpdateUrl},
 };
 
 const ReportTestData extension_forcelist_policies[] = {
     {HKEY_LOCAL_MACHINE, kChromePoliciesForcelistKeyPath, L"test1",
-     kTestExtensionId1WithUpdateUrl},
+     kTestingExtensionId1WithUpdateUrl},
     {HKEY_CURRENT_USER, kChromePoliciesForcelistKeyPath, L"test2",
-     kTestExtensionId2WithUpdateUrl},
+     kTestingExtensionId2WithUpdateUrl},
 };
 
 const UwSId kFakePupId = 42;
 
-const wchar_t kFakeChromeFolder[] = L"google\\chrome\\application\\42.12.34.56";
+const wchar_t kFakeChromeFolderForTests[] =
+    L"google\\chrome\\application\\42.12.34.56";
 
-const char kDefaultExtensionsJson[] = R"(
+const char kDefaultExtensionsJsonForTests[] = R"(
     {
       "ababababcdcdcdcdefefefefghghghgh" : {
         "external_update_url": "https://clients2.google.com/service/update2/crx"
@@ -125,7 +127,8 @@
       }
     })";
 // Don't include the null-terminator in the length.
-const int kDefaultExtensionsJsonSize = sizeof(kDefaultExtensionsJson) - 1;
+const int kDefaultExtensionsJsonForTestsSize =
+    sizeof(kDefaultExtensionsJsonForTests) - 1;
 
 // ExtensionSettings that has two force_installed extensions and two not.
 const wchar_t kExtensionSettingsJson[] =
@@ -145,14 +148,14 @@
       },
       "extensionwithnosettingsabcdefghi": {}
     })";
+
 const ReportTestData extension_settings_entry = {
     HKEY_LOCAL_MACHINE, kChromePoliciesKeyPath, L"ExtensionSettings",
     kExtensionSettingsJson};
 
-const wchar_t kChromeExePath[] = L"google\\chrome\\application";
-const wchar_t kMasterPreferencesFileName[] = L"master_preferences";
-const char kMasterPreferencesJson[] =
-    R"(
+const wchar_t kChromeExePathForTests[] = L"google\\chrome\\application";
+const wchar_t kMasterPreferencesFileNameForTests[] = L"master_preferences";
+const char kMasterPreferencesJsonForTests[] = R"(
     {
       "homepage": "http://dev.chromium.org/",
       "extensions": {
@@ -173,6 +176,9 @@
       }
     })";
 
+typedef std::map<base::string16, std::vector<base::string16>>
+    ExtensionIdToFileNamesMap;
+
 class SystemReportComponentTest : public testing::Test {
  public:
   SystemReportComponentTest() : component_(&json_parser_) {}
@@ -184,6 +190,21 @@
     LoggingServiceAPI::SetInstanceForTesting(cleaner_logging_service_);
     registry_override_.OverrideRegistry(HKEY_CURRENT_USER);
     registry_override_.OverrideRegistry(HKEY_LOCAL_MACHINE);
+
+    ASSERT_TRUE(fake_user_data_.CreateUniqueTempDir());
+
+    extension_id_to_filenames_map_[kTestingExtensionId1] = {L"file1.test",
+                                                            L"file2.test"};
+    ASSERT_TRUE(CreateProfileWithExtensionAndFiles(
+        fake_user_data_.GetPath().Append(L"Profile 1"), kTestingExtensionId1,
+        extension_id_to_filenames_map_[kTestingExtensionId1]));
+
+    extension_id_to_filenames_map_[kTestingExtensionId2] = {L"file1.test"};
+    ASSERT_TRUE(CreateProfileWithExtensionAndFiles(
+        fake_user_data_.GetPath().Append(L"Profile 2"), kTestingExtensionId2,
+        extension_id_to_filenames_map_[kTestingExtensionId2]));
+
+    component_.SetUserDataPathForTesting(fake_user_data_.GetPath());
   }
 
   void TearDown() override {
@@ -201,6 +222,8 @@
   SystemReportComponent component_;
   CleanerLoggingService* cleaner_logging_service_ = nullptr;
   registry_util::RegistryOverrideManager registry_override_;
+  base::ScopedTempDir fake_user_data_;
+  ExtensionIdToFileNamesMap extension_id_to_filenames_map_;
 };
 
 template <typename Component>
@@ -256,6 +279,41 @@
   return false;
 }
 
+// Returns whether |extension_files| contains the expected file information
+// of |extension_id|.
+::testing::AssertionResult ExtensionsReportedWithExpectedFiles(
+    const RepeatedPtrField<InstalledExtension>& reported_extensions,
+    const ExtensionIdToFileNamesMap& extension_id_to_filenames_map) {
+  for (const auto& installed_extension : reported_extensions) {
+    const RepeatedPtrField<FileInformation> extension_files =
+        installed_extension.extension_files();
+
+    std::unordered_set<base::string16> expected_files(
+        extension_id_to_filenames_map
+            .at(base::UTF8ToUTF16(installed_extension.extension_id()))
+            .begin(),
+        extension_id_to_filenames_map
+            .at(base::UTF8ToUTF16(installed_extension.extension_id()))
+            .end());
+
+    if (static_cast<size_t>(extension_files.size()) != expected_files.size())
+      return ::testing::AssertionFailure()
+             << "Different number of files than expected for extension "
+             << installed_extension.extension_id();
+
+    for (const auto& file : extension_files) {
+      base::string16 file_name =
+          base::FilePath(base::UTF8ToUTF16(file.path())).BaseName().value();
+
+      if (expected_files.find(file_name) == expected_files.end())
+        return ::testing::AssertionFailure()
+               << "file : " << file_name << " not reported for extension "
+               << installed_extension.extension_id();
+    }
+  }
+  return ::testing::AssertionSuccess();
+}
+
 // Returns whether |reported_extensions| contains an entry for an extension with
 // |extension_id| and |install_method|.
 bool InstalledExtensionReported(
@@ -521,13 +579,14 @@
       base::PathService::Get(base::DIR_PROGRAM_FILES, &program_files_dir));
 
   base::FilePath fake_apps_dir(
-      program_files_dir.Append(kFakeChromeFolder).Append(L"default_apps"));
+      program_files_dir.Append(kFakeChromeFolderForTests)
+          .Append(L"default_apps"));
   ASSERT_TRUE(base::CreateDirectoryAndGetError(fake_apps_dir, nullptr));
 
   base::FilePath default_extensions_file =
       fake_apps_dir.Append(L"external_extensions.json");
-  CreateFileWithContent(default_extensions_file, kDefaultExtensionsJson,
-                        kDefaultExtensionsJsonSize);
+  CreateFileWithContent(default_extensions_file, kDefaultExtensionsJsonForTests,
+                        kDefaultExtensionsJsonForTestsSize);
   ASSERT_TRUE(base::PathExists(default_extensions_file));
 
   base::test::ScopedCommandLine scoped_command_line;
@@ -536,11 +595,15 @@
 
   EXPECT_EQ(2, report.system_report().installed_extensions().size());
   EXPECT_TRUE(InstalledExtensionReported(
-      report.system_report().installed_extensions(), kTestExtensionId1,
+      report.system_report().installed_extensions(), kTestingExtensionId1,
       ExtensionInstallMethod::DEFAULT_APPS_EXTENSION));
   EXPECT_TRUE(InstalledExtensionReported(
-      report.system_report().installed_extensions(), kTestExtensionId2,
+      report.system_report().installed_extensions(), kTestingExtensionId2,
       ExtensionInstallMethod::DEFAULT_APPS_EXTENSION));
+
+  EXPECT_TRUE(ExtensionsReportedWithExpectedFiles(
+      report.system_report().installed_extensions(),
+      extension_id_to_filenames_map_));
 }
 
 TEST_F(SystemReportComponentTest, ReportExtensionSettings) {
@@ -560,11 +623,15 @@
   ChromeCleanerReport report = GetChromeCleanerReport();
 
   EXPECT_TRUE(InstalledExtensionReported(
-      report.system_report().installed_extensions(), kTestExtensionId1,
+      report.system_report().installed_extensions(), kTestingExtensionId1,
       ExtensionInstallMethod::POLICY_EXTENSION_SETTINGS));
   EXPECT_TRUE(InstalledExtensionReported(
-      report.system_report().installed_extensions(), kTestExtensionId2,
+      report.system_report().installed_extensions(), kTestingExtensionId2,
       ExtensionInstallMethod::POLICY_EXTENSION_SETTINGS));
+
+  EXPECT_TRUE(ExtensionsReportedWithExpectedFiles(
+      report.system_report().installed_extensions(),
+      extension_id_to_filenames_map_));
 }
 
 TEST_F(SystemReportComponentTest, ReportMasterPreferencesExtensions) {
@@ -576,13 +643,13 @@
   ASSERT_TRUE(
       base::PathService::Get(base::DIR_PROGRAM_FILES, &program_files_dir));
 
-  base::FilePath chrome_dir(program_files_dir.Append(kChromeExePath));
+  base::FilePath chrome_dir(program_files_dir.Append(kChromeExePathForTests));
   ASSERT_TRUE(base::CreateDirectoryAndGetError(chrome_dir, nullptr));
 
   base::FilePath master_preferences =
-      chrome_dir.Append(kMasterPreferencesFileName);
-  CreateFileWithContent(master_preferences, kMasterPreferencesJson,
-                        sizeof(kMasterPreferencesJson) - 1);
+      chrome_dir.Append(kMasterPreferencesFileNameForTests);
+  CreateFileWithContent(master_preferences, kMasterPreferencesJsonForTests,
+                        sizeof(kMasterPreferencesJsonForTests) - 1);
   ASSERT_TRUE(base::PathExists(master_preferences));
 
   base::test::ScopedCommandLine scoped_command_line;
@@ -591,11 +658,15 @@
 
   EXPECT_EQ(2, report.system_report().installed_extensions().size());
   EXPECT_TRUE(InstalledExtensionReported(
-      report.system_report().installed_extensions(), kTestExtensionId1,
+      report.system_report().installed_extensions(), kTestingExtensionId1,
       ExtensionInstallMethod::POLICY_MASTER_PREFERENCES));
   EXPECT_TRUE(InstalledExtensionReported(
-      report.system_report().installed_extensions(), kTestExtensionId2,
+      report.system_report().installed_extensions(), kTestingExtensionId2,
       ExtensionInstallMethod::POLICY_MASTER_PREFERENCES));
+
+  EXPECT_TRUE(ExtensionsReportedWithExpectedFiles(
+      report.system_report().installed_extensions(),
+      extension_id_to_filenames_map_));
 }
 
 }  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/cleaner_logging_service.cc b/chrome/chrome_cleaner/logging/cleaner_logging_service.cc
index 57d1d07..61df1ff 100644
--- a/chrome/chrome_cleaner/logging/cleaner_logging_service.cc
+++ b/chrome/chrome_cleaner/logging/cleaner_logging_service.cc
@@ -29,6 +29,7 @@
 #include "chrome/chrome_cleaner/logging/api_keys.h"
 #include "chrome/chrome_cleaner/logging/pending_logs_service.h"
 #include "chrome/chrome_cleaner/logging/proto/removal_status.pb.h"
+#include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
 #include "chrome/chrome_cleaner/logging/registry_logger.h"
 #include "chrome/chrome_cleaner/logging/utils.h"
 #include "chrome/chrome_cleaner/os/disk_util.h"
@@ -585,13 +586,19 @@
 
 void CleanerLoggingService::AddInstalledExtension(
     const base::string16& extension_id,
-    ExtensionInstallMethod install_method) {
+    ExtensionInstallMethod install_method,
+    const std::vector<internal::FileInformation>& extension_files) {
   base::AutoLock lock(lock_);
   ChromeCleanerReport_SystemReport_InstalledExtension* installed_extension =
       chrome_cleaner_report_.mutable_system_report()
           ->add_installed_extensions();
   installed_extension->set_extension_id(base::UTF16ToUTF8(extension_id));
   installed_extension->set_install_method(install_method);
+  for (const auto& file : extension_files) {
+    FileInformation proto_file_information;
+    FileInformationToProtoObject(file, &proto_file_information);
+    *installed_extension->add_extension_files() = proto_file_information;
+  }
 }
 
 void CleanerLoggingService::AddScheduledTask(
diff --git a/chrome/chrome_cleaner/logging/cleaner_logging_service.h b/chrome/chrome_cleaner/logging/cleaner_logging_service.h
index b0149970..f4a6b8a 100644
--- a/chrome/chrome_cleaner/logging/cleaner_logging_service.h
+++ b/chrome/chrome_cleaner/logging/cleaner_logging_service.h
@@ -89,12 +89,15 @@
                                bool autodetect) override;
   void SetWinHttpProxySettings(const base::string16& config,
                                const base::string16& bypass) override;
-  void AddInstalledExtension(const base::string16& extension_id,
-                             ExtensionInstallMethod install_method) override;
+  void AddInstalledExtension(
+      const base::string16& extension_id,
+      ExtensionInstallMethod install_method,
+      const std::vector<internal::FileInformation>& extension_files) override;
   void AddScheduledTask(
       const base::string16& name,
       const base::string16& description,
       const std::vector<internal::FileInformation>& actions) override;
+
   void LogProcessInformation(SandboxType process_type,
                              const SystemResourceUsage& usage) override;
 
diff --git a/chrome/chrome_cleaner/logging/cleaner_logging_service_unittest.cc b/chrome/chrome_cleaner/logging/cleaner_logging_service_unittest.cc
index eb94ffa0..a00dbb6 100644
--- a/chrome/chrome_cleaner/logging/cleaner_logging_service_unittest.cc
+++ b/chrome/chrome_cleaner/logging/cleaner_logging_service_unittest.cc
@@ -1074,10 +1074,17 @@
   ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
   ASSERT_EQ(report.system_report().installed_extensions_size(), 0);
 
+  internal::FileInformation file1, file2;
+  const base::string16 kFilePath1 = L"path/file1";
+  const base::string16 kFilePath2 = L"path/file2";
+  file1.path = kFilePath1;
+  file2.path = kFilePath2;
   logging_service_->AddInstalledExtension(
-      kExtensionId, ExtensionInstallMethod::POLICY_EXTENSION_FORCELIST);
+      kExtensionId, ExtensionInstallMethod::POLICY_EXTENSION_FORCELIST,
+      {file1});
   logging_service_->AddInstalledExtension(
-      kExtensionId2, ExtensionInstallMethod::POLICY_MASTER_PREFERENCES);
+      kExtensionId2, ExtensionInstallMethod::POLICY_MASTER_PREFERENCES,
+      {file1, file2});
 
   ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
   ASSERT_EQ(report.system_report().installed_extensions_size(), 2);
@@ -1087,12 +1094,23 @@
   EXPECT_EQ(base::WideToUTF8(kExtensionId), installed_extension.extension_id());
   EXPECT_EQ(ExtensionInstallMethod::POLICY_EXTENSION_FORCELIST,
             installed_extension.install_method());
+  ASSERT_EQ(installed_extension.extension_files().size(), 1);
+  EXPECT_EQ(installed_extension.extension_files(0).path(),
+            base::UTF16ToUTF8(kFilePath1));
 
   installed_extension = report.system_report().installed_extensions(1);
   EXPECT_EQ(base::WideToUTF8(kExtensionId2),
             installed_extension.extension_id());
   EXPECT_EQ(ExtensionInstallMethod::POLICY_MASTER_PREFERENCES,
             installed_extension.install_method());
+  ASSERT_EQ(installed_extension.extension_files().size(), 2);
+
+  std::vector<std::string> reported_files = {
+      installed_extension.extension_files(0).path(),
+      installed_extension.extension_files(1).path()};
+  EXPECT_THAT(reported_files, testing::UnorderedElementsAreArray(
+                                  {base::UTF16ToUTF8(kFilePath1),
+                                   base::UTF16ToUTF8(kFilePath2)}));
 }
 
 TEST_P(CleanerLoggingServiceTest, AddScheduledTask) {
diff --git a/chrome/chrome_cleaner/logging/logging_service_api.h b/chrome/chrome_cleaner/logging/logging_service_api.h
index 14a4121..a4e80de9 100644
--- a/chrome/chrome_cleaner/logging/logging_service_api.h
+++ b/chrome/chrome_cleaner/logging/logging_service_api.h
@@ -161,8 +161,10 @@
                                        const base::string16& bypass) = 0;
 
   // Add an installed extension to the system report.
-  virtual void AddInstalledExtension(const base::string16& extension_id,
-                                     ExtensionInstallMethod install_method) = 0;
+  virtual void AddInstalledExtension(
+      const base::string16& extension_id,
+      ExtensionInstallMethod install_method,
+      const std::vector<internal::FileInformation>& extension_files) = 0;
 
   // Add a scheduled task to the system report.
   virtual void AddScheduledTask(
diff --git a/chrome/chrome_cleaner/logging/mock_logging_service.h b/chrome/chrome_cleaner/logging/mock_logging_service.h
index ec0609f..b20bbda 100644
--- a/chrome/chrome_cleaner/logging/mock_logging_service.h
+++ b/chrome/chrome_cleaner/logging/mock_logging_service.h
@@ -72,9 +72,11 @@
   MOCK_METHOD2(SetWinHttpProxySettings,
                void(const base::string16& config,
                     const base::string16& bypass));
-  MOCK_METHOD2(AddInstalledExtension,
-               void(const base::string16& extension_id,
-                    ExtensionInstallMethod install_method));
+  MOCK_METHOD3(
+      AddInstalledExtension,
+      void(const base::string16& extension_id,
+           ExtensionInstallMethod install_method,
+           const std::vector<internal::FileInformation>& file_information));
   MOCK_METHOD3(AddScheduledTask,
                void(const base::string16& name,
                     const base::string16& description,
diff --git a/chrome/chrome_cleaner/logging/noop_logging_service.cc b/chrome/chrome_cleaner/logging/noop_logging_service.cc
index 9e028a8a..bbaa0cd0 100644
--- a/chrome/chrome_cleaner/logging/noop_logging_service.cc
+++ b/chrome/chrome_cleaner/logging/noop_logging_service.cc
@@ -91,7 +91,8 @@
 
 void NoOpLoggingService::AddInstalledExtension(
     const base::string16& extension_id,
-    ExtensionInstallMethod install_method) {}
+    ExtensionInstallMethod install_method,
+    const std::vector<internal::FileInformation>& extension_files) {}
 
 void NoOpLoggingService::AddScheduledTask(
     const base::string16& /*name*/,
diff --git a/chrome/chrome_cleaner/logging/noop_logging_service.h b/chrome/chrome_cleaner/logging/noop_logging_service.h
index c062731..3bc8107 100644
--- a/chrome/chrome_cleaner/logging/noop_logging_service.h
+++ b/chrome/chrome_cleaner/logging/noop_logging_service.h
@@ -67,12 +67,15 @@
                                bool autodetect) override;
   void SetWinHttpProxySettings(const base::string16& config,
                                const base::string16& bypass) override;
-  void AddInstalledExtension(const base::string16& extension_id,
-                             ExtensionInstallMethod install_method) override;
+  void AddInstalledExtension(
+      const base::string16& extension_id,
+      ExtensionInstallMethod install_method,
+      const std::vector<internal::FileInformation>& extension_files) override;
   void AddScheduledTask(
       const base::string16& name,
       const base::string16& description,
       const std::vector<internal::FileInformation>& actions) override;
+
   void LogProcessInformation(SandboxType process_type,
                              const SystemResourceUsage& usage) override;
   bool AllExpectedRemovalsConfirmed() const override;
diff --git a/chrome/chrome_cleaner/logging/reporter_logging_service.cc b/chrome/chrome_cleaner/logging/reporter_logging_service.cc
index 241c7f6..85d0811 100644
--- a/chrome/chrome_cleaner/logging/reporter_logging_service.cc
+++ b/chrome/chrome_cleaner/logging/reporter_logging_service.cc
@@ -276,7 +276,8 @@
 
 void ReporterLoggingService::AddInstalledExtension(
     const base::string16& extension_id,
-    ExtensionInstallMethod install_method) {}
+    ExtensionInstallMethod install_method,
+    const std::vector<internal::FileInformation>& extension_files) {}
 
 void ReporterLoggingService::AddScheduledTask(
     const base::string16& /*name*/,
diff --git a/chrome/chrome_cleaner/logging/reporter_logging_service.h b/chrome/chrome_cleaner/logging/reporter_logging_service.h
index c3170e031..e418fbf 100644
--- a/chrome/chrome_cleaner/logging/reporter_logging_service.h
+++ b/chrome/chrome_cleaner/logging/reporter_logging_service.h
@@ -70,12 +70,15 @@
                                bool autodetect) override;
   void SetWinHttpProxySettings(const base::string16& config,
                                const base::string16& bypass) override;
-  void AddInstalledExtension(const base::string16& extension_id,
-                             ExtensionInstallMethod install_method) override;
+  void AddInstalledExtension(
+      const base::string16& extension_id,
+      ExtensionInstallMethod install_method,
+      const std::vector<internal::FileInformation>& extension_files) override;
   void AddScheduledTask(
       const base::string16& name,
       const base::string16& description,
       const std::vector<internal::FileInformation>& actions) override;
+
   void LogProcessInformation(SandboxType process_type,
                              const SystemResourceUsage& usage) override;
   bool AllExpectedRemovalsConfirmed() const override;
diff --git a/chrome/chrome_cleaner/test/test_extensions.cc b/chrome/chrome_cleaner/test/test_extensions.cc
index 83375fa..873f1c8 100644
--- a/chrome/chrome_cleaner/test/test_extensions.cc
+++ b/chrome/chrome_cleaner/test/test_extensions.cc
@@ -4,6 +4,9 @@
 
 #include "chrome/chrome_cleaner/test/test_extensions.h"
 
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+
 namespace chrome_cleaner {
 
 TestRegistryEntry::TestRegistryEntry(HKEY hkey,
@@ -15,4 +18,31 @@
 TestRegistryEntry& TestRegistryEntry::operator=(
     const TestRegistryEntry& other) = default;
 
+bool CreateProfileWithExtensionAndFiles(
+    const base::FilePath& profile_path,
+    const base::string16& extension_id,
+    const std::vector<base::string16>& extension_files) {
+  if (!base::CreateDirectory(profile_path))
+    return false;
+
+  base::FilePath extensions_folder_path = profile_path.Append(L"Extensions");
+  if (!base::CreateDirectory(extensions_folder_path))
+    return false;
+
+  base::FilePath extension_path = extensions_folder_path.Append(extension_id);
+  if (!base::CreateDirectory(extension_path))
+    return false;
+
+  for (const base::string16& file_name : extension_files) {
+    base::File extension_file(
+        extension_path.Append(file_name),
+        base::File::Flags::FLAG_CREATE | base::File::Flags::FLAG_READ);
+
+    if (!extension_file.IsValid())
+      return false;
+  }
+
+  return true;
+}
+
 }  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/test/test_extensions.h b/chrome/chrome_cleaner/test/test_extensions.h
index 03e54c8..aa64c42 100644
--- a/chrome/chrome_cleaner/test/test_extensions.h
+++ b/chrome/chrome_cleaner/test/test_extensions.h
@@ -5,6 +5,10 @@
 #ifndef CHROME_CHROME_CLEANER_TEST_TEST_EXTENSIONS_H_
 #define CHROME_CHROME_CLEANER_TEST_TEST_EXTENSIONS_H_
 
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/strings/string16.h"
 #include "base/win/registry.h"
 #include "chrome/chrome_cleaner/os/registry_util.h"
 
@@ -127,6 +131,11 @@
   "homepage": "http://dev.chromium.org/"
 })";
 
+bool CreateProfileWithExtensionAndFiles(
+    const base::FilePath& profile_path,
+    const base::string16& extension_id,
+    const std::vector<base::string16>& extension_files);
+
 }  // namespace chrome_cleaner
 
 #endif  // CHROME_CHROME_CLEANER_TEST_TEST_EXTENSIONS_H_
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc
index 8828b6d..4915458bf 100644
--- a/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_contentsecuritypolicy_unittest.cc
@@ -9,6 +9,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace errors = extensions::manifest_errors;
+namespace keys = extensions::manifest_keys;
 using extensions::ErrorUtils;
 
 class ContentSecurityPolicyManifestTest : public ChromeManifestTest {
@@ -19,12 +20,15 @@
       Testcase(
           "insecure_contentsecuritypolicy_1.json",
           ErrorUtils::FormatErrorMessage(errors::kInvalidCSPInsecureValue,
+                                         keys::kContentSecurityPolicy,
                                          "http://example.com", "script-src")),
       Testcase("insecure_contentsecuritypolicy_2.json",
                ErrorUtils::FormatErrorMessage(errors::kInvalidCSPInsecureValue,
+                                              keys::kContentSecurityPolicy,
                                               "'unsafe-inline'", "script-src")),
       Testcase("insecure_contentsecuritypolicy_3.json",
                ErrorUtils::FormatErrorMessage(
-                   errors::kInvalidCSPMissingSecureSrc, "object-src"))};
+                   errors::kInvalidCSPMissingSecureSrc,
+                   keys::kContentSecurityPolicy, "object-src"))};
   RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_WARNING);
 }
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc
index d6f2436..57e0f5f 100644
--- a/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc
@@ -20,6 +20,7 @@
 namespace extensions {
 
 namespace errors = manifest_errors;
+namespace keys = manifest_keys;
 
 class PlatformAppsManifestTest : public ChromeManifestTest {
 };
@@ -100,6 +101,7 @@
   LoadAndExpectWarning(
       "init_platform_app_csp_insecure.json",
       ErrorUtils::FormatErrorMessage(errors::kInvalidCSPInsecureValue,
+                                     keys::kPlatformAppContentSecurityPolicy,
                                      "http://www.google.com", "default-src"));
 }
 
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index 9e6a631..e8615bd 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -187,6 +187,9 @@
 #endif
 
 #if defined(OS_CHROMEOS)
+const char kChromeUIAccountManagerWelcomeHost[] = "account-manager-welcome";
+const char kChromeUIAccountManagerWelcomeURL[] =
+    "chrome://account-manager-welcome";
 const char kChromeUIActivationMessageHost[] = "activationmessage";
 const char kChromeUIBluetoothPairingHost[] = "bluetooth-pairing";
 const char kChromeUIBluetoothPairingURL[] = "chrome://bluetooth-pairing/";
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 6d48133..9cdf6ad9 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -186,6 +186,8 @@
 #endif  // defined(OS_ANDROID)
 
 #if defined(OS_CHROMEOS)
+extern const char kChromeUIAccountManagerWelcomeHost[];
+extern const char kChromeUIAccountManagerWelcomeURL[];
 extern const char kChromeUIActivationMessageHost[];
 extern const char kChromeUIBluetoothPairingHost[];
 extern const char kChromeUIBluetoothPairingURL[];
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 77a2699..f581981 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -763,7 +763,6 @@
       "../browser/prerender/prerender_nostate_prefetch_browsertest.cc",
       "../browser/prerender/prerender_test_utils.cc",
       "../browser/prerender/prerender_test_utils.h",
-      "../browser/previews/lazyload_browsertest.cc",
       "../browser/previews/previews_browsertest.cc",
       "../browser/previews/previews_lite_page_browsertest.cc",
       "../browser/previews/previews_service_browser_test.cc",
@@ -1665,6 +1664,7 @@
         "../browser/chromeos/child_accounts/time_limit_test_utils.cc",
         "../browser/chromeos/customization/customization_document_browsertest.cc",
         "../browser/chromeos/customization/customization_wallpaper_downloader_browsertest.cc",
+        "../browser/chromeos/dbus/proxy_resolution_service_provider_browsertest.cc",
         "../browser/chromeos/display/display_prefs_browsertest.cc",
         "../browser/chromeos/display/quirks_browsertest.cc",
         "../browser/chromeos/drive/drive_integration_service_browsertest.cc",
@@ -5218,6 +5218,8 @@
     sources = [
       "../app/chrome_version.rc.version",
       "../browser/extensions/api/cast_streaming/performance_test.cc",
+      "../browser/extensions/api/tab_capture/tab_capture_performance_test_base.cc",
+      "../browser/extensions/api/tab_capture/tab_capture_performance_test_base.h",
       "../browser/extensions/api/tab_capture/tab_capture_performancetest.cc",
       "../browser/extensions/chrome_extension_test_notification_observer.cc",
       "../browser/extensions/chrome_extension_test_notification_observer.h",
diff --git a/chrome/test/data/extensions/api_test/cast_streaming/performance.js b/chrome/test/data/extensions/api_test/cast_streaming/performance.js
deleted file mode 100644
index 9cb5bd4..0000000
--- a/chrome/test/data/extensions/api_test/cast_streaming/performance.js
+++ /dev/null
@@ -1,161 +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.
-
-// Run a cast v2 mirroring session for 15 seconds.
-
-console.log('Starting eval of performance.js...');
-
-chrome.test.runTests([
-  function sendTestPatterns() {
-    console.log('Starting sendTestPatterns()...');
-
-    const kMaxFrameRate = 30;
-    const kCallbackTimeoutMillis = 10000;
-    const kTestRunTimeMillis = 15000;
-
-    const rtpStream = chrome.cast.streaming.rtpStream;
-
-    // The receive port changes between browser_test invocations, and is passed
-    // as an query parameter in the URL.
-    let recvPort;
-    let autoThrottling;
-    let aesKey;
-    let aesIvMask;
-    try {
-      const params = window.location.search;
-      recvPort = parseInt(params.match(/(\?|&)port=(\d+)/)[2]);
-      chrome.test.assertTrue(recvPort > 0);
-      autoThrottling = (params.match(/(\?|&)autoThrottling=(true|false)/)[2] ==
-                            'true');
-      aesKey = params.match(/(\?|&)aesKey=([0-9A-F]{32})/)[2];
-      aesIvMask = params.match(/(\?|&)aesIvMask=([0-9A-F]{32})/)[2];
-    } catch (err) {
-      chrome.test.fail("Error parsing params -- " + err.message);
-      return;
-    }
-
-    // Start capture and wait up to kCallbackTimeoutMillis for it to start.
-    const startCapturePromise = new Promise((resolve, reject) => {
-      const timeoutId = setTimeout(() => {
-        reject(Error('chrome.tabCapture.capture() did not call back'));
-      }, kCallbackTimeoutMillis);
-
-      const captureOptions = {
-        video: true,
-        audio: true,
-        videoConstraints: {
-          mandatory: {
-            minWidth: autoThrottling ? 320 : 1920,
-            minHeight: autoThrottling ? 180 : 1080,
-            maxWidth: 1920,
-            maxHeight: 1080,
-            maxFrameRate: kMaxFrameRate,
-            enableAutoThrottling: autoThrottling,
-          }
-        }
-      };
-      console.log('About to call chrome.tabCapture.capture()...');
-      chrome.tabCapture.capture(captureOptions, captureStream => {
-        console.log('chrome.tabCapture.capture() callback is running...');
-        clearTimeout(timeoutId);
-        if (captureStream) {
-          console.log('Started tab capture.');
-          resolve(captureStream);
-        } else {
-          if (chrome.runtime.lastError) {
-            reject(chrome.runtime.lastError);
-          } else {
-            reject(Error('null stream'));
-          }
-        }
-      });
-    });
-
-    // Then, start Cast Streaming and wait up to kCallbackTimeoutMillis for it
-    // to start.
-    const startStreamingPromise = startCapturePromise.then(captureStream => {
-      console.log('Starting streaming...');
-      return new Promise((resolve, reject) => {
-        const timeoutId = setTimeout(() => {
-          reject(Error(
-            'chrome.cast.streaming.session.create() did not call back'));
-        }, kCallbackTimeoutMillis);
-
-        console.log('Creating cast streaming session...');
-        chrome.cast.streaming.session.create(
-            captureStream.getAudioTracks()[0],
-            captureStream.getVideoTracks()[0],
-            (audioId, videoId, udpId) => {
-          console.log('Cast streaming session create callback is running...');
-          clearTimeout(timeoutId);
-
-          try {
-            chrome.cast.streaming.udpTransport.setDestination(
-                udpId, { address: "127.0.0.1", port: recvPort } );
-            rtpStream.onError.addListener(() => {
-              chrome.test.fail('RTP stream error');
-            });
-            const audioParams = rtpStream.getSupportedParams(audioId)[0];
-            audioParams.payload.aesKey = aesKey;
-            audioParams.payload.aesIvMask = aesIvMask;
-            rtpStream.start(audioId, audioParams);
-            const videoParams = rtpStream.getSupportedParams(videoId)[0];
-            videoParams.payload.clockRate = kMaxFrameRate;
-            videoParams.payload.aesKey = aesKey;
-            videoParams.payload.aesIvMask = aesIvMask;
-            rtpStream.start(videoId, videoParams);
-
-            console.log('Started Cast Streaming.');
-            resolve([captureStream, audioId, videoId, udpId]);
-          } catch (error) {
-            reject(error);
-          }
-        });
-      });
-    });
-
-    // Then, let the test run for kTestRunTimeMillis, and then shut down the RTP
-    // streams and MediaStreamTracks.
-    const doneTestingPromise = startStreamingPromise.then(
-        ([captureStream, audioId, videoId, udpId]) => {
-      return new Promise(resolve => {
-        console.log(`Running test for ${kTestRunTimeMillis} ms.`);
-        setTimeout(resolve, kTestRunTimeMillis);
-      }).then(() => {
-        console.log('Stopping cast streaming...');
-        rtpStream.stop(audioId);
-        rtpStream.stop(videoId);
-        rtpStream.destroy(audioId);
-        rtpStream.destroy(videoId);
-
-        chrome.cast.streaming.udpTransport.destroy(udpId);
-
-        console.log('Stopping tab capture (MediaStreamTracks)...');
-        const tracks = captureStream.getTracks();
-        for (let i = 0; i < tracks.length; ++i) {
-          tracks[i].stop();
-        }
-      });
-    });
-
-    // If all of the above completed without error, the test run has succeeded.
-    // Otherwise, flag that the test has failed with the cause.
-    doneTestingPromise.then(() => {
-      console.log('About to chrome.test.succeed()...');
-      chrome.test.succeed();
-      console.log('Did chrome.test.succeed()...');
-    }).catch(error => {
-      if (typeof error === 'object' &&
-          ('stack' in error || 'message' in error)) {
-        chrome.test.fail(error.stack || error.message);
-      } else {
-        chrome.test.fail(String(error));
-      }
-    });
-
-    console.log('Completed sendTestPatterns(). (Waiting for promises...)');
-  }
-]);
-
-console.log('Completed eval of performance.js.');
diff --git a/chrome/test/data/extensions/api_test/cast_streaming/performance24.html b/chrome/test/data/extensions/api_test/cast_streaming/performance24.html
deleted file mode 100644
index 32fe264..0000000
--- a/chrome/test/data/extensions/api_test/cast_streaming/performance24.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<html>
-<head>
-<script src="performance.js"></script>
-</head>
-<body>
-<video autoplay width="100%" height="100%">
-  <source src="test_video_24fps.webm" type="video/webm">
-</video>
-</body>
-</html>
diff --git a/chrome/test/data/extensions/api_test/cast_streaming/performance30.html b/chrome/test/data/extensions/api_test/cast_streaming/performance30.html
deleted file mode 100644
index 0f18d03..0000000
--- a/chrome/test/data/extensions/api_test/cast_streaming/performance30.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<html>
-<head>
-<script src="performance.js"></script>
-</head>
-<body>
-<video autoplay width="100%" height="100%">
-  <source src="test_video_30fps.webm" type="video/webm">
-</video>
-</body>
-</html>
diff --git a/chrome/test/data/extensions/api_test/cast_streaming/performance60.html b/chrome/test/data/extensions/api_test/cast_streaming/performance60.html
deleted file mode 100644
index 3638fd14..0000000
--- a/chrome/test/data/extensions/api_test/cast_streaming/performance60.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<html>
-<head>
-<script src="performance.js"></script>
-</head>
-<body>
-<video autoplay width="100%" height="100%">
-  <source src="test_video_60fps.webm" type="video/webm">
-</video>
-</body>
-</html>
diff --git a/chrome/test/data/extensions/api_test/cast_streaming/perftest_extension/background.js b/chrome/test/data/extensions/api_test/cast_streaming/perftest_extension/background.js
new file mode 100644
index 0000000..7a683ec
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/cast_streaming/perftest_extension/background.js
@@ -0,0 +1,113 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+console.log('Registering chrome.runtime message listener...');
+
+chrome.runtime.onMessageExternal.addListener((request, from, sendResponse) => {
+  if (!request.start) {
+    return;
+  }
+
+  const kCallbackTimeoutMillis = 10000;
+
+  if (window.currentCaptureStream) {
+    sendResponse({success: false, reason: 'Already started!'});
+    return;
+  }
+
+  // Start capture and wait up to kCallbackTimeoutMillis for it to start.
+  const startCapturePromise = new Promise((resolve, reject) => {
+    const timeoutId = setTimeout(() => {
+      reject(Error('chrome.tabCapture.capture() did not call back'));
+    }, kCallbackTimeoutMillis);
+
+    const captureOptions = {
+      video: true,
+      audio: true,
+      videoConstraints: {
+        mandatory: {
+          minWidth: request.enableAutoThrottling ? 320 : 1920,
+          minHeight: request.enableAutoThrottling ? 180 : 1080,
+          maxWidth: 1920,
+          maxHeight: 1080,
+          maxFrameRate: (request.maxFrameRate || 30),
+        }
+      }
+    };
+    console.log('Starting tab capture...');
+    chrome.tabCapture.capture(captureOptions, captureStream => {
+      clearTimeout(timeoutId);
+      if (captureStream) {
+        console.log('Started tab capture.');
+        resolve(captureStream);
+      } else {
+        if (chrome.runtime.lastError) {
+          reject(chrome.runtime.lastError);
+        } else {
+          reject(Error('null stream'));
+        }
+      }
+    });
+  });
+
+  // Then, start Cast Streaming and wait up to kCallbackTimeoutMillis for it to
+  // start.
+  const startStreamingPromise = startCapturePromise.then(captureStream => {
+    return new Promise((resolve, reject) => {
+      const timeoutId = setTimeout(() => {
+        reject(Error(
+          'chrome.cast.streaming.session.create() did not call back'));
+      }, kCallbackTimeoutMillis);
+
+      console.log('Starting Cast streaming...');
+      chrome.cast.streaming.session.create(
+          captureStream.getAudioTracks()[0],
+          captureStream.getVideoTracks()[0],
+          (audioId, videoId, udpId) => {
+        clearTimeout(timeoutId);
+
+        try {
+          chrome.cast.streaming.udpTransport.setDestination(
+              udpId, { address: '127.0.0.1', port: request.recvPort } );
+          const rtpStream = chrome.cast.streaming.rtpStream;
+          rtpStream.onError.addListener(() => {
+            console.error('RTP stream error');
+          });
+          const audioParams = rtpStream.getSupportedParams(audioId)[0];
+          audioParams.payload.aesKey = request.aesKey;
+          audioParams.payload.aesIvMask = request.aesIvMask;
+          rtpStream.start(audioId, audioParams);
+          const videoParams = rtpStream.getSupportedParams(videoId)[0];
+          videoParams.payload.clockRate = (request.maxFrameRate || 30);
+          videoParams.payload.aesKey = request.aesKey;
+          videoParams.payload.aesIvMask = request.aesIvMask;
+          rtpStream.start(videoId, videoParams);
+
+          console.log('Started Cast streaming.');
+          window.currentCaptureStream = captureStream;
+          resolve();
+        } catch (error) {
+          reject(error);
+        }
+      });
+    });
+  });
+
+  startStreamingPromise.then(() => {
+    console.log('Sending success response...');
+    sendResponse({success: true});
+  }).catch(error => {
+    console.log('Sending error response...');
+    let errorMessage;
+    if (typeof error === 'object' &&
+        ('stack' in error || 'message' in error)) {
+      errorMessage = (error.stack || error.message);
+    } else {
+      errorMessage = String(error);
+    }
+    sendResponse({success: false, reason: errorMessage});
+  });
+
+  return true;  // Indicate that sendResponse() will be called asynchronously.
+});
diff --git a/chrome/test/data/extensions/api_test/cast_streaming/perftest_extension/manifest.json b/chrome/test/data/extensions/api_test/cast_streaming/perftest_extension/manifest.json
new file mode 100644
index 0000000..72ea5e8
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/cast_streaming/perftest_extension/manifest.json
@@ -0,0 +1,15 @@
+{
+  "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8xv6iO+j4kzj1HiBL93+XVJH/CRyAQMUHS/Z0l8nCAzaAFkW/JsNwxJqQhrZspnxLqbQxNncXs6g6bsXAwKHiEs+LSs+bIv0Gc/2ycZdhXJ8GhEsSMakog5dpQd1681c2gLK/8CrAoewE/0GIKhaFcp7a2iZlGh4Am6fgMKy0iQIDAQAB",
+  "name": "CastV2PerformanceTest extension",
+  "version": "1",
+  "manifest_version": 2,
+  "description": "Launches tabCapture and cast.streaming on an active tab.",
+  "permissions": ["tabs", "tabCapture", "cast", "cast.streaming"],
+  "background": {
+    "scripts": ["background.js"],
+    "persistent": true
+  },
+  "externally_connectable": {
+    "matches": ["*://*.chromium.org/*"]
+  }
+}
diff --git a/chrome/test/data/extensions/api_test/tab_capture/balls.html b/chrome/test/data/extensions/api_test/tab_capture/balls.html
new file mode 100644
index 0000000..8d2da6b
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tab_capture/balls.html
@@ -0,0 +1,53 @@
+<html>
+<head>
+<style>
+html {
+  width: 100%;
+  height: 100%;
+}
+
+body {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+canvas {
+  width: 1920px;
+  height: 1080px;
+}
+</style>
+<script>
+let frame_number = 0;
+
+function drawForever() {
+  // Request next animation frame.
+  requestAnimationFrame(drawForever);
+  ++frame_number;
+
+  const canvas = document.getElementsByTagName('canvas')[0];
+  const context = canvas.getContext('2d');
+
+  // Fill with white background.
+  context.fillStyle = 'rgb(255,255,255)';
+  context.fillRect(0, 0, canvas.width, canvas.height );
+
+  // Draw 200 balls of various colors at various positions.
+  for (let j = 0; j < 200; j++) {
+    const i = (j + frame_number) % 200;
+    const t = (frame_number + 3000) * (0.01 + i / 8000.0);
+    const x = (Math.sin( t ) * 0.45 + 0.5) * canvas.width;
+    const y = (Math.cos( t * 0.9 )  * 0.45 + 0.5) * canvas.height;
+    context.fillStyle = 'rgb(' + (255 - i) + ',' + (155 +i) + ', ' + i + ')';
+    context.beginPath();
+    context.arc(x, y, 50, 0, Math.PI * 2, true);
+    context.closePath();
+    context.fill();
+  }
+}
+</script>
+</head>
+<body onload="requestAnimationFrame(drawForever);">
+  <canvas width="1920" height="1080"/>
+</body>
+</html>
diff --git a/chrome/test/data/extensions/api_test/tab_capture/performance.html b/chrome/test/data/extensions/api_test/tab_capture/performance.html
deleted file mode 100644
index 8cdc3406..0000000
--- a/chrome/test/data/extensions/api_test/tab_capture/performance.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<html>
-<head>
-<script src="performance.js"></script>
-</head>
-<body>
-</body>
-</html>
diff --git a/chrome/test/data/extensions/api_test/tab_capture/performance.js b/chrome/test/data/extensions/api_test/tab_capture/performance.js
deleted file mode 100644
index d869a489e..0000000
--- a/chrome/test/data/extensions/api_test/tab_capture/performance.js
+++ /dev/null
@@ -1,142 +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.
-
-// The tests here cover the end-to-end functionality of tab capturing and
-// playback as video.  The page generates a test patter (moving balls) and
-// the rendering output of the tab is captured into a LocalMediaStream.  Then,
-// the LocalMediaStream is plugged into a video element for playback.
-//
-
-// Global to prevent gc from eating the video tag.
-var video = null;
-var captureStream = null;
-
-function stopStream(stream) {
-  const tracks = stream.getTracks();
-  for (let i = 0; i < tracks.length; ++i) {
-    tracks[i].stop();
-  }
-  chrome.test.assertFalse(stream.active);
-}
-
-function connectToVideoAndRunTest(stream) {
-  if (!stream) {
-    chrome.test.fail(chrome.runtime.lastError.message || 'null stream');
-    return;
-  }
-
-  // Create the video element, to play out the captured video; but there's no
-  // need to append it to the DOM.
-  video = document.createElement("video");
-  video.width = 1920;
-  video.height = 1080;
-  video.addEventListener("error", chrome.test.fail);
-
-  // Create a canvas and add it to the test page being captured. The draw()
-  // function below will update the canvas's content, triggering tab capture for
-  // each animation frame.
-  var canvas = document.createElement("canvas");
-  canvas.width = video.width;
-  canvas.height = video.height;
-  var context = canvas.getContext("2d");
-  document.body.appendChild(canvas);
-  var start_time = new Date().getTime();
-
-  // Play the LocalMediaStream in the video element.
-  video.srcObject = stream;
-  video.play();
-
-  var frame = 0;
-  function draw() {
-    // Run for 15 seconds.
-    if (new Date().getTime() - start_time > 15000) {
-      chrome.test.succeed();
-      // Note that the API testing framework might not terminate if we keep
-      // animating and capturing, so we have to make sure that we stop doing
-      // that here.
-      if (captureStream) {
-        stopStream(captureStream);
-        captureStream = null;
-      }
-      stopStream(stream);
-      return;
-    }
-    requestAnimationFrame(draw);
-    frame = frame + 1;
-    context.fillStyle = 'rgb(255,255,255)';
-    context.fillRect(0, 0, canvas.width, canvas.height );
-    for (var j = 0; j < 200; j++) {
-      var i = (j + frame) % 200;
-      var t = (frame + 3000) * (0.01 + i / 8000.0);
-      var x = (Math.sin( t ) * 0.45 + 0.5) * canvas.width;
-      var y = (Math.cos( t * 0.9 )  * 0.45 + 0.5) * canvas.height;
-      context.fillStyle = 'rgb(' + (255 - i) + ',' + (155 +i) + ', ' + i + ')';
-      context.beginPath();
-      context.arc(x, y, 50, 0, Math.PI * 2, true);
-      context.closePath();
-      context.fill();
-    }
-  }
-
-  // Kick it off.
-  draw();
-}
-
-// Set up a WebRTC connection and pipe |stream| through it.
-function testThroughWebRTC(stream) {
-  captureStream = stream;
-  console.log("Testing through webrtc.");
-  var sender = new RTCPeerConnection();
-  var receiver = new RTCPeerConnection();
-  sender.onicecandidate = function (event) {
-    if (event.candidate) {
-      receiver.addIceCandidate(new RTCIceCandidate(event.candidate));
-    }
-  };
-  receiver.onicecandidate = function (event) {
-    if (event.candidate) {
-      sender.addIceCandidate(new RTCIceCandidate(event.candidate));
-    }
-  };
-  receiver.onaddstream = function (event) {
-    connectToVideoAndRunTest(event.stream);
-  };
-  sender.addStream(stream);
-  sender.createOffer(function (sender_description) {
-    sender.setLocalDescription(sender_description);
-    receiver.setRemoteDescription(sender_description);
-    receiver.createAnswer(function (receiver_description) {
-      receiver.setLocalDescription(receiver_description);
-      sender.setRemoteDescription(receiver_description);
-    }, function() {
-    });
-  }, function() {
-  });
-}
-
-function tabCapturePerformanceTest() {
-  var f = connectToVideoAndRunTest;
-  if (parseInt(window.location.href.split('?WebRTC=')[1])) {
-    f = testThroughWebRTC;
-  }
-  var fps = parseInt(window.location.href.split('&fps=')[1]);
-  chrome.tabCapture.capture(
-    { video: true, audio: false,
-      videoConstraints: {
-        mandatory: {
-          minWidth: 1920,
-          minHeight: 1080,
-          maxWidth: 1920,
-          maxHeight: 1080,
-          maxFrameRate: fps,
-        }
-      }
-    },
-    f);
-}
-
-chrome.test.runTests([ tabCapturePerformanceTest ]);
-
-// TODO(hubbe): Consider capturing audio as well, need to figure out how to
-// capture relevant statistics for that though.
diff --git a/chrome/test/data/extensions/api_test/tab_capture/perftest_extension/background.js b/chrome/test/data/extensions/api_test/tab_capture/perftest_extension/background.js
new file mode 100644
index 0000000..451aad9
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tab_capture/perftest_extension/background.js
@@ -0,0 +1,123 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+console.log('Registering chrome.runtime message listener...');
+
+chrome.runtime.onMessageExternal.addListener((request, from, sendResponse) => {
+  if (!request.start) {
+    return;
+  }
+
+  const kCallbackTimeoutMillis = 10000;
+
+  if (window.currentStream) {
+    sendResponse({success: false, reason: 'Already started!'});
+    return;
+  }
+
+  // Start capture and wait up to kCallbackTimeoutMillis for it to start.
+  const startCapturePromise = new Promise((resolve, reject) => {
+    const timeoutId = setTimeout(() => {
+      reject(Error('chrome.tabCapture.capture() did not call back'));
+    }, kCallbackTimeoutMillis);
+
+    const captureOptions = {
+      video: true,
+      audio: false,
+      videoConstraints: {
+        mandatory: {
+          minWidth: 1920,
+          minHeight: 1080,
+          maxWidth: 1920,
+          maxHeight: 1080,
+          maxFrameRate: 60,
+        }
+      }
+    };
+    console.log('Starting tab capture...');
+    chrome.tabCapture.capture(captureOptions, captureStream => {
+      clearTimeout(timeoutId);
+      if (captureStream) {
+        console.log('Started tab capture.');
+        resolve(captureStream);
+      } else {
+        if (chrome.runtime.lastError) {
+          reject(chrome.runtime.lastError);
+        } else {
+          reject(Error('null stream'));
+        }
+      }
+    });
+  });
+
+  // Then, start streaming the data via WebRTC (or nothing, depending on the
+  // request).
+  const startStreamingPromise = startCapturePromise.then(captureStream => {
+    return new Promise((resolve, reject) => {
+      if (!request.passThroughWebRTC) {
+        resolve(captureStream);
+        return;
+      }
+
+      const timeoutId = setTimeout(() => {
+        reject(Error('receiver did not get a stream'));
+      }, kCallbackTimeoutMillis);
+
+      console.log('Routing through RTCPeerConnection...');
+      const sender = new RTCPeerConnection();
+      const receiver = new RTCPeerConnection();
+      sender.addStream(captureStream);
+      sender.onicecandidate = (event) => {
+        if (event.candidate) {
+          receiver.addIceCandidate(new RTCIceCandidate(event.candidate));
+        }
+      };
+      receiver.onicecandidate = (event) => {
+        if (event.candidate) {
+          sender.addIceCandidate(new RTCIceCandidate(event.candidate));
+        }
+      };
+      receiver.onaddstream = (event) => {
+        console.log('Receiving stream...');
+        resolve(event.stream);
+      };
+      sender.createOffer((sender_description) => {
+        sender.setLocalDescription(sender_description);
+        receiver.setRemoteDescription(sender_description);
+        receiver.createAnswer((receiver_description) => {
+          receiver.setLocalDescription(receiver_description);
+          sender.setRemoteDescription(receiver_description);
+        }, reject);
+      }, reject);
+
+      window.rtcSender = sender;
+      window.rtcReceiver = receiver;
+    });
+  });
+
+  // Plug the capture into a video element to finish assembling the end-to-end
+  // system.
+  startStreamingPromise.then(receiveStream => {
+    console.log('Starting receiver video playback...');
+    window.currentStream = receiveStream;
+    window.receiverVideo = document.createElement('video');
+    window.receiverVideo.srcObject = receiveStream;
+    window.receiverVideo.play();
+
+    console.log('Sending success response...');
+    sendResponse({success: true});
+  }).catch(error => {
+    console.log('Sending error response...');
+    let errorMessage;
+    if (typeof error === 'object' &&
+        ('stack' in error || 'message' in error)) {
+      errorMessage = (error.stack || error.message);
+    } else {
+      errorMessage = String(error);
+    }
+    sendResponse({success: false, reason: errorMessage});
+  });
+
+  return true;  // Indicate that sendResponse() will be called asynchronously.
+});
diff --git a/chrome/test/data/extensions/api_test/tab_capture/perftest_extension/manifest.json b/chrome/test/data/extensions/api_test/tab_capture/perftest_extension/manifest.json
new file mode 100644
index 0000000..0a51aaa
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tab_capture/perftest_extension/manifest.json
@@ -0,0 +1,15 @@
+{
+  "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8xv6iO+j4kzj1HiBL93+XVJH/CRyAQMUHS/Z0l8nCAzaAFkW/JsNwxJqQhrZspnxLqbQxNncXs6g6bsXAwKHiEs+LSs+bIv0Gc/2ycZdhXJ8GhEsSMakog5dpQd1681c2gLK/8CrAoewE/0GIKhaFcp7a2iZlGh4Am6fgMKy0iQIDAQAB",
+  "name": "TabCapturePerformanceTest extension",
+  "version": "1",
+  "manifest_version": 2,
+  "description": "Launches tabCapture on an active tab.",
+  "permissions": ["tabs", "tabCapture"],
+  "background": {
+    "scripts": ["background.js"],
+    "persistent": true
+  },
+  "externally_connectable": {
+    "matches": ["*://*.chromium.org/*"]
+  }
+}
diff --git a/chrome/test/data/lazyload/css-background-image.html b/chrome/test/data/lazyload/css-background-image.html
deleted file mode 100644
index 336c596..0000000
--- a/chrome/test/data/lazyload/css-background-image.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<!DOCTYPE html>
-<html>
-<style>
-div.css_bg_image {
-  height: 800px;
-  background-color: #cccccc;
-}
-div.a {
-  background-image: url("images/fruit1.jpg");
-}
-div.b {
-  background-image: url("images/fruit2.jpg");
-}
-</style>
-
-<body>
-  <div style="height:10000px;"></div>
-  <a name="images"></a>
-  <div class="css_bg_image a"></div>
-  <div class="css_bg_image b"></div>
-</body>
-</html>
diff --git a/chrome/test/data/lazyload/images/fruit1.jpg b/chrome/test/data/lazyload/images/fruit1.jpg
deleted file mode 100644
index 1b7005f..0000000
--- a/chrome/test/data/lazyload/images/fruit1.jpg
+++ /dev/null
Binary files differ
diff --git a/chrome/test/data/lazyload/images/fruit2.jpg b/chrome/test/data/lazyload/images/fruit2.jpg
deleted file mode 100644
index 1b7005f..0000000
--- a/chrome/test/data/lazyload/images/fruit2.jpg
+++ /dev/null
Binary files differ
diff --git a/chrome/test/data/webui/bookmarks/bookmarks_browsertest.js b/chrome/test/data/webui/bookmarks/bookmarks_browsertest.js
index ce10b564..fe0f84d 100644
--- a/chrome/test/data/webui/bookmarks/bookmarks_browsertest.js
+++ b/chrome/test/data/webui/bookmarks/bookmarks_browsertest.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 /**
- * @fileoverview Test suite for the Material Design bookmarks page.
+ * @fileoverview Test suite for the bookmarks page.
  */
 const ROOT_PATH = '../../../../../';
 
@@ -12,14 +12,14 @@
 GEN('#include "chrome/browser/prefs/incognito_mode_prefs.h"');
 GEN('#include "chrome/browser/ui/webui/bookmarks/bookmarks_browsertest.h"');
 
-function MaterialBookmarksBrowserTest() {}
+function BookmarksBrowserTest() {}
 
-MaterialBookmarksBrowserTest.prototype = {
+BookmarksBrowserTest.prototype = {
   __proto__: PolymerTest.prototype,
 
   browsePreload: 'chrome://bookmarks',
 
-  typedefCppFixture: 'MdBookmarksBrowserTest',
+  typedefCppFixture: 'BookmarksBrowserTest',
 
   extraLibraries: PolymerTest.getLibraries(ROOT_PATH).concat([
     '../test_store.js',
@@ -33,56 +33,56 @@
   runAccessibilityChecks: true,
 };
 
-function MaterialBookmarksActionsTest() {}
+function BookmarksActionsTest() {}
 
-MaterialBookmarksActionsTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksActionsTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'actions_test.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksActionsTest', 'All', function() {
+TEST_F('BookmarksActionsTest', 'All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksAppTest() {}
+function BookmarksAppTest() {}
 
-MaterialBookmarksAppTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksAppTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'app_test.js',
     ROOT_PATH + 'ui/webui/resources/js/util.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksAppTest', 'All', function() {
+TEST_F('BookmarksAppTest', 'All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksCommandManagerTest() {}
+function BookmarksCommandManagerTest() {}
 
-MaterialBookmarksCommandManagerTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksCommandManagerTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     '../settings/test_util.js',
     'command_manager_test.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksCommandManagerTest', 'All', function() {
+TEST_F('BookmarksCommandManagerTest', 'All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksDNDManagerTest() {}
+function BookmarksDNDManagerTest() {}
 
-MaterialBookmarksDNDManagerTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksDNDManagerTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'dnd_manager_test.js',
   ]),
 };
@@ -94,165 +94,165 @@
 GEN('#define MAYBE_All All');
 GEN('#endif');
 
-TEST_F('MaterialBookmarksDNDManagerTest', 'MAYBE_All', function() {
+TEST_F('BookmarksDNDManagerTest', 'MAYBE_All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksEditDialogTest() {}
+function BookmarksEditDialogTest() {}
 
-MaterialBookmarksEditDialogTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksEditDialogTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'edit_dialog_test.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksEditDialogTest', 'All', function() {
+TEST_F('BookmarksEditDialogTest', 'All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksItemTest() {}
+function BookmarksItemTest() {}
 
-MaterialBookmarksItemTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksItemTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'item_test.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksItemTest', 'All', function() {
+TEST_F('BookmarksItemTest', 'All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksListTest() {}
+function BookmarksListTest() {}
 
-MaterialBookmarksListTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksListTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'list_test.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksListTest', 'All', function() {
+TEST_F('BookmarksListTest', 'All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksReducersTest() {}
+function BookmarksReducersTest() {}
 
-MaterialBookmarksReducersTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksReducersTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'reducers_test.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksReducersTest', 'All', function() {
+TEST_F('BookmarksReducersTest', 'All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksRouterTest() {}
+function BookmarksRouterTest() {}
 
-MaterialBookmarksRouterTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksRouterTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'router_test.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksRouterTest', 'All', function() {
+TEST_F('BookmarksRouterTest', 'All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksFolderNodeTest() {}
+function BookmarksFolderNodeTest() {}
 
-MaterialBookmarksFolderNodeTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksFolderNodeTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'folder_node_test.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksFolderNodeTest', 'All', function() {
+TEST_F('BookmarksFolderNodeTest', 'All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksToastManagerTest() {}
+function BookmarksToastManagerTest() {}
 
-MaterialBookmarksToastManagerTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksToastManagerTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'toast_manager_test.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksToastManagerTest', 'All', function() {
+TEST_F('BookmarksToastManagerTest', 'All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksPolicyTest() {}
+function BookmarksPolicyTest() {}
 
-MaterialBookmarksPolicyTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksPolicyTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
   testGenPreamble: function() {
     GEN('SetIncognitoAvailability(IncognitoModePrefs::DISABLED);');
     GEN('SetCanEditBookmarks(false);');
   },
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'policy_test.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksPolicyTest', 'All', function() {
+TEST_F('BookmarksPolicyTest', 'All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksStoreTest() {}
+function BookmarksStoreTest() {}
 
-MaterialBookmarksStoreTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksStoreTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'store_test.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksStoreTest', 'All', function() {
+TEST_F('BookmarksStoreTest', 'All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksToolbarTest() {}
+function BookmarksToolbarTest() {}
 
-MaterialBookmarksToolbarTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksToolbarTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'toolbar_test.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksToolbarTest', 'All', function() {
+TEST_F('BookmarksToolbarTest', 'All', function() {
   mocha.run();
 });
 
-function MaterialBookmarksUtilTest() {}
+function BookmarksUtilTest() {}
 
-MaterialBookmarksUtilTest.prototype = {
-  __proto__: MaterialBookmarksBrowserTest.prototype,
+BookmarksUtilTest.prototype = {
+  __proto__: BookmarksBrowserTest.prototype,
 
-  extraLibraries: MaterialBookmarksBrowserTest.prototype.extraLibraries.concat([
+  extraLibraries: BookmarksBrowserTest.prototype.extraLibraries.concat([
     'util_test.js',
   ]),
 };
 
-TEST_F('MaterialBookmarksUtilTest', 'All', function() {
+TEST_F('BookmarksUtilTest', 'All', function() {
   mocha.run();
 });
diff --git a/chrome/test/data/webui/bookmarks/bookmarks_focus_test.js b/chrome/test/data/webui/bookmarks/bookmarks_focus_test.js
index bc6bc905..6743624 100644
--- a/chrome/test/data/webui/bookmarks/bookmarks_focus_test.js
+++ b/chrome/test/data/webui/bookmarks/bookmarks_focus_test.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 /**
- * @fileoverview Tests for MD Bookmarks which are run as interactive ui tests.
+ * @fileoverview Tests for Bookmarks which are run as interactive ui tests.
  * Should be used for tests which care about focus.
  */
 
@@ -12,9 +12,9 @@
 GEN_INCLUDE(
     [ROOT_PATH + 'chrome/test/data/webui/polymer_interactive_ui_test.js']);
 
-function MaterialBookmarksFocusTest() {}
+function BookmarksFocusTest() {}
 
-MaterialBookmarksFocusTest.prototype = {
+BookmarksFocusTest.prototype = {
   __proto__: PolymerInteractiveUITest.prototype,
 
   browsePreload: 'chrome://bookmarks',
@@ -29,7 +29,7 @@
   ]),
 };
 
-TEST_F('MaterialBookmarksFocusTest', 'All', function() {
+TEST_F('BookmarksFocusTest', 'All', function() {
   suite('<bookmarks-folder-node>', function() {
     let rootNode;
     let store;
diff --git a/chrome/test/data/webui/settings/multidevice_subpage_tests.js b/chrome/test/data/webui/settings/multidevice_subpage_tests.js
index b57edeb..6947216 100644
--- a/chrome/test/data/webui/settings/multidevice_subpage_tests.js
+++ b/chrome/test/data/webui/settings/multidevice_subpage_tests.js
@@ -61,6 +61,7 @@
           messagesState: pairingComplete ?
               settings.MultiDeviceFeatureState.ENABLED_BY_USER :
               settings.MultiDeviceFeatureState.FURTHER_SETUP_REQUIRED,
+          isAndroidSmsPairingComplete: pairingComplete,
         });
     Polymer.dom.flush();
   }
diff --git a/chrome/test/data/webui/settings/site_entry_tests.js b/chrome/test/data/webui/settings/site_entry_tests.js
index 62d9518..c558d4dc 100644
--- a/chrome/test/data/webui/settings/site_entry_tests.js
+++ b/chrome/test/data/webui/settings/site_entry_tests.js
@@ -140,48 +140,93 @@
     assertTrue(overflowMenuButton.closest('.row-aligned').hidden);
   });
 
+  function resetSettingsViaOverflowMenu(buttonType) {
+    assertTrue(buttonType == 'cancel-button' || buttonType == 'action-button');
+    Polymer.dom.flush();
+    const overflowMenuButton = testElement.$.overflowMenuButton;
+    assertFalse(overflowMenuButton.closest('.row-aligned').hidden);
+    // Open the reset settings dialog and make sure both cancelling the
+    // action and resetting all permissions work.
+    const overflowMenu = testElement.$.menu.get();
+    const menuItems = overflowMenu.querySelectorAll('.dropdown-item');
+
+    // Test clicking on the overflow menu button opens the menu.
+    assertFalse(overflowMenu.open);
+    overflowMenuButton.click();
+    assertTrue(overflowMenu.open);
+
+    // Open the reset settings dialog and tap the |buttonType| button.
+    assertFalse(testElement.$.confirmResetSettings.open);
+    menuItems[0].click();
+    assertTrue(testElement.$.confirmResetSettings.open);
+    const actionButtonList =
+        testElement.$.confirmResetSettings.getElementsByClassName(buttonType);
+    assertEquals(1, actionButtonList.length);
+    actionButtonList[0].click();
+
+    // Check the dialog and overflow menu are now both closed.
+    assertFalse(testElement.$.confirmResetSettings.open);
+    assertFalse(overflowMenu.open);
+  }
+
+  test('cancelling the confirm dialog on resetting settings works', function() {
+    testElement.siteGroup = TEST_MULTIPLE_SITE_GROUP;
+    resetSettingsViaOverflowMenu('cancel-button');
+  });
+
   test(
       'with multiple origins can reset settings via overflow menu', function() {
-        testElement.siteGroup = TEST_MULTIPLE_SITE_GROUP;
-        Polymer.dom.flush();
-        const overflowMenuButton = testElement.$.overflowMenuButton;
-        assertFalse(overflowMenuButton.closest('.row-aligned').hidden);
-
-        // Open the reset settings dialog and make sure both cancelling the
-        // action and resetting all permissions work.
-        const overflowMenu = testElement.$.menu.get();
-        const menuItems = overflowMenu.querySelectorAll('.dropdown-item');
-        ['cancel-button', 'action-button'].forEach(buttonType => {
-          // Test clicking on the overflow menu button opens the menu.
-          assertFalse(overflowMenu.open);
-          overflowMenuButton.click();
-          assertTrue(overflowMenu.open);
-
-          // Open the reset settings dialog and tap the |buttonType| button.
-          assertFalse(testElement.$.confirmResetSettings.open);
-          menuItems[0].click();
-          assertTrue(testElement.$.confirmResetSettings.open);
-          const actionButtonList =
-              testElement.$.confirmResetSettings.getElementsByClassName(
-                  buttonType);
-          assertEquals(1, actionButtonList.length);
-          actionButtonList[0].click();
-
-          // Check the dialog and overflow menu are now both closed.
-          assertFalse(testElement.$.confirmResetSettings.open);
-          assertFalse(overflowMenu.open);
+        let deleteCurrentEntryCalled = false;
+        testElement.addEventListener('delete-current-entry', function() {
+          deleteCurrentEntryCalled = true;
         });
 
+        // Test when entire siteGroup has no data or cookies.
+        // Clone this object to avoid propagating changes made in this test.
+        testElement.siteGroup =
+            JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+        resetSettingsViaOverflowMenu('action-button');
         // Ensure a call was made to setOriginPermissions for each origin.
         assertEquals(
             TEST_MULTIPLE_SITE_GROUP.origins.length,
             browserProxy.getCallCount('setOriginPermissions'));
+        assertTrue(deleteCurrentEntryCalled);
+
+        // Test when one origin has data and cookies.
+        // Clone this object to avoid propagating changes made in this test.
+        testElement.siteGroup =
+            JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+        testElement.siteGroup.origins[0].hasPermissionSettings = true;
+        testElement.siteGroup.origins[0].usage = 100;
+        testElement.siteGroup.origins[0].numCookies = 2;
+        deleteCurrentEntryCalled = false;
+        resetSettingsViaOverflowMenu('action-button');
+        assertFalse(deleteCurrentEntryCalled);
+        assertEquals(1, testElement.siteGroup.origins.length);
+        assertFalse(testElement.siteGroup.origins[0].hasPermissionSettings);
+        assertEquals(testElement.siteGroup.origins[0].usage, 100);
+        assertEquals(testElement.siteGroup.origins[0].numCookies, 2);
+
+        // Test when none of origin have data or cookies, but etld+1 has
+        // cookies. In this case, a placeholder origin will be created with the
+        // Etld+1 cookies number. Clone this object to avoid propagating changes
+        // made in this test.
+        testElement.siteGroup =
+            JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+        testElement.siteGroup.numCookies = 5;
+        deleteCurrentEntryCalled = false;
+        resetSettingsViaOverflowMenu('action-button');
+        assertFalse(deleteCurrentEntryCalled);
+        assertEquals(1, testElement.siteGroup.origins.length);
+        assertFalse(testElement.siteGroup.origins[0].hasPermissionSettings);
+        assertEquals(testElement.siteGroup.origins[0].usage, 0);
+        assertEquals(testElement.siteGroup.origins[0].numCookies, 5);
       });
 
   test(
       'moving from grouped to ungrouped does not get stuck in opened state',
       function() {
-        // Clone this object to avoid propogating changes made in this test.
+        // Clone this object to avoid propagating changes made in this test.
         testElement.siteGroup =
             JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
         Polymer.dom.flush();
@@ -242,7 +287,7 @@
   });
 
   test('data usage shown correctly for grouped entries', function() {
-    // Clone this object to avoid propogating changes made in this test.
+    // Clone this object to avoid propagating changes made in this test.
     const testSiteGroup = JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
     const numBytes1 = 74622;
     const numBytes2 = 1274;
@@ -262,7 +307,7 @@
   });
 
   test('data usage shown correctly for ungrouped entries', function() {
-    // Clone this object to avoid propogating changes made in this test.
+    // Clone this object to avoid propagating changes made in this test.
     const testSiteGroup = JSON.parse(JSON.stringify(TEST_SINGLE_SITE_GROUP));
     const numBytes = 74622;
     testSiteGroup.origins[0].usage = numBytes;
@@ -276,4 +321,78 @@
 
     });
   });
+
+  function clearDataViaOverflowMenu(buttonType) {
+    assertTrue(buttonType == 'cancel-button' || buttonType == 'action-button');
+    Polymer.dom.flush();
+    const overflowMenuButton = testElement.$.overflowMenuButton;
+    assertFalse(overflowMenuButton.closest('.row-aligned').hidden);
+
+    // Open the clear data dialog and make sure both cancelling the
+    // action and resetting all permissions work.
+    const overflowMenu = testElement.$.menu.get();
+    const menuItems = overflowMenu.querySelectorAll('.dropdown-item');
+    // Test clicking on the overflow menu button opens the menu.
+    assertFalse(overflowMenu.open);
+    overflowMenuButton.click();
+    assertTrue(overflowMenu.open);
+
+    // Open the clear data dialog and tap the |buttonType| button.
+    assertFalse(testElement.$.confirmClearData.open);
+    menuItems[1].click();
+    assertTrue(testElement.$.confirmClearData.open);
+    const actionButtonList =
+        testElement.$.confirmClearData.getElementsByClassName(buttonType);
+    assertEquals(1, actionButtonList.length);
+    actionButtonList[0].click();
+
+    // Check the dialog and overflow menu are now both closed.
+    assertFalse(testElement.$.confirmClearData.open);
+    assertFalse(overflowMenu.open);
+  }
+
+  test('cancelling the confirm dialog on clear data works', function() {
+    testElement.siteGroup = TEST_MULTIPLE_SITE_GROUP;
+    clearDataViaOverflowMenu('cancel-button');
+  });
+
+  test('with multiple origins can clear data via overflow menu', function() {
+    let deleteCurrentEntryCalled = false;
+    testElement.addEventListener('delete-current-entry', function() {
+      deleteCurrentEntryCalled = true;
+    });
+    // Test when all origins has no permission settings and no data.
+    // Clone this object to avoid propagating changes made in this test.
+    testElement.siteGroup =
+        JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+    clearDataViaOverflowMenu('action-button');
+    // Ensure a call was made to clearEtldPlus1DataAndCookies.
+    assertEquals(1, browserProxy.getCallCount('clearEtldPlus1DataAndCookies'));
+    assertTrue(deleteCurrentEntryCalled);
+
+    // Test when there is one origin has permissions settings.
+    // Clone this object to avoid propagating changes made in this test.
+    testElement.siteGroup =
+        JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+    deleteCurrentEntryCalled = false;
+    testElement.siteGroup.origins[0].hasPermissionSettings = true;
+    clearDataViaOverflowMenu('action-button');
+    assertFalse(deleteCurrentEntryCalled);
+    assertEquals(testElement.siteGroup.origins.length, 1);
+
+    // Test when one origin has permission settings and data, clear data only
+    // clears the data and cookies.
+    testElement.siteGroup =
+        JSON.parse(JSON.stringify(TEST_MULTIPLE_SITE_GROUP));
+    deleteCurrentEntryCalled = false;
+    testElement.siteGroup.origins[0].hasPermissionSettings = true;
+    testElement.siteGroup.origins[0].usage = 100;
+    testElement.siteGroup.origins[0].numCookies = 3;
+    clearDataViaOverflowMenu('action-button');
+    assertFalse(deleteCurrentEntryCalled);
+    assertEquals(testElement.siteGroup.origins.length, 1);
+    assertTrue(testElement.siteGroup.origins[0].hasPermissionSettings);
+    assertEquals(testElement.siteGroup.origins[0].usage, 0);
+    assertEquals(testElement.siteGroup.origins[0].numCookies, 0);
+  });
 });
diff --git a/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.js b/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.js
index e7ac82d..11b26e63 100644
--- a/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.js
+++ b/chrome/test/data/webui/settings/test_site_settings_prefs_browser_proxy.js
@@ -49,6 +49,7 @@
       'setOriginPermissions',
       'setProtocolDefault',
       'updateIncognitoStatus',
+      'clearEtldPlus1DataAndCookies',
     ]);
 
     /** @private {boolean} */
@@ -432,4 +433,9 @@
   fetchBlockAutoplayStatus() {
     this.methodCalled('fetchBlockAutoplayStatus');
   }
+
+  /** @override */
+  clearEtldPlus1DataAndCookies() {
+    this.methodCalled('clearEtldPlus1DataAndCookies');
+  }
 }
diff --git a/chrome/test/data/webui/settings/test_util.js b/chrome/test/data/webui/settings/test_util.js
index df52a80..fcb90f5 100644
--- a/chrome/test/data/webui/settings/test_util.js
+++ b/chrome/test/data/webui/settings/test_util.js
@@ -241,6 +241,7 @@
           engagement: 0,
           usage: 0,
           numCookies: 0,
+          hasPermissionSettings: false,
         },
         override);
   }
diff --git a/chromeos/services/device_sync/BUILD.gn b/chromeos/services/device_sync/BUILD.gn
index 79b95ed..ce651a8 100644
--- a/chromeos/services/device_sync/BUILD.gn
+++ b/chromeos/services/device_sync/BUILD.gn
@@ -26,6 +26,8 @@
     "cryptauth_enrollment_manager.h",
     "cryptauth_enrollment_manager_impl.cc",
     "cryptauth_enrollment_manager_impl.h",
+    "cryptauth_enrollment_result.cc",
+    "cryptauth_enrollment_result.h",
     "cryptauth_gcm_manager.cc",
     "cryptauth_gcm_manager.h",
     "cryptauth_gcm_manager_impl.cc",
diff --git a/chromeos/services/device_sync/cryptauth_enrollment_result.cc b/chromeos/services/device_sync/cryptauth_enrollment_result.cc
new file mode 100644
index 0000000..40405b7
--- /dev/null
+++ b/chromeos/services/device_sync/cryptauth_enrollment_result.cc
@@ -0,0 +1,53 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/device_sync/cryptauth_enrollment_result.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+// static
+CryptAuthEnrollmentResult::ResultCode
+CryptAuthEnrollmentResult::NetworkRequestErrorToResultCode(
+    NetworkRequestError error) {
+  switch (error) {
+    case NetworkRequestError::kOffline:
+      return CryptAuthEnrollmentResult::ResultCode::kNetworkRequestErrorOffline;
+    case NetworkRequestError::kEndpointNotFound:
+      return CryptAuthEnrollmentResult::ResultCode::
+          kNetworkRequestErrorEndpointNotFound;
+    case NetworkRequestError::kAuthenticationError:
+      return CryptAuthEnrollmentResult::ResultCode::
+          kNetworkRequestErrorAuthenticationError;
+    case NetworkRequestError::kBadRequest:
+      return CryptAuthEnrollmentResult::ResultCode::
+          kNetworkRequestErrorBadRequest;
+    case NetworkRequestError::kResponseMalformed:
+      return CryptAuthEnrollmentResult::ResultCode::
+          kNetworkRequestErrorResponseMalformed;
+    case NetworkRequestError::kInternalServerError:
+      return CryptAuthEnrollmentResult::ResultCode::
+          kNetworkRequestErrorInternalServerError;
+    case NetworkRequestError::kUnknown:
+      return CryptAuthEnrollmentResult::ResultCode::kNetworkRequestErrorUnknown;
+  }
+}
+
+CryptAuthEnrollmentResult::CryptAuthEnrollmentResult(
+    ResultCode result_code,
+    const base::Optional<cryptauthv2::ClientDirective>& client_directive)
+    : result_code_(result_code), client_directive_(client_directive) {}
+
+CryptAuthEnrollmentResult::~CryptAuthEnrollmentResult() = default;
+
+bool CryptAuthEnrollmentResult::IsSuccess() const {
+  return result_code_ == ResultCode::kSuccessNoSyncRequired ||
+         result_code_ == ResultCode::kSuccessNewKeysEnrolled ||
+         result_code_ == ResultCode::kSuccessNoNewKeysNeeded;
+}
+
+}  // namespace device_sync
+
+}  // namespace chromeos
diff --git a/chromeos/services/device_sync/cryptauth_enrollment_result.h b/chromeos/services/device_sync/cryptauth_enrollment_result.h
new file mode 100644
index 0000000..2f23e87f
--- /dev/null
+++ b/chromeos/services/device_sync/cryptauth_enrollment_result.h
@@ -0,0 +1,82 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_ENROLLMENT_RESULT_H_
+#define CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_ENROLLMENT_RESULT_H_
+
+#include "base/optional.h"
+#include "chromeos/services/device_sync/network_request_error.h"
+#include "chromeos/services/device_sync/proto/cryptauth_directive.pb.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+// Information about the result of a CryptAuth v2 Enrollment attempt.
+class CryptAuthEnrollmentResult {
+ public:
+  // Enum class to denote the result of a CryptAuth v2 Enrollment attempt
+  enum class ResultCode {
+    // No SyncKeysRequest was made to CryptAuth because it was not necessary.
+    kSuccessNoSyncRequired,
+    // Successfully synced but no new keys were requested by CryptAuth, so no
+    // EnrollKeysRequest was made.
+    kSuccessNoNewKeysNeeded,
+    // Successfully synced and enrolled new key(s) with CryptAuth.
+    kSuccessNewKeysEnrolled,
+    // Request could not be completed because the device is offline or has
+    // issues sending the HTTP request.
+    kNetworkRequestErrorOffline,
+    // Server endpoint could not be found.
+    kNetworkRequestErrorEndpointNotFound,
+    // Authentication error contacting back-end.
+    kNetworkRequestErrorAuthenticationError,
+    // Network request was invalid.
+    kNetworkRequestErrorBadRequest,
+    // The server responded, but the response was not formatted correctly.
+    kNetworkRequestErrorResponseMalformed,
+    // Internal server error.
+    kNetworkRequestErrorInternalServerError,
+    // Unknown network request error.
+    kNetworkRequestErrorUnknown,
+    // The CryptAuth server indicated via SyncKeysResponse::server_status that
+    // it was overloaded and did not process the SyncKeysRequest.
+    kErrorCryptAuthServerOverloaded,
+    // An error occurred while performing a cryptographic operation, such as
+    // generating key proofs.
+    kErrorCryptographicOperationFailed,
+    // The size of SyncKeysResponse::sync_single_key_responses does not agree
+    // with the number of key bundle names.
+    kErrorSyncKeysResponseIncorrectNumberOfKeys,
+    // Failed to generate the new keys requested by CryptAuth or the ephemeral
+    // keys needed for intermediate cryptographic operations.
+    kErrorKeyCreationFailed
+  };
+
+  static ResultCode NetworkRequestErrorToResultCode(NetworkRequestError error);
+
+  CryptAuthEnrollmentResult(
+      ResultCode result_code,
+      const base::Optional<cryptauthv2::ClientDirective>& client_directive);
+
+  ~CryptAuthEnrollmentResult();
+
+  ResultCode result_code() const { return result_code_; }
+
+  const base::Optional<cryptauthv2::ClientDirective>& client_directive() const {
+    return client_directive_;
+  };
+
+  bool IsSuccess() const;
+
+ private:
+  ResultCode result_code_;
+  base::Optional<cryptauthv2::ClientDirective> client_directive_;
+};
+
+}  // namespace device_sync
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_ENROLLMENT_RESULT_H_
diff --git a/components/BUILD.gn b/components/BUILD.gn
index f9e89774..6d5a22e 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -128,7 +128,6 @@
     "//components/omnibox/browser:unit_tests",
     "//components/open_from_clipboard:unit_tests",
     "//components/os_crypt:unit_tests",
-    "//components/page_image_annotation/core:unit_tests",
     "//components/password_manager/core/browser:unit_tests",
     "//components/password_manager/core/common:unit_tests",
     "//components/payments/core:unit_tests",
@@ -239,6 +238,7 @@
       "//components/network_hints/renderer:unit_tests",
       "//components/offline_pages:unit_tests",
       "//components/optimization_guide:unit_tests",
+      "//components/page_image_annotation/core:unit_tests",
       "//components/password_manager/content/browser:unit_tests",
       "//components/payments/content:unit_tests",
       "//components/payments/content/utility:unit_tests",
diff --git a/components/cronet/native/cronet.idl b/components/cronet/native/cronet.idl
index 0b2d42a6..4470fe1 100644
--- a/components/cronet/native/cronet.idl
+++ b/components/cronet/native/cronet.idl
@@ -1181,7 +1181,7 @@
   /**
    * Number of milliseconds since the UNIX epoch.
    */
-  int64 value;
+  int64 value = 0;
 };
 
 /*
diff --git a/components/cronet/native/generated/cronet.idl_impl_struct.h b/components/cronet/native/generated/cronet.idl_impl_struct.h
index 42e3a2ec34..09264b0 100644
--- a/components/cronet/native/generated/cronet.idl_impl_struct.h
+++ b/components/cronet/native/generated/cronet.idl_impl_struct.h
@@ -164,7 +164,7 @@
   explicit Cronet_DateTime(Cronet_DateTime&& from);
   ~Cronet_DateTime();
 
-  int64_t value;
+  int64_t value = 0;
 
  private:
   DISALLOW_ASSIGN(Cronet_DateTime);
diff --git a/components/cronet/native/generated/cronet.idl_impl_struct_unittest.cc b/components/cronet/native/generated/cronet.idl_impl_struct_unittest.cc
index f7e0270..e2d6f1a 100644
--- a/components/cronet/native/generated/cronet.idl_impl_struct_unittest.cc
+++ b/components/cronet/native/generated/cronet.idl_impl_struct_unittest.cc
@@ -261,14 +261,8 @@
   Cronet_UrlRequestParams_Destroy(second);
 }
 
-// Fails under MSAN: crbug.com/922842
-#if defined(MEMORY_SANITIZER)
-#define MAYBE_TestCronet_DateTime DISABLED_TestCronet_DateTime
-#else
-#define MAYBE_TestCronet_DateTime TestCronet_DateTime
-#endif
 // Test Struct Cronet_DateTime setters and getters.
-TEST_F(CronetStructTest, MAYBE_TestCronet_DateTime) {
+TEST_F(CronetStructTest, TestCronet_DateTime) {
   Cronet_DateTimePtr first = Cronet_DateTime_Create();
   Cronet_DateTimePtr second = Cronet_DateTime_Create();
 
diff --git a/components/leveldb_proto/internal/shared_proto_database.cc b/components/leveldb_proto/internal/shared_proto_database.cc
index c8c1a2d7..62211004 100644
--- a/components/leveldb_proto/internal/shared_proto_database.cc
+++ b/components/leveldb_proto/internal/shared_proto_database.cc
@@ -58,9 +58,8 @@
       db_(std::make_unique<LevelDB>(client_db_id.c_str())),
       db_wrapper_(std::make_unique<ProtoLevelDBWrapper>(task_runner_)),
       metadata_db_(std::make_unique<LevelDB>(kMetadataDatabaseName)),
-      metadata_db_wrapper_(std::make_unique<ProtoLevelDBWrapper>(task_runner_)),
-      weak_factory_(
-          std::make_unique<base::WeakPtrFactory<SharedProtoDatabase>>(this)) {
+      metadata_db_wrapper_(
+          std::make_unique<ProtoLevelDBWrapper>(task_runner_)) {
   DETACH_FROM_SEQUENCE(on_task_runner_);
 }
 
@@ -73,10 +72,9 @@
     Callbacks::InitStatusCallback callback) {
   DCHECK(base::SequencedTaskRunnerHandle::IsSet());
   task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&SharedProtoDatabase::RunInitCallback,
-                     weak_factory_->GetWeakPtr(), std::move(callback),
-                     base::SequencedTaskRunnerHandle::Get()));
+      FROM_HERE, base::BindOnce(&SharedProtoDatabase::RunInitCallback, this,
+                                std::move(callback),
+                                base::SequencedTaskRunnerHandle::Get()));
 }
 
 void SharedProtoDatabase::RunInitCallback(
@@ -93,9 +91,8 @@
   if (base::SequencedTaskRunnerHandle::Get() != task_runner_) {
     task_runner_->PostTask(
         FROM_HERE,
-        base::BindOnce(&SharedProtoDatabase::UpdateClientMetadataAsync,
-                       weak_factory_->GetWeakPtr(), client_db_id,
-                       migration_status, std::move(callback)));
+        base::BindOnce(&SharedProtoDatabase::UpdateClientMetadataAsync, this,
+                       client_db_id, migration_status, std::move(callback)));
     return;
   }
   auto update_entries = std::make_unique<
@@ -121,9 +118,9 @@
   // on the calling sequence.
   metadata_db_wrapper_->GetEntry<SharedDBMetadataProto>(
       std::string(client_db_id),
-      base::BindOnce(&SharedProtoDatabase::OnGetClientMetadata,
-                     weak_factory_->GetWeakPtr(), client_db_id,
-                     std::move(callback), std::move(callback_task_runner)));
+      base::BindOnce(&SharedProtoDatabase::OnGetClientMetadata, this,
+                     client_db_id, std::move(callback),
+                     std::move(callback_task_runner)));
 }
 
 // As mentioned above, |current_task_runner| is the appropriate calling sequence
@@ -251,9 +248,8 @@
   metadata_db_wrapper_->InitWithDatabase(
       metadata_db_.get(), metadata_path, CreateSimpleOptions(),
       true /* destroy_on_corruption */,
-      base::BindOnce(&SharedProtoDatabase::OnMetadataInitComplete,
-                     weak_factory_->GetWeakPtr(), create_shared_db_if_missing,
-                     attempt, corruption));
+      base::BindOnce(&SharedProtoDatabase::OnMetadataInitComplete, this,
+                     create_shared_db_if_missing, attempt, corruption));
 }
 
 void SharedProtoDatabase::OnMetadataInitComplete(
@@ -282,9 +278,8 @@
   // to treat the shared database as corrupt, we can't know for sure anymore.
   metadata_db_wrapper_->GetEntry<SharedDBMetadataProto>(
       std::string(kGlobalMetadataKey),
-      base::BindOnce(&SharedProtoDatabase::OnGetGlobalMetadata,
-                     weak_factory_->GetWeakPtr(), create_shared_db_if_missing,
-                     corruption));
+      base::BindOnce(&SharedProtoDatabase::OnGetGlobalMetadata, this,
+                     create_shared_db_if_missing, corruption));
 }
 
 void SharedProtoDatabase::OnGetGlobalMetadata(
@@ -306,8 +301,8 @@
   metadata_->set_corruptions(corruption ? 1U : 0U);
   metadata_->clear_migration_status();
   CommitUpdatedGlobalMetadata(
-      base::BindOnce(&SharedProtoDatabase::OnFinishCorruptionCountWrite,
-                     weak_factory_->GetWeakPtr(), create_shared_db_if_missing));
+      base::BindOnce(&SharedProtoDatabase::OnFinishCorruptionCountWrite, this,
+                     create_shared_db_if_missing));
 }
 
 void SharedProtoDatabase::OnFinishCorruptionCountWrite(
@@ -337,8 +332,7 @@
   // the InitState will be final after Init is called.
   db_wrapper_->InitWithDatabase(
       db_.get(), db_dir_, options, false /* destroy_on_corruption */,
-      base::BindOnce(&SharedProtoDatabase::OnDatabaseInit,
-                     weak_factory_->GetWeakPtr()));
+      base::BindOnce(&SharedProtoDatabase::OnDatabaseInit, this));
 }
 
 void SharedProtoDatabase::OnDatabaseInit(Enums::InitStatus status) {
@@ -354,9 +348,8 @@
     // serious has gone wrong with the metadata database.
     metadata_->set_corruptions(metadata_->corruptions() + 1);
 
-    CommitUpdatedGlobalMetadata(
-        base::BindOnce(&SharedProtoDatabase::OnUpdateCorruptionCountAtInit,
-                       weak_factory_->GetWeakPtr()));
+    CommitUpdatedGlobalMetadata(base::BindOnce(
+        &SharedProtoDatabase::OnUpdateCorruptionCountAtInit, this));
     return;
   }
 
@@ -410,7 +403,6 @@
 SharedProtoDatabase::~SharedProtoDatabase() {
   task_runner_->DeleteSoon(FROM_HERE, std::move(db_));
   task_runner_->DeleteSoon(FROM_HERE, std::move(metadata_db_));
-  task_runner_->DeleteSoon(FROM_HERE, std::move(weak_factory_));
 }
 
 LevelDB* SharedProtoDatabase::GetLevelDBForTesting() const {
diff --git a/components/leveldb_proto/internal/shared_proto_database.h b/components/leveldb_proto/internal/shared_proto_database.h
index 074d78a..98107f9 100644
--- a/components/leveldb_proto/internal/shared_proto_database.h
+++ b/components/leveldb_proto/internal/shared_proto_database.h
@@ -179,8 +179,6 @@
 
   std::queue<std::unique_ptr<InitRequest>> outstanding_init_requests_;
 
-  std::unique_ptr<base::WeakPtrFactory<SharedProtoDatabase>> weak_factory_;
-
   DISALLOW_COPY_AND_ASSIGN(SharedProtoDatabase);
 };
 
@@ -218,8 +216,8 @@
   SharedProtoDatabaseClient<T>* client_ptr = client.get();
   task_runner_->PostTask(
       FROM_HERE,
-      base::BindOnce(&SharedProtoDatabase::Init, weak_factory_->GetWeakPtr(),
-                     create_if_missing, client_ptr->client_db_id(),
+      base::BindOnce(&SharedProtoDatabase::Init, this, create_if_missing,
+                     client_ptr->client_db_id(),
                      base::BindOnce(&GetClientInitCallback<T>,
                                     std::move(callback), std::move(client)),
                      std::move(current_task_runner)));
@@ -238,9 +236,9 @@
   auto client = GetClientInternal<T>(client_namespace, type_prefix);
   task_runner_->PostTask(
       FROM_HERE,
-      base::BindOnce(&SharedProtoDatabase::Init, weak_factory_->GetWeakPtr(),
-                     create_if_missing, client->client_db_id(),
-                     std::move(callback), std::move(current_task_runner)));
+      base::BindOnce(&SharedProtoDatabase::Init, this, create_if_missing,
+                     client->client_db_id(), std::move(callback),
+                     std::move(current_task_runner)));
   return client;
 }
 
diff --git a/components/leveldb_proto/internal/shared_proto_database_client_unittest.cc b/components/leveldb_proto/internal/shared_proto_database_client_unittest.cc
index 4fabc5a..28746d82 100644
--- a/components/leveldb_proto/internal/shared_proto_database_client_unittest.cc
+++ b/components/leveldb_proto/internal/shared_proto_database_client_unittest.cc
@@ -25,11 +25,6 @@
 const char* kDefaultNamespace2 = "cfd";
 const char* kDefaultTypePrefix = "tp";
 
-void DeleteSoon(scoped_refptr<SharedProtoDatabase> db,
-                std::unique_ptr<base::ScopedTempDir>) {
-  db.reset();
-}
-
 }  // namespace
 
 class SharedProtoDatabaseClientTest : public testing::Test {
@@ -42,11 +37,8 @@
   }
 
   void TearDown() override {
-    // TODO(ssid): SharedProtoDatabase should use scoped_refptr and this
-    // shouldn't be required.
-    db_->database_task_runner_for_testing()->PostTask(
-        FROM_HERE,
-        base::BindOnce(&DeleteSoon, std::move(db_), std::move(temp_dir_)));
+    db_->database_task_runner_for_testing()->DeleteSoon(FROM_HERE,
+                                                        std::move(temp_dir_));
   }
 
  protected:
diff --git a/components/leveldb_proto/internal/shared_proto_database_unittest.cc b/components/leveldb_proto/internal/shared_proto_database_unittest.cc
index bfdf5c39..d2d2a233 100644
--- a/components/leveldb_proto/internal/shared_proto_database_unittest.cc
+++ b/components/leveldb_proto/internal/shared_proto_database_unittest.cc
@@ -56,8 +56,7 @@
               SharedClientInitCallback callback) {
     db_->task_runner_->PostTask(
         FROM_HERE,
-        base::BindOnce(&SharedProtoDatabase::Init,
-                       db_->weak_factory_->GetWeakPtr(), create_if_missing,
+        base::BindOnce(&SharedProtoDatabase::Init, db_, create_if_missing,
                        client_name, std::move(callback),
                        scoped_task_environment_.GetMainThreadTaskRunner()));
   }
diff --git a/components/omnibox/browser/location_bar_model_impl.cc b/components/omnibox/browser/location_bar_model_impl.cc
index 7a1b5b2..377bd973 100644
--- a/components/omnibox/browser/location_bar_model_impl.cc
+++ b/components/omnibox/browser/location_bar_model_impl.cc
@@ -50,11 +50,6 @@
 
 #if defined(OS_IOS)
   format_types |= url_formatter::kFormatUrlTrimAfterHost;
-#else
-  if (base::FeatureList::IsEnabled(
-          omnibox::kHideSteadyStateUrlPathQueryAndRef)) {
-    format_types |= url_formatter::kFormatUrlTrimAfterHost;
-  }
 #endif
 
   if (OmniboxFieldTrial::IsHideSteadyStateUrlSchemeEnabled())
diff --git a/components/omnibox/browser/omnibox_pedal.h b/components/omnibox/browser/omnibox_pedal.h
index 578481e..9be53f5 100644
--- a/components/omnibox/browser/omnibox_pedal.h
+++ b/components/omnibox/browser/omnibox_pedal.h
@@ -143,6 +143,8 @@
  protected:
   FRIEND_TEST_ALL_PREFIXES(OmniboxPedalTest, SynonymGroupErasesFirstMatchOnly);
   FRIEND_TEST_ALL_PREFIXES(OmniboxPedalTest, SynonymGroupsDriveConceptMatches);
+  FRIEND_TEST_ALL_PREFIXES(OmniboxPedalImplementationsTest,
+                           UnorderedSynonymExpressionsAreConceptMatches);
 
   // If a sufficient set of triggering synonym groups are present in match_text
   // then it's a concept match and this returns true.  If a required group is
diff --git a/components/omnibox/browser/omnibox_pedal_implementations_unittest.cc b/components/omnibox/browser/omnibox_pedal_implementations_unittest.cc
index 59ef623..1127584a 100644
--- a/components/omnibox/browser/omnibox_pedal_implementations_unittest.cc
+++ b/components/omnibox/browser/omnibox_pedal_implementations_unittest.cc
@@ -54,3 +54,1902 @@
   const GURL& url = omnibox_edit_controller_->destination_url();
   EXPECT_EQ(url, GURL("chrome://settings/clearBrowserData"));
 }
+
+// Exhaustive test of unordered synonym groups for concept matches; this is
+// useful in detecting possible clashes between cross-group synonym strings.
+// By ensuring that each set matches for exactly one Pedal, we can also
+// prevent clashes between concepts for different Pedals.
+TEST_F(OmniboxPedalImplementationsTest,
+       UnorderedSynonymExpressionsAreConceptMatches) {
+  const std::vector<std::vector<const char*>> literal_concept_expressions = {
+      // Note: The lists below are auto-generated from raw synonym group data.
+
+      // Clear Browsing Data
+      {
+          "browser cache clear",
+          "browser cache delete",
+          "browser cache erase",
+          "browser cache remove",
+          "browser cache wipe",
+          "browser clear cache",
+          "browser clear data",
+          "browser clear history",
+          "browser data clear",
+          "browser data delete",
+          "browser data erase",
+          "browser data remove",
+          "browser data wipe",
+          "browser delete cache",
+          "browser delete data",
+          "browser delete history",
+          "browser erase cache",
+          "browser erase data",
+          "browser erase history",
+          "browser history clear",
+          "browser history delete",
+          "browser history erase",
+          "browser history remove",
+          "browser history wipe",
+          "browser remove cache",
+          "browser remove data",
+          "browser remove history",
+          "browser wipe cache",
+          "browser wipe data",
+          "browser wipe history",
+          "cache browser clear",
+          "cache browser delete",
+          "cache browser erase",
+          "cache browser remove",
+          "cache browser wipe",
+          "cache chrome clear",
+          "cache chrome delete",
+          "cache chrome erase",
+          "cache chrome remove",
+          "cache chrome wipe",
+          "cache clear",
+          "cache clear browser",
+          "cache clear chrome",
+          "cache clear google chrome",
+          "cache delete",
+          "cache delete browser",
+          "cache delete chrome",
+          "cache delete google chrome",
+          "cache erase",
+          "cache erase browser",
+          "cache erase chrome",
+          "cache erase google chrome",
+          "cache google chrome clear",
+          "cache google chrome delete",
+          "cache google chrome erase",
+          "cache google chrome remove",
+          "cache google chrome wipe",
+          "cache remove",
+          "cache remove browser",
+          "cache remove chrome",
+          "cache remove google chrome",
+          "cache wipe",
+          "cache wipe browser",
+          "cache wipe chrome",
+          "cache wipe google chrome",
+          "chrome cache clear",
+          "chrome cache delete",
+          "chrome cache erase",
+          "chrome cache remove",
+          "chrome cache wipe",
+          "chrome clear cache",
+          "chrome clear data",
+          "chrome clear history",
+          "chrome data clear",
+          "chrome data delete",
+          "chrome data erase",
+          "chrome data remove",
+          "chrome data wipe",
+          "chrome delete cache",
+          "chrome delete data",
+          "chrome delete history",
+          "chrome erase cache",
+          "chrome erase data",
+          "chrome erase history",
+          "chrome history clear",
+          "chrome history delete",
+          "chrome history erase",
+          "chrome history remove",
+          "chrome history wipe",
+          "chrome remove cache",
+          "chrome remove data",
+          "chrome remove history",
+          "chrome wipe cache",
+          "chrome wipe data",
+          "chrome wipe history",
+          "clear browser cache",
+          "clear browser data",
+          "clear browser history",
+          "clear cache",
+          "clear cache browser",
+          "clear cache chrome",
+          "clear cache google chrome",
+          "clear chrome cache",
+          "clear chrome data",
+          "clear chrome history",
+          "clear data",
+          "clear data browser",
+          "clear data chrome",
+          "clear data google chrome",
+          "clear google chrome cache",
+          "clear google chrome data",
+          "clear google chrome history",
+          "clear history",
+          "clear history browser",
+          "clear history chrome",
+          "clear history google chrome",
+          "data browser clear",
+          "data browser delete",
+          "data browser erase",
+          "data browser remove",
+          "data browser wipe",
+          "data chrome clear",
+          "data chrome delete",
+          "data chrome erase",
+          "data chrome remove",
+          "data chrome wipe",
+          "data clear",
+          "data clear browser",
+          "data clear chrome",
+          "data clear google chrome",
+          "data delete",
+          "data delete browser",
+          "data delete chrome",
+          "data delete google chrome",
+          "data erase",
+          "data erase browser",
+          "data erase chrome",
+          "data erase google chrome",
+          "data google chrome clear",
+          "data google chrome delete",
+          "data google chrome erase",
+          "data google chrome remove",
+          "data google chrome wipe",
+          "data remove",
+          "data remove browser",
+          "data remove chrome",
+          "data remove google chrome",
+          "data wipe",
+          "data wipe browser",
+          "data wipe chrome",
+          "data wipe google chrome",
+          "delete browser cache",
+          "delete browser data",
+          "delete browser history",
+          "delete cache",
+          "delete cache browser",
+          "delete cache chrome",
+          "delete cache google chrome",
+          "delete chrome cache",
+          "delete chrome data",
+          "delete chrome history",
+          "delete data",
+          "delete data browser",
+          "delete data chrome",
+          "delete data google chrome",
+          "delete google chrome cache",
+          "delete google chrome data",
+          "delete google chrome history",
+          "delete history",
+          "delete history browser",
+          "delete history chrome",
+          "delete history google chrome",
+          "erase browser cache",
+          "erase browser data",
+          "erase browser history",
+          "erase cache",
+          "erase cache browser",
+          "erase cache chrome",
+          "erase cache google chrome",
+          "erase chrome cache",
+          "erase chrome data",
+          "erase chrome history",
+          "erase data",
+          "erase data browser",
+          "erase data chrome",
+          "erase data google chrome",
+          "erase google chrome cache",
+          "erase google chrome data",
+          "erase google chrome history",
+          "erase history",
+          "erase history browser",
+          "erase history chrome",
+          "erase history google chrome",
+          "google chrome cache clear",
+          "google chrome cache delete",
+          "google chrome cache erase",
+          "google chrome cache remove",
+          "google chrome cache wipe",
+          "google chrome clear cache",
+          "google chrome clear data",
+          "google chrome clear history",
+          "google chrome data clear",
+          "google chrome data delete",
+          "google chrome data erase",
+          "google chrome data remove",
+          "google chrome data wipe",
+          "google chrome delete cache",
+          "google chrome delete data",
+          "google chrome delete history",
+          "google chrome erase cache",
+          "google chrome erase data",
+          "google chrome erase history",
+          "google chrome history clear",
+          "google chrome history delete",
+          "google chrome history erase",
+          "google chrome history remove",
+          "google chrome history wipe",
+          "google chrome remove cache",
+          "google chrome remove data",
+          "google chrome remove history",
+          "google chrome wipe cache",
+          "google chrome wipe data",
+          "google chrome wipe history",
+          "history browser clear",
+          "history browser delete",
+          "history browser erase",
+          "history browser remove",
+          "history browser wipe",
+          "history chrome clear",
+          "history chrome delete",
+          "history chrome erase",
+          "history chrome remove",
+          "history chrome wipe",
+          "history clear",
+          "history clear browser",
+          "history clear chrome",
+          "history clear google chrome",
+          "history delete",
+          "history delete browser",
+          "history delete chrome",
+          "history delete google chrome",
+          "history erase",
+          "history erase browser",
+          "history erase chrome",
+          "history erase google chrome",
+          "history google chrome clear",
+          "history google chrome delete",
+          "history google chrome erase",
+          "history google chrome remove",
+          "history google chrome wipe",
+          "history remove",
+          "history remove browser",
+          "history remove chrome",
+          "history remove google chrome",
+          "history wipe",
+          "history wipe browser",
+          "history wipe chrome",
+          "history wipe google chrome",
+          "remove browser cache",
+          "remove browser data",
+          "remove browser history",
+          "remove cache",
+          "remove cache browser",
+          "remove cache chrome",
+          "remove cache google chrome",
+          "remove chrome cache",
+          "remove chrome data",
+          "remove chrome history",
+          "remove data",
+          "remove data browser",
+          "remove data chrome",
+          "remove data google chrome",
+          "remove google chrome cache",
+          "remove google chrome data",
+          "remove google chrome history",
+          "remove history",
+          "remove history browser",
+          "remove history chrome",
+          "remove history google chrome",
+          "wipe browser cache",
+          "wipe browser data",
+          "wipe browser history",
+          "wipe cache",
+          "wipe cache browser",
+          "wipe cache chrome",
+          "wipe cache google chrome",
+          "wipe chrome cache",
+          "wipe chrome data",
+          "wipe chrome history",
+          "wipe data",
+          "wipe data browser",
+          "wipe data chrome",
+          "wipe data google chrome",
+          "wipe google chrome cache",
+          "wipe google chrome data",
+          "wipe google chrome history",
+          "wipe history",
+          "wipe history browser",
+          "wipe history chrome",
+          "wipe history google chrome",
+      },
+
+      // Change Search Engine
+      {
+          "browser change default search engine",
+          "browser change search",
+          "browser change search engine",
+          "browser change standard search engine",
+          "browser choose default search engine",
+          "browser choose search",
+          "browser choose search engine",
+          "browser choose standard search engine",
+          "browser default search engine change",
+          "browser default search engine choose",
+          "browser default search engine select",
+          "browser default search engine switch",
+          "browser search change",
+          "browser search choose",
+          "browser search engine change",
+          "browser search engine choose",
+          "browser search engine select",
+          "browser search engine switch",
+          "browser search select",
+          "browser search switch",
+          "browser select default search engine",
+          "browser select search",
+          "browser select search engine",
+          "browser select standard search engine",
+          "browser standard search engine change",
+          "browser standard search engine choose",
+          "browser standard search engine select",
+          "browser standard search engine switch",
+          "browser switch default search engine",
+          "browser switch search",
+          "browser switch search engine",
+          "browser switch standard search engine",
+          "change browser default search engine",
+          "change browser search",
+          "change browser search engine",
+          "change browser standard search engine",
+          "change chrome default search engine",
+          "change chrome search",
+          "change chrome search engine",
+          "change chrome standard search engine",
+          "change default search engine",
+          "change default search engine browser",
+          "change default search engine chrome",
+          "change default search engine google chrome",
+          "change google chrome default search engine",
+          "change google chrome search",
+          "change google chrome search engine",
+          "change google chrome standard search engine",
+          "change search",
+          "change search browser",
+          "change search chrome",
+          "change search engine",
+          "change search engine browser",
+          "change search engine chrome",
+          "change search engine google chrome",
+          "change search google chrome",
+          "change standard search engine",
+          "change standard search engine browser",
+          "change standard search engine chrome",
+          "change standard search engine google chrome",
+          "choose browser default search engine",
+          "choose browser search",
+          "choose browser search engine",
+          "choose browser standard search engine",
+          "choose chrome default search engine",
+          "choose chrome search",
+          "choose chrome search engine",
+          "choose chrome standard search engine",
+          "choose default search engine",
+          "choose default search engine browser",
+          "choose default search engine chrome",
+          "choose default search engine google chrome",
+          "choose google chrome default search engine",
+          "choose google chrome search",
+          "choose google chrome search engine",
+          "choose google chrome standard search engine",
+          "choose search",
+          "choose search browser",
+          "choose search chrome",
+          "choose search engine",
+          "choose search engine browser",
+          "choose search engine chrome",
+          "choose search engine google chrome",
+          "choose search google chrome",
+          "choose standard search engine",
+          "choose standard search engine browser",
+          "choose standard search engine chrome",
+          "choose standard search engine google chrome",
+          "chrome change default search engine",
+          "chrome change search",
+          "chrome change search engine",
+          "chrome change standard search engine",
+          "chrome choose default search engine",
+          "chrome choose search",
+          "chrome choose search engine",
+          "chrome choose standard search engine",
+          "chrome default search engine change",
+          "chrome default search engine choose",
+          "chrome default search engine select",
+          "chrome default search engine switch",
+          "chrome search change",
+          "chrome search choose",
+          "chrome search engine change",
+          "chrome search engine choose",
+          "chrome search engine select",
+          "chrome search engine switch",
+          "chrome search select",
+          "chrome search switch",
+          "chrome select default search engine",
+          "chrome select search",
+          "chrome select search engine",
+          "chrome select standard search engine",
+          "chrome standard search engine change",
+          "chrome standard search engine choose",
+          "chrome standard search engine select",
+          "chrome standard search engine switch",
+          "chrome switch default search engine",
+          "chrome switch search",
+          "chrome switch search engine",
+          "chrome switch standard search engine",
+          "default search engine browser change",
+          "default search engine browser choose",
+          "default search engine browser select",
+          "default search engine browser switch",
+          "default search engine change",
+          "default search engine change browser",
+          "default search engine change chrome",
+          "default search engine change google chrome",
+          "default search engine choose",
+          "default search engine choose browser",
+          "default search engine choose chrome",
+          "default search engine choose google chrome",
+          "default search engine chrome change",
+          "default search engine chrome choose",
+          "default search engine chrome select",
+          "default search engine chrome switch",
+          "default search engine google chrome change",
+          "default search engine google chrome choose",
+          "default search engine google chrome select",
+          "default search engine google chrome switch",
+          "default search engine select",
+          "default search engine select browser",
+          "default search engine select chrome",
+          "default search engine select google chrome",
+          "default search engine switch",
+          "default search engine switch browser",
+          "default search engine switch chrome",
+          "default search engine switch google chrome",
+          "google chrome change default search engine",
+          "google chrome change search",
+          "google chrome change search engine",
+          "google chrome change standard search engine",
+          "google chrome choose default search engine",
+          "google chrome choose search",
+          "google chrome choose search engine",
+          "google chrome choose standard search engine",
+          "google chrome default search engine change",
+          "google chrome default search engine choose",
+          "google chrome default search engine select",
+          "google chrome default search engine switch",
+          "google chrome search change",
+          "google chrome search choose",
+          "google chrome search engine change",
+          "google chrome search engine choose",
+          "google chrome search engine select",
+          "google chrome search engine switch",
+          "google chrome search select",
+          "google chrome search switch",
+          "google chrome select default search engine",
+          "google chrome select search",
+          "google chrome select search engine",
+          "google chrome select standard search engine",
+          "google chrome standard search engine change",
+          "google chrome standard search engine choose",
+          "google chrome standard search engine select",
+          "google chrome standard search engine switch",
+          "google chrome switch default search engine",
+          "google chrome switch search",
+          "google chrome switch search engine",
+          "google chrome switch standard search engine",
+          "search browser change",
+          "search browser choose",
+          "search browser select",
+          "search browser switch",
+          "search change",
+          "search change browser",
+          "search change chrome",
+          "search change google chrome",
+          "search choose",
+          "search choose browser",
+          "search choose chrome",
+          "search choose google chrome",
+          "search chrome change",
+          "search chrome choose",
+          "search chrome select",
+          "search chrome switch",
+          "search engine browser change",
+          "search engine browser choose",
+          "search engine browser select",
+          "search engine browser switch",
+          "search engine change",
+          "search engine change browser",
+          "search engine change chrome",
+          "search engine change google chrome",
+          "search engine choose",
+          "search engine choose browser",
+          "search engine choose chrome",
+          "search engine choose google chrome",
+          "search engine chrome change",
+          "search engine chrome choose",
+          "search engine chrome select",
+          "search engine chrome switch",
+          "search engine google chrome change",
+          "search engine google chrome choose",
+          "search engine google chrome select",
+          "search engine google chrome switch",
+          "search engine select",
+          "search engine select browser",
+          "search engine select chrome",
+          "search engine select google chrome",
+          "search engine switch",
+          "search engine switch browser",
+          "search engine switch chrome",
+          "search engine switch google chrome",
+          "search google chrome change",
+          "search google chrome choose",
+          "search google chrome select",
+          "search google chrome switch",
+          "search select",
+          "search select browser",
+          "search select chrome",
+          "search select google chrome",
+          "search switch",
+          "search switch browser",
+          "search switch chrome",
+          "search switch google chrome",
+          "select browser default search engine",
+          "select browser search",
+          "select browser search engine",
+          "select browser standard search engine",
+          "select chrome default search engine",
+          "select chrome search",
+          "select chrome search engine",
+          "select chrome standard search engine",
+          "select default search engine",
+          "select default search engine browser",
+          "select default search engine chrome",
+          "select default search engine google chrome",
+          "select google chrome default search engine",
+          "select google chrome search",
+          "select google chrome search engine",
+          "select google chrome standard search engine",
+          "select search",
+          "select search browser",
+          "select search chrome",
+          "select search engine",
+          "select search engine browser",
+          "select search engine chrome",
+          "select search engine google chrome",
+          "select search google chrome",
+          "select standard search engine",
+          "select standard search engine browser",
+          "select standard search engine chrome",
+          "select standard search engine google chrome",
+          "standard search engine browser change",
+          "standard search engine browser choose",
+          "standard search engine browser select",
+          "standard search engine browser switch",
+          "standard search engine change",
+          "standard search engine change browser",
+          "standard search engine change chrome",
+          "standard search engine change google chrome",
+          "standard search engine choose",
+          "standard search engine choose browser",
+          "standard search engine choose chrome",
+          "standard search engine choose google chrome",
+          "standard search engine chrome change",
+          "standard search engine chrome choose",
+          "standard search engine chrome select",
+          "standard search engine chrome switch",
+          "standard search engine google chrome change",
+          "standard search engine google chrome choose",
+          "standard search engine google chrome select",
+          "standard search engine google chrome switch",
+          "standard search engine select",
+          "standard search engine select browser",
+          "standard search engine select chrome",
+          "standard search engine select google chrome",
+          "standard search engine switch",
+          "standard search engine switch browser",
+          "standard search engine switch chrome",
+          "standard search engine switch google chrome",
+          "switch browser default search engine",
+          "switch browser search",
+          "switch browser search engine",
+          "switch browser standard search engine",
+          "switch chrome default search engine",
+          "switch chrome search",
+          "switch chrome search engine",
+          "switch chrome standard search engine",
+          "switch default search engine",
+          "switch default search engine browser",
+          "switch default search engine chrome",
+          "switch default search engine google chrome",
+          "switch google chrome default search engine",
+          "switch google chrome search",
+          "switch google chrome search engine",
+          "switch google chrome standard search engine",
+          "switch search",
+          "switch search browser",
+          "switch search chrome",
+          "switch search engine",
+          "switch search engine browser",
+          "switch search engine chrome",
+          "switch search engine google chrome",
+          "switch search google chrome",
+          "switch standard search engine",
+          "switch standard search engine browser",
+          "switch standard search engine chrome",
+          "switch standard search engine google chrome",
+      },
+
+      // Manage Passwords
+      {
+          "browser change passwords",
+          "browser manage passwords",
+          "browser manager passwords",
+          "browser passwords change",
+          "browser passwords manage",
+          "browser passwords manager",
+          "browser passwords update",
+          "browser update passwords",
+          "change browser passwords",
+          "change chrome passwords",
+          "change google chrome passwords",
+          "change passwords",
+          "change passwords browser",
+          "change passwords chrome",
+          "change passwords google chrome",
+          "chrome change passwords",
+          "chrome manage passwords",
+          "chrome manager passwords",
+          "chrome passwords change",
+          "chrome passwords manage",
+          "chrome passwords manager",
+          "chrome passwords update",
+          "chrome update passwords",
+          "google chrome change passwords",
+          "google chrome manage passwords",
+          "google chrome manager passwords",
+          "google chrome passwords change",
+          "google chrome passwords manage",
+          "google chrome passwords manager",
+          "google chrome passwords update",
+          "google chrome update passwords",
+          "manage browser passwords",
+          "manage chrome passwords",
+          "manage google chrome passwords",
+          "manage passwords",
+          "manage passwords browser",
+          "manage passwords chrome",
+          "manage passwords google chrome",
+          "manager browser passwords",
+          "manager chrome passwords",
+          "manager google chrome passwords",
+          "manager passwords",
+          "manager passwords browser",
+          "manager passwords chrome",
+          "manager passwords google chrome",
+          "passwords browser change",
+          "passwords browser manage",
+          "passwords browser manager",
+          "passwords browser update",
+          "passwords change",
+          "passwords change browser",
+          "passwords change chrome",
+          "passwords change google chrome",
+          "passwords chrome change",
+          "passwords chrome manage",
+          "passwords chrome manager",
+          "passwords chrome update",
+          "passwords google chrome change",
+          "passwords google chrome manage",
+          "passwords google chrome manager",
+          "passwords google chrome update",
+          "passwords manage",
+          "passwords manage browser",
+          "passwords manage chrome",
+          "passwords manage google chrome",
+          "passwords manager",
+          "passwords manager browser",
+          "passwords manager chrome",
+          "passwords manager google chrome",
+          "passwords update",
+          "passwords update browser",
+          "passwords update chrome",
+          "passwords update google chrome",
+          "update browser passwords",
+          "update chrome passwords",
+          "update google chrome passwords",
+          "update passwords",
+          "update passwords browser",
+          "update passwords chrome",
+          "update passwords google chrome",
+      },
+
+      // Change Home Page
+      {
+          "browser change home page",
+          "browser change homepage",
+          "browser choose home page",
+          "browser choose homepage",
+          "browser home page change",
+          "browser home page choose",
+          "browser home page set",
+          "browser homepage change",
+          "browser homepage choose",
+          "browser homepage set",
+          "browser set home page",
+          "browser set homepage",
+          "change browser home page",
+          "change browser homepage",
+          "change chrome home page",
+          "change chrome homepage",
+          "change google chrome home page",
+          "change google chrome homepage",
+          "change home page",
+          "change home page browser",
+          "change home page chrome",
+          "change home page google chrome",
+          "change homepage",
+          "change homepage browser",
+          "change homepage chrome",
+          "change homepage google chrome",
+          "choose browser home page",
+          "choose browser homepage",
+          "choose chrome home page",
+          "choose chrome homepage",
+          "choose google chrome home page",
+          "choose google chrome homepage",
+          "choose home page",
+          "choose home page browser",
+          "choose home page chrome",
+          "choose home page google chrome",
+          "choose homepage",
+          "choose homepage browser",
+          "choose homepage chrome",
+          "choose homepage google chrome",
+          "chrome change home page",
+          "chrome change homepage",
+          "chrome choose home page",
+          "chrome choose homepage",
+          "chrome home page change",
+          "chrome home page choose",
+          "chrome home page set",
+          "chrome homepage change",
+          "chrome homepage choose",
+          "chrome homepage set",
+          "chrome set home page",
+          "chrome set homepage",
+          "google chrome change home page",
+          "google chrome change homepage",
+          "google chrome choose home page",
+          "google chrome choose homepage",
+          "google chrome home page change",
+          "google chrome home page choose",
+          "google chrome home page set",
+          "google chrome homepage change",
+          "google chrome homepage choose",
+          "google chrome homepage set",
+          "google chrome set home page",
+          "google chrome set homepage",
+          "home page browser change",
+          "home page browser choose",
+          "home page browser set",
+          "home page change",
+          "home page change browser",
+          "home page change chrome",
+          "home page change google chrome",
+          "home page choose",
+          "home page choose browser",
+          "home page choose chrome",
+          "home page choose google chrome",
+          "home page chrome change",
+          "home page chrome choose",
+          "home page chrome set",
+          "home page google chrome change",
+          "home page google chrome choose",
+          "home page google chrome set",
+          "home page set",
+          "home page set browser",
+          "home page set chrome",
+          "home page set google chrome",
+          "homepage browser change",
+          "homepage browser choose",
+          "homepage browser set",
+          "homepage change",
+          "homepage change browser",
+          "homepage change chrome",
+          "homepage change google chrome",
+          "homepage choose",
+          "homepage choose browser",
+          "homepage choose chrome",
+          "homepage choose google chrome",
+          "homepage chrome change",
+          "homepage chrome choose",
+          "homepage chrome set",
+          "homepage google chrome change",
+          "homepage google chrome choose",
+          "homepage google chrome set",
+          "homepage set",
+          "homepage set browser",
+          "homepage set chrome",
+          "homepage set google chrome",
+          "set browser home page",
+          "set browser homepage",
+          "set chrome home page",
+          "set chrome homepage",
+          "set google chrome home page",
+          "set google chrome homepage",
+          "set home page",
+          "set home page browser",
+          "set home page chrome",
+          "set home page google chrome",
+          "set homepage",
+          "set homepage browser",
+          "set homepage chrome",
+          "set homepage google chrome",
+      },
+
+      // Update Credit Card
+      {
+          "browser card info update",
+          "browser cards update",
+          "browser credit card update",
+          "browser update card info",
+          "browser update cards",
+          "browser update credit card",
+          "card info browser update",
+          "card info chrome update",
+          "card info google chrome update",
+          "card info update",
+          "card info update browser",
+          "card info update chrome",
+          "card info update google chrome",
+          "cards browser update",
+          "cards chrome update",
+          "cards google chrome update",
+          "cards update",
+          "cards update browser",
+          "cards update chrome",
+          "cards update google chrome",
+          "chrome card info update",
+          "chrome cards update",
+          "chrome credit card update",
+          "chrome update card info",
+          "chrome update cards",
+          "chrome update credit card",
+          "credit card browser update",
+          "credit card chrome update",
+          "credit card google chrome update",
+          "credit card update",
+          "credit card update browser",
+          "credit card update chrome",
+          "credit card update google chrome",
+          "google chrome card info update",
+          "google chrome cards update",
+          "google chrome credit card update",
+          "google chrome update card info",
+          "google chrome update cards",
+          "google chrome update credit card",
+          "update browser card info",
+          "update browser cards",
+          "update browser credit card",
+          "update card info",
+          "update card info browser",
+          "update card info chrome",
+          "update card info google chrome",
+          "update cards",
+          "update cards browser",
+          "update cards chrome",
+          "update cards google chrome",
+          "update chrome card info",
+          "update chrome cards",
+          "update chrome credit card",
+          "update credit card",
+          "update credit card browser",
+          "update credit card chrome",
+          "update credit card google chrome",
+          "update google chrome card info",
+          "update google chrome cards",
+          "update google chrome credit card",
+      },
+
+      // Launch Incognito
+      {
+          "browser dark mode enter",
+          "browser dark mode launch",
+          "browser dark mode open",
+          "browser dark mode start",
+          "browser dark tab enter",
+          "browser dark tab launch",
+          "browser dark tab open",
+          "browser dark tab start",
+          "browser dark window enter",
+          "browser dark window launch",
+          "browser dark window open",
+          "browser dark window start",
+          "browser enter dark mode",
+          "browser enter dark tab",
+          "browser enter dark window",
+          "browser enter incognito",
+          "browser enter incognito mode",
+          "browser enter incognito tab",
+          "browser enter incognito window",
+          "browser enter private mode",
+          "browser enter private tab",
+          "browser enter private window",
+          "browser incognito enter",
+          "browser incognito launch",
+          "browser incognito mode enter",
+          "browser incognito mode launch",
+          "browser incognito mode open",
+          "browser incognito mode start",
+          "browser incognito open",
+          "browser incognito start",
+          "browser incognito tab enter",
+          "browser incognito tab launch",
+          "browser incognito tab open",
+          "browser incognito tab start",
+          "browser incognito window enter",
+          "browser incognito window launch",
+          "browser incognito window open",
+          "browser incognito window start",
+          "browser launch dark mode",
+          "browser launch dark tab",
+          "browser launch dark window",
+          "browser launch incognito",
+          "browser launch incognito mode",
+          "browser launch incognito tab",
+          "browser launch incognito window",
+          "browser launch private mode",
+          "browser launch private tab",
+          "browser launch private window",
+          "browser open dark mode",
+          "browser open dark tab",
+          "browser open dark window",
+          "browser open incognito",
+          "browser open incognito mode",
+          "browser open incognito tab",
+          "browser open incognito window",
+          "browser open private mode",
+          "browser open private tab",
+          "browser open private window",
+          "browser private mode enter",
+          "browser private mode launch",
+          "browser private mode open",
+          "browser private mode start",
+          "browser private tab enter",
+          "browser private tab launch",
+          "browser private tab open",
+          "browser private tab start",
+          "browser private window enter",
+          "browser private window launch",
+          "browser private window open",
+          "browser private window start",
+          "browser start dark mode",
+          "browser start dark tab",
+          "browser start dark window",
+          "browser start incognito",
+          "browser start incognito mode",
+          "browser start incognito tab",
+          "browser start incognito window",
+          "browser start private mode",
+          "browser start private tab",
+          "browser start private window",
+          "chrome dark mode enter",
+          "chrome dark mode launch",
+          "chrome dark mode open",
+          "chrome dark mode start",
+          "chrome dark tab enter",
+          "chrome dark tab launch",
+          "chrome dark tab open",
+          "chrome dark tab start",
+          "chrome dark window enter",
+          "chrome dark window launch",
+          "chrome dark window open",
+          "chrome dark window start",
+          "chrome enter dark mode",
+          "chrome enter dark tab",
+          "chrome enter dark window",
+          "chrome enter incognito",
+          "chrome enter incognito mode",
+          "chrome enter incognito tab",
+          "chrome enter incognito window",
+          "chrome enter private mode",
+          "chrome enter private tab",
+          "chrome enter private window",
+          "chrome incognito enter",
+          "chrome incognito launch",
+          "chrome incognito mode enter",
+          "chrome incognito mode launch",
+          "chrome incognito mode open",
+          "chrome incognito mode start",
+          "chrome incognito open",
+          "chrome incognito start",
+          "chrome incognito tab enter",
+          "chrome incognito tab launch",
+          "chrome incognito tab open",
+          "chrome incognito tab start",
+          "chrome incognito window enter",
+          "chrome incognito window launch",
+          "chrome incognito window open",
+          "chrome incognito window start",
+          "chrome launch dark mode",
+          "chrome launch dark tab",
+          "chrome launch dark window",
+          "chrome launch incognito",
+          "chrome launch incognito mode",
+          "chrome launch incognito tab",
+          "chrome launch incognito window",
+          "chrome launch private mode",
+          "chrome launch private tab",
+          "chrome launch private window",
+          "chrome open dark mode",
+          "chrome open dark tab",
+          "chrome open dark window",
+          "chrome open incognito",
+          "chrome open incognito mode",
+          "chrome open incognito tab",
+          "chrome open incognito window",
+          "chrome open private mode",
+          "chrome open private tab",
+          "chrome open private window",
+          "chrome private mode enter",
+          "chrome private mode launch",
+          "chrome private mode open",
+          "chrome private mode start",
+          "chrome private tab enter",
+          "chrome private tab launch",
+          "chrome private tab open",
+          "chrome private tab start",
+          "chrome private window enter",
+          "chrome private window launch",
+          "chrome private window open",
+          "chrome private window start",
+          "chrome start dark mode",
+          "chrome start dark tab",
+          "chrome start dark window",
+          "chrome start incognito",
+          "chrome start incognito mode",
+          "chrome start incognito tab",
+          "chrome start incognito window",
+          "chrome start private mode",
+          "chrome start private tab",
+          "chrome start private window",
+          "dark mode browser enter",
+          "dark mode browser launch",
+          "dark mode browser open",
+          "dark mode browser start",
+          "dark mode chrome enter",
+          "dark mode chrome launch",
+          "dark mode chrome open",
+          "dark mode chrome start",
+          "dark mode enter",
+          "dark mode enter browser",
+          "dark mode enter chrome",
+          "dark mode enter google chrome",
+          "dark mode google chrome enter",
+          "dark mode google chrome launch",
+          "dark mode google chrome open",
+          "dark mode google chrome start",
+          "dark mode launch",
+          "dark mode launch browser",
+          "dark mode launch chrome",
+          "dark mode launch google chrome",
+          "dark mode open",
+          "dark mode open browser",
+          "dark mode open chrome",
+          "dark mode open google chrome",
+          "dark mode start",
+          "dark mode start browser",
+          "dark mode start chrome",
+          "dark mode start google chrome",
+          "dark tab browser enter",
+          "dark tab browser launch",
+          "dark tab browser open",
+          "dark tab browser start",
+          "dark tab chrome enter",
+          "dark tab chrome launch",
+          "dark tab chrome open",
+          "dark tab chrome start",
+          "dark tab enter",
+          "dark tab enter browser",
+          "dark tab enter chrome",
+          "dark tab enter google chrome",
+          "dark tab google chrome enter",
+          "dark tab google chrome launch",
+          "dark tab google chrome open",
+          "dark tab google chrome start",
+          "dark tab launch",
+          "dark tab launch browser",
+          "dark tab launch chrome",
+          "dark tab launch google chrome",
+          "dark tab open",
+          "dark tab open browser",
+          "dark tab open chrome",
+          "dark tab open google chrome",
+          "dark tab start",
+          "dark tab start browser",
+          "dark tab start chrome",
+          "dark tab start google chrome",
+          "dark window browser enter",
+          "dark window browser launch",
+          "dark window browser open",
+          "dark window browser start",
+          "dark window chrome enter",
+          "dark window chrome launch",
+          "dark window chrome open",
+          "dark window chrome start",
+          "dark window enter",
+          "dark window enter browser",
+          "dark window enter chrome",
+          "dark window enter google chrome",
+          "dark window google chrome enter",
+          "dark window google chrome launch",
+          "dark window google chrome open",
+          "dark window google chrome start",
+          "dark window launch",
+          "dark window launch browser",
+          "dark window launch chrome",
+          "dark window launch google chrome",
+          "dark window open",
+          "dark window open browser",
+          "dark window open chrome",
+          "dark window open google chrome",
+          "dark window start",
+          "dark window start browser",
+          "dark window start chrome",
+          "dark window start google chrome",
+          "enter browser dark mode",
+          "enter browser dark tab",
+          "enter browser dark window",
+          "enter browser incognito",
+          "enter browser incognito mode",
+          "enter browser incognito tab",
+          "enter browser incognito window",
+          "enter browser private mode",
+          "enter browser private tab",
+          "enter browser private window",
+          "enter chrome dark mode",
+          "enter chrome dark tab",
+          "enter chrome dark window",
+          "enter chrome incognito",
+          "enter chrome incognito mode",
+          "enter chrome incognito tab",
+          "enter chrome incognito window",
+          "enter chrome private mode",
+          "enter chrome private tab",
+          "enter chrome private window",
+          "enter dark mode",
+          "enter dark mode browser",
+          "enter dark mode chrome",
+          "enter dark mode google chrome",
+          "enter dark tab",
+          "enter dark tab browser",
+          "enter dark tab chrome",
+          "enter dark tab google chrome",
+          "enter dark window",
+          "enter dark window browser",
+          "enter dark window chrome",
+          "enter dark window google chrome",
+          "enter google chrome dark mode",
+          "enter google chrome dark tab",
+          "enter google chrome dark window",
+          "enter google chrome incognito",
+          "enter google chrome incognito mode",
+          "enter google chrome incognito tab",
+          "enter google chrome incognito window",
+          "enter google chrome private mode",
+          "enter google chrome private tab",
+          "enter google chrome private window",
+          "enter incognito",
+          "enter incognito browser",
+          "enter incognito chrome",
+          "enter incognito google chrome",
+          "enter incognito mode",
+          "enter incognito mode browser",
+          "enter incognito mode chrome",
+          "enter incognito mode google chrome",
+          "enter incognito tab",
+          "enter incognito tab browser",
+          "enter incognito tab chrome",
+          "enter incognito tab google chrome",
+          "enter incognito window",
+          "enter incognito window browser",
+          "enter incognito window chrome",
+          "enter incognito window google chrome",
+          "enter private mode",
+          "enter private mode browser",
+          "enter private mode chrome",
+          "enter private mode google chrome",
+          "enter private tab",
+          "enter private tab browser",
+          "enter private tab chrome",
+          "enter private tab google chrome",
+          "enter private window",
+          "enter private window browser",
+          "enter private window chrome",
+          "enter private window google chrome",
+          "google chrome dark mode enter",
+          "google chrome dark mode launch",
+          "google chrome dark mode open",
+          "google chrome dark mode start",
+          "google chrome dark tab enter",
+          "google chrome dark tab launch",
+          "google chrome dark tab open",
+          "google chrome dark tab start",
+          "google chrome dark window enter",
+          "google chrome dark window launch",
+          "google chrome dark window open",
+          "google chrome dark window start",
+          "google chrome enter dark mode",
+          "google chrome enter dark tab",
+          "google chrome enter dark window",
+          "google chrome enter incognito",
+          "google chrome enter incognito mode",
+          "google chrome enter incognito tab",
+          "google chrome enter incognito window",
+          "google chrome enter private mode",
+          "google chrome enter private tab",
+          "google chrome enter private window",
+          "google chrome incognito enter",
+          "google chrome incognito launch",
+          "google chrome incognito mode enter",
+          "google chrome incognito mode launch",
+          "google chrome incognito mode open",
+          "google chrome incognito mode start",
+          "google chrome incognito open",
+          "google chrome incognito start",
+          "google chrome incognito tab enter",
+          "google chrome incognito tab launch",
+          "google chrome incognito tab open",
+          "google chrome incognito tab start",
+          "google chrome incognito window enter",
+          "google chrome incognito window launch",
+          "google chrome incognito window open",
+          "google chrome incognito window start",
+          "google chrome launch dark mode",
+          "google chrome launch dark tab",
+          "google chrome launch dark window",
+          "google chrome launch incognito",
+          "google chrome launch incognito mode",
+          "google chrome launch incognito tab",
+          "google chrome launch incognito window",
+          "google chrome launch private mode",
+          "google chrome launch private tab",
+          "google chrome launch private window",
+          "google chrome open dark mode",
+          "google chrome open dark tab",
+          "google chrome open dark window",
+          "google chrome open incognito",
+          "google chrome open incognito mode",
+          "google chrome open incognito tab",
+          "google chrome open incognito window",
+          "google chrome open private mode",
+          "google chrome open private tab",
+          "google chrome open private window",
+          "google chrome private mode enter",
+          "google chrome private mode launch",
+          "google chrome private mode open",
+          "google chrome private mode start",
+          "google chrome private tab enter",
+          "google chrome private tab launch",
+          "google chrome private tab open",
+          "google chrome private tab start",
+          "google chrome private window enter",
+          "google chrome private window launch",
+          "google chrome private window open",
+          "google chrome private window start",
+          "google chrome start dark mode",
+          "google chrome start dark tab",
+          "google chrome start dark window",
+          "google chrome start incognito",
+          "google chrome start incognito mode",
+          "google chrome start incognito tab",
+          "google chrome start incognito window",
+          "google chrome start private mode",
+          "google chrome start private tab",
+          "google chrome start private window",
+          "incognito browser enter",
+          "incognito browser launch",
+          "incognito browser open",
+          "incognito browser start",
+          "incognito chrome enter",
+          "incognito chrome launch",
+          "incognito chrome open",
+          "incognito chrome start",
+          "incognito enter",
+          "incognito enter browser",
+          "incognito enter chrome",
+          "incognito enter google chrome",
+          "incognito google chrome enter",
+          "incognito google chrome launch",
+          "incognito google chrome open",
+          "incognito google chrome start",
+          "incognito launch",
+          "incognito launch browser",
+          "incognito launch chrome",
+          "incognito launch google chrome",
+          "incognito mode browser enter",
+          "incognito mode browser launch",
+          "incognito mode browser open",
+          "incognito mode browser start",
+          "incognito mode chrome enter",
+          "incognito mode chrome launch",
+          "incognito mode chrome open",
+          "incognito mode chrome start",
+          "incognito mode enter",
+          "incognito mode enter browser",
+          "incognito mode enter chrome",
+          "incognito mode enter google chrome",
+          "incognito mode google chrome enter",
+          "incognito mode google chrome launch",
+          "incognito mode google chrome open",
+          "incognito mode google chrome start",
+          "incognito mode launch",
+          "incognito mode launch browser",
+          "incognito mode launch chrome",
+          "incognito mode launch google chrome",
+          "incognito mode open",
+          "incognito mode open browser",
+          "incognito mode open chrome",
+          "incognito mode open google chrome",
+          "incognito mode start",
+          "incognito mode start browser",
+          "incognito mode start chrome",
+          "incognito mode start google chrome",
+          "incognito open",
+          "incognito open browser",
+          "incognito open chrome",
+          "incognito open google chrome",
+          "incognito start",
+          "incognito start browser",
+          "incognito start chrome",
+          "incognito start google chrome",
+          "incognito tab browser enter",
+          "incognito tab browser launch",
+          "incognito tab browser open",
+          "incognito tab browser start",
+          "incognito tab chrome enter",
+          "incognito tab chrome launch",
+          "incognito tab chrome open",
+          "incognito tab chrome start",
+          "incognito tab enter",
+          "incognito tab enter browser",
+          "incognito tab enter chrome",
+          "incognito tab enter google chrome",
+          "incognito tab google chrome enter",
+          "incognito tab google chrome launch",
+          "incognito tab google chrome open",
+          "incognito tab google chrome start",
+          "incognito tab launch",
+          "incognito tab launch browser",
+          "incognito tab launch chrome",
+          "incognito tab launch google chrome",
+          "incognito tab open",
+          "incognito tab open browser",
+          "incognito tab open chrome",
+          "incognito tab open google chrome",
+          "incognito tab start",
+          "incognito tab start browser",
+          "incognito tab start chrome",
+          "incognito tab start google chrome",
+          "incognito window browser enter",
+          "incognito window browser launch",
+          "incognito window browser open",
+          "incognito window browser start",
+          "incognito window chrome enter",
+          "incognito window chrome launch",
+          "incognito window chrome open",
+          "incognito window chrome start",
+          "incognito window enter",
+          "incognito window enter browser",
+          "incognito window enter chrome",
+          "incognito window enter google chrome",
+          "incognito window google chrome enter",
+          "incognito window google chrome launch",
+          "incognito window google chrome open",
+          "incognito window google chrome start",
+          "incognito window launch",
+          "incognito window launch browser",
+          "incognito window launch chrome",
+          "incognito window launch google chrome",
+          "incognito window open",
+          "incognito window open browser",
+          "incognito window open chrome",
+          "incognito window open google chrome",
+          "incognito window start",
+          "incognito window start browser",
+          "incognito window start chrome",
+          "incognito window start google chrome",
+          "launch browser dark mode",
+          "launch browser dark tab",
+          "launch browser dark window",
+          "launch browser incognito",
+          "launch browser incognito mode",
+          "launch browser incognito tab",
+          "launch browser incognito window",
+          "launch browser private mode",
+          "launch browser private tab",
+          "launch browser private window",
+          "launch chrome dark mode",
+          "launch chrome dark tab",
+          "launch chrome dark window",
+          "launch chrome incognito",
+          "launch chrome incognito mode",
+          "launch chrome incognito tab",
+          "launch chrome incognito window",
+          "launch chrome private mode",
+          "launch chrome private tab",
+          "launch chrome private window",
+          "launch dark mode",
+          "launch dark mode browser",
+          "launch dark mode chrome",
+          "launch dark mode google chrome",
+          "launch dark tab",
+          "launch dark tab browser",
+          "launch dark tab chrome",
+          "launch dark tab google chrome",
+          "launch dark window",
+          "launch dark window browser",
+          "launch dark window chrome",
+          "launch dark window google chrome",
+          "launch google chrome dark mode",
+          "launch google chrome dark tab",
+          "launch google chrome dark window",
+          "launch google chrome incognito",
+          "launch google chrome incognito mode",
+          "launch google chrome incognito tab",
+          "launch google chrome incognito window",
+          "launch google chrome private mode",
+          "launch google chrome private tab",
+          "launch google chrome private window",
+          "launch incognito",
+          "launch incognito browser",
+          "launch incognito chrome",
+          "launch incognito google chrome",
+          "launch incognito mode",
+          "launch incognito mode browser",
+          "launch incognito mode chrome",
+          "launch incognito mode google chrome",
+          "launch incognito tab",
+          "launch incognito tab browser",
+          "launch incognito tab chrome",
+          "launch incognito tab google chrome",
+          "launch incognito window",
+          "launch incognito window browser",
+          "launch incognito window chrome",
+          "launch incognito window google chrome",
+          "launch private mode",
+          "launch private mode browser",
+          "launch private mode chrome",
+          "launch private mode google chrome",
+          "launch private tab",
+          "launch private tab browser",
+          "launch private tab chrome",
+          "launch private tab google chrome",
+          "launch private window",
+          "launch private window browser",
+          "launch private window chrome",
+          "launch private window google chrome",
+          "open browser dark mode",
+          "open browser dark tab",
+          "open browser dark window",
+          "open browser incognito",
+          "open browser incognito mode",
+          "open browser incognito tab",
+          "open browser incognito window",
+          "open browser private mode",
+          "open browser private tab",
+          "open browser private window",
+          "open chrome dark mode",
+          "open chrome dark tab",
+          "open chrome dark window",
+          "open chrome incognito",
+          "open chrome incognito mode",
+          "open chrome incognito tab",
+          "open chrome incognito window",
+          "open chrome private mode",
+          "open chrome private tab",
+          "open chrome private window",
+          "open dark mode",
+          "open dark mode browser",
+          "open dark mode chrome",
+          "open dark mode google chrome",
+          "open dark tab",
+          "open dark tab browser",
+          "open dark tab chrome",
+          "open dark tab google chrome",
+          "open dark window",
+          "open dark window browser",
+          "open dark window chrome",
+          "open dark window google chrome",
+          "open google chrome dark mode",
+          "open google chrome dark tab",
+          "open google chrome dark window",
+          "open google chrome incognito",
+          "open google chrome incognito mode",
+          "open google chrome incognito tab",
+          "open google chrome incognito window",
+          "open google chrome private mode",
+          "open google chrome private tab",
+          "open google chrome private window",
+          "open incognito",
+          "open incognito browser",
+          "open incognito chrome",
+          "open incognito google chrome",
+          "open incognito mode",
+          "open incognito mode browser",
+          "open incognito mode chrome",
+          "open incognito mode google chrome",
+          "open incognito tab",
+          "open incognito tab browser",
+          "open incognito tab chrome",
+          "open incognito tab google chrome",
+          "open incognito window",
+          "open incognito window browser",
+          "open incognito window chrome",
+          "open incognito window google chrome",
+          "open private mode",
+          "open private mode browser",
+          "open private mode chrome",
+          "open private mode google chrome",
+          "open private tab",
+          "open private tab browser",
+          "open private tab chrome",
+          "open private tab google chrome",
+          "open private window",
+          "open private window browser",
+          "open private window chrome",
+          "open private window google chrome",
+          "private mode browser enter",
+          "private mode browser launch",
+          "private mode browser open",
+          "private mode browser start",
+          "private mode chrome enter",
+          "private mode chrome launch",
+          "private mode chrome open",
+          "private mode chrome start",
+          "private mode enter",
+          "private mode enter browser",
+          "private mode enter chrome",
+          "private mode enter google chrome",
+          "private mode google chrome enter",
+          "private mode google chrome launch",
+          "private mode google chrome open",
+          "private mode google chrome start",
+          "private mode launch",
+          "private mode launch browser",
+          "private mode launch chrome",
+          "private mode launch google chrome",
+          "private mode open",
+          "private mode open browser",
+          "private mode open chrome",
+          "private mode open google chrome",
+          "private mode start",
+          "private mode start browser",
+          "private mode start chrome",
+          "private mode start google chrome",
+          "private tab browser enter",
+          "private tab browser launch",
+          "private tab browser open",
+          "private tab browser start",
+          "private tab chrome enter",
+          "private tab chrome launch",
+          "private tab chrome open",
+          "private tab chrome start",
+          "private tab enter",
+          "private tab enter browser",
+          "private tab enter chrome",
+          "private tab enter google chrome",
+          "private tab google chrome enter",
+          "private tab google chrome launch",
+          "private tab google chrome open",
+          "private tab google chrome start",
+          "private tab launch",
+          "private tab launch browser",
+          "private tab launch chrome",
+          "private tab launch google chrome",
+          "private tab open",
+          "private tab open browser",
+          "private tab open chrome",
+          "private tab open google chrome",
+          "private tab start",
+          "private tab start browser",
+          "private tab start chrome",
+          "private tab start google chrome",
+          "private window browser enter",
+          "private window browser launch",
+          "private window browser open",
+          "private window browser start",
+          "private window chrome enter",
+          "private window chrome launch",
+          "private window chrome open",
+          "private window chrome start",
+          "private window enter",
+          "private window enter browser",
+          "private window enter chrome",
+          "private window enter google chrome",
+          "private window google chrome enter",
+          "private window google chrome launch",
+          "private window google chrome open",
+          "private window google chrome start",
+          "private window launch",
+          "private window launch browser",
+          "private window launch chrome",
+          "private window launch google chrome",
+          "private window open",
+          "private window open browser",
+          "private window open chrome",
+          "private window open google chrome",
+          "private window start",
+          "private window start browser",
+          "private window start chrome",
+          "private window start google chrome",
+          "start browser dark mode",
+          "start browser dark tab",
+          "start browser dark window",
+          "start browser incognito",
+          "start browser incognito mode",
+          "start browser incognito tab",
+          "start browser incognito window",
+          "start browser private mode",
+          "start browser private tab",
+          "start browser private window",
+          "start chrome dark mode",
+          "start chrome dark tab",
+          "start chrome dark window",
+          "start chrome incognito",
+          "start chrome incognito mode",
+          "start chrome incognito tab",
+          "start chrome incognito window",
+          "start chrome private mode",
+          "start chrome private tab",
+          "start chrome private window",
+          "start dark mode",
+          "start dark mode browser",
+          "start dark mode chrome",
+          "start dark mode google chrome",
+          "start dark tab",
+          "start dark tab browser",
+          "start dark tab chrome",
+          "start dark tab google chrome",
+          "start dark window",
+          "start dark window browser",
+          "start dark window chrome",
+          "start dark window google chrome",
+          "start google chrome dark mode",
+          "start google chrome dark tab",
+          "start google chrome dark window",
+          "start google chrome incognito",
+          "start google chrome incognito mode",
+          "start google chrome incognito tab",
+          "start google chrome incognito window",
+          "start google chrome private mode",
+          "start google chrome private tab",
+          "start google chrome private window",
+          "start incognito",
+          "start incognito browser",
+          "start incognito chrome",
+          "start incognito google chrome",
+          "start incognito mode",
+          "start incognito mode browser",
+          "start incognito mode chrome",
+          "start incognito mode google chrome",
+          "start incognito tab",
+          "start incognito tab browser",
+          "start incognito tab chrome",
+          "start incognito tab google chrome",
+          "start incognito window",
+          "start incognito window browser",
+          "start incognito window chrome",
+          "start incognito window google chrome",
+          "start private mode",
+          "start private mode browser",
+          "start private mode chrome",
+          "start private mode google chrome",
+          "start private tab",
+          "start private tab browser",
+          "start private tab chrome",
+          "start private tab google chrome",
+          "start private window",
+          "start private window browser",
+          "start private window chrome",
+          "start private window google chrome",
+      },
+
+      // Translate
+      {
+          "browser change language page",
+          "browser change language this",
+          "browser change language this page",
+          "browser page change language",
+          "browser page translate",
+          "browser this change language",
+          "browser this page change language",
+          "browser this page translate",
+          "browser this translate",
+          "browser translate page",
+          "browser translate this",
+          "browser translate this page",
+          "change language browser page",
+          "change language browser this",
+          "change language browser this page",
+          "change language chrome page",
+          "change language chrome this",
+          "change language chrome this page",
+          "change language google chrome page",
+          "change language google chrome this",
+          "change language google chrome this page",
+          "change language page",
+          "change language page browser",
+          "change language page chrome",
+          "change language page google chrome",
+          "change language this",
+          "change language this browser",
+          "change language this chrome",
+          "change language this google chrome",
+          "change language this page",
+          "change language this page browser",
+          "change language this page chrome",
+          "change language this page google chrome",
+          "chrome change language page",
+          "chrome change language this",
+          "chrome change language this page",
+          "chrome page change language",
+          "chrome page translate",
+          "chrome this change language",
+          "chrome this page change language",
+          "chrome this page translate",
+          "chrome this translate",
+          "chrome translate page",
+          "chrome translate this",
+          "chrome translate this page",
+          "google chrome change language page",
+          "google chrome change language this",
+          "google chrome change language this page",
+          "google chrome page change language",
+          "google chrome page translate",
+          "google chrome this change language",
+          "google chrome this page change language",
+          "google chrome this page translate",
+          "google chrome this translate",
+          "google chrome translate page",
+          "google chrome translate this",
+          "google chrome translate this page",
+          "page browser change language",
+          "page browser translate",
+          "page change language",
+          "page change language browser",
+          "page change language chrome",
+          "page change language google chrome",
+          "page chrome change language",
+          "page chrome translate",
+          "page google chrome change language",
+          "page google chrome translate",
+          "page translate",
+          "page translate browser",
+          "page translate chrome",
+          "page translate google chrome",
+          "this browser change language",
+          "this browser translate",
+          "this change language",
+          "this change language browser",
+          "this change language chrome",
+          "this change language google chrome",
+          "this chrome change language",
+          "this chrome translate",
+          "this google chrome change language",
+          "this google chrome translate",
+          "this page browser change language",
+          "this page browser translate",
+          "this page change language",
+          "this page change language browser",
+          "this page change language chrome",
+          "this page change language google chrome",
+          "this page chrome change language",
+          "this page chrome translate",
+          "this page google chrome change language",
+          "this page google chrome translate",
+          "this page translate",
+          "this page translate browser",
+          "this page translate chrome",
+          "this page translate google chrome",
+          "this translate",
+          "this translate browser",
+          "this translate chrome",
+          "this translate google chrome",
+          "translate browser page",
+          "translate browser this",
+          "translate browser this page",
+          "translate chrome page",
+          "translate chrome this",
+          "translate chrome this page",
+          "translate google chrome page",
+          "translate google chrome this",
+          "translate google chrome this page",
+          "translate page",
+          "translate page browser",
+          "translate page chrome",
+          "translate page google chrome",
+          "translate this",
+          "translate this browser",
+          "translate this chrome",
+          "translate this google chrome",
+          "translate this page",
+          "translate this page browser",
+          "translate this page chrome",
+          "translate this page google chrome",
+      },
+
+      // Update Chrome
+      {
+          "browser install",
+          "browser update",
+          "browser upgrade",
+          "chrome install",
+          "chrome update",
+          "chrome upgrade",
+          "google chrome install",
+          "google chrome update",
+          "google chrome upgrade",
+          "install browser",
+          "install chrome",
+          "install google chrome",
+          "update browser",
+          "update chrome",
+          "update google chrome",
+          "upgrade browser",
+          "upgrade chrome",
+          "upgrade google chrome",
+      },
+
+      // End of generated test code
+  };
+
+  // The test code below ensures that each element of the outer vector above,
+  // |literal_concept_expressions|, fully corresponds to exactly one Pedal
+  // implementation.  For each one, the full list of literal expressions are
+  // confirmed as concept matches for the Pedal.  Finally, we verify that every
+  // implemented Pedal has been tested using set logic.
+  const auto pedals = GetPedalImplementations();
+  std::unordered_set<const OmniboxPedal*> found_pedals(pedals.size());
+  for (const auto& pedal_concept : literal_concept_expressions) {
+    const base::string16 first_trigger = base::ASCIIToUTF16(pedal_concept[0]);
+    auto iter =
+        std::find_if(pedals.begin(), pedals.end(), [&](const auto& pedal) {
+          return pedal->IsConceptMatch(first_trigger);
+        });
+    EXPECT_NE(iter, pedals.end())
+        << "Canonical pedal not found for: " << first_trigger;
+    const OmniboxPedal* canonical_pedal = iter->get();
+    const bool is_newly_found = found_pedals.insert(canonical_pedal).second;
+    EXPECT_TRUE(is_newly_found)
+        << "Found the same Pedal more than once with: " << first_trigger;
+    for (const char* literal : pedal_concept) {
+      const base::string16 expression = base::ASCIIToUTF16(literal);
+      const auto is_match = [&](const auto& pedal) {
+        return pedal->IsConceptMatch(expression);
+      };
+      iter = std::find_if(pedals.begin(), pedals.end(), is_match);
+      EXPECT_NE(iter, pedals.end()) << "Pedal not found for: " << expression;
+      EXPECT_EQ(iter->get(), canonical_pedal)
+          << "Found wrong Pedal for: " << expression;
+      iter = std::find_if(iter + 1, pedals.end(), is_match);
+      EXPECT_EQ(iter, pedals.end())
+          << "Found more than one Pedal match for: " << expression;
+    }
+  }
+  EXPECT_EQ(pedals.size(), found_pedals.size())
+      << "Not all implemented Pedals were represented in test cases.";
+}
diff --git a/components/omnibox/browser/omnibox_view.cc b/components/omnibox/browser/omnibox_view.cc
index 1839db1..db2ea6a 100644
--- a/components/omnibox/browser/omnibox_view.cc
+++ b/components/omnibox/browser/omnibox_view.cc
@@ -265,7 +265,7 @@
     model_->OnChanged();
 }
 
-void OmniboxView::UpdateTextStyle(
+bool OmniboxView::UpdateTextStyle(
     const base::string16& display_text,
     const bool text_is_url,
     const AutocompleteSchemeClassifier& classifier) {
@@ -320,4 +320,7 @@
   // Emphasize the scheme for security UI display purposes (if necessary).
   if (!model()->user_input_in_progress() && scheme_range.IsValid())
     UpdateSchemeStyle(scheme_range);
+
+  // Path is eligible for fading only when the host is the only emphasized part.
+  return deemphasize == ALL_BUT_HOST;
 }
diff --git a/components/omnibox/browser/omnibox_view.h b/components/omnibox/browser/omnibox_view.h
index 7b419c1d..6c59a0e 100644
--- a/components/omnibox/browser/omnibox_view.h
+++ b/components/omnibox/browser/omnibox_view.h
@@ -283,8 +283,8 @@
   // everything is emphasized equally, whereas for URLs the scheme may be styled
   // based on the current security state, with parts of the URL de-emphasized to
   // draw attention to whatever best represents the "identity" of the current
-  // URL.
-  void UpdateTextStyle(const base::string16& display_text,
+  // URL. Returns true if the path component is eligible for fadeout.
+  bool UpdateTextStyle(const base::string16& display_text,
                        const bool text_is_url,
                        const AutocompleteSchemeClassifier& classifier);
 
diff --git a/components/page_image_annotation/DEPS b/components/page_image_annotation/DEPS
index e7981184..2ea972b2 100644
--- a/components/page_image_annotation/DEPS
+++ b/components/page_image_annotation/DEPS
@@ -2,4 +2,7 @@
   # Page image annotation is a layered component; subdirectories must explicitly
   # introduce the ability to use the content layer as appropriate.
   "-components/page_image_annotation/content",
+  "+services/image_annotation/public/mojom",
+  "+services/image_annotation/public/cpp",
+  "+third_party/skia",
 ]
diff --git a/components/page_image_annotation/core/BUILD.gn b/components/page_image_annotation/core/BUILD.gn
index 396b16d8..7d36bd6 100644
--- a/components/page_image_annotation/core/BUILD.gn
+++ b/components/page_image_annotation/core/BUILD.gn
@@ -2,28 +2,39 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-static_library("core") {
-  sources = [
-    "page_annotator.cc",
-    "page_annotator.h",
-  ]
+# TODO(crbug.com/916420): this does not compile on iOS, where JPEGCodec (used by
+#                         ImageProcessor) is not supported. We should use an
+#                         alternative encoding scheme or support JPEGCodec on
+#                         iOS.
 
-  deps = [
-    "//base",
-  ]
-}
+if (!is_ios) {
+  static_library("core") {
+    sources = [
+      "page_annotator.cc",
+      "page_annotator.h",
+    ]
 
-source_set("unit_tests") {
-  testonly = true
-  sources = [
-    "page_annotator_unittest.cc",
-  ]
+    deps = [
+      "//base",
+      "//services/image_annotation/public/cpp",
+      "//skia",
+    ]
+  }
 
-  deps = [
-    ":core",
-    "//base",
-    "//base/test:test_support",
-    "//testing/gmock",
-    "//testing/gtest",
-  ]
+  source_set("unit_tests") {
+    testonly = true
+    sources = [
+      "page_annotator_unittest.cc",
+    ]
+
+    deps = [
+      ":core",
+      "//base",
+      "//base/test:test_support",
+      "//services/image_annotation/public/cpp",
+      "//skia",
+      "//testing/gmock",
+      "//testing/gtest",
+    ]
+  }
 }
diff --git a/components/page_image_annotation/core/page_annotator.cc b/components/page_image_annotation/core/page_annotator.cc
index 3f516ec..7755315 100644
--- a/components/page_image_annotation/core/page_annotator.cc
+++ b/components/page_image_annotation/core/page_annotator.cc
@@ -8,59 +8,59 @@
 
 PageAnnotator::Observer::~Observer() {}
 
-PageAnnotator::Subscription::Subscription(
-    const Observer* const observer,
-    base::WeakPtr<PageAnnotator> page_annotator)
-    : observer_(observer), page_annotator_(page_annotator) {}
-
-PageAnnotator::Subscription::Subscription(Subscription&& subscription) =
-    default;
-
-PageAnnotator::Subscription::~Subscription() {
-  Cancel();
-}
-
-void PageAnnotator::Subscription::Cancel() {
-  if (page_annotator_)
-    page_annotator_->RemoveObserver(observer_);
-}
-
-PageAnnotator::PageAnnotator() : weak_ptr_factory_(this) {}
+PageAnnotator::PageAnnotator() {}
 
 PageAnnotator::~PageAnnotator() {}
 
-void PageAnnotator::ImageAdded(const uint64_t node_id,
-                               const std::string& source_id) {
-  // TODO(crbug.com/916363): create a connection to the image annotation service
-  //                         for this image.
-  for (Observer& observer : observers_) {
-    observer.OnImageAdded(node_id);
-  }
-}
+void PageAnnotator::ImageAddedOrPossiblyModified(
+    const ImageMetadata& metadata,
+    base::RepeatingCallback<SkBitmap()> pixels_callback) {
+  const auto lookup = images_.find(metadata.node_id);
 
-void PageAnnotator::ImageModified(const uint64_t node_id,
-                                  const std::string& source_id) {
-  // TODO(crbug.com/916363): reset the service connection for this image.
+  if (lookup == images_.end()) {
+    // This is an image addition.
 
-  for (Observer& observer : observers_) {
-    observer.OnImageModified(node_id);
+    AddNewImage(metadata, std::move(pixels_callback));
+
+    for (Observer& observer : observers_) {
+      observer.OnImageAdded(metadata);
+    }
+  } else if (lookup->second.first.source_id != metadata.source_id) {
+    // We already have older data for this node ID; this is an update.
+
+    images_.erase(lookup);
+    AddNewImage(metadata, std::move(pixels_callback));
+
+    for (Observer& observer : observers_) {
+      observer.OnImageModified(metadata);
+    }
   }
 }
 
 void PageAnnotator::ImageRemoved(const uint64_t node_id) {
-  // TODO(crbug.com/916363): close the service connection for this image.
+  images_.erase(node_id);
+
   for (Observer& observer : observers_) {
     observer.OnImageRemoved(node_id);
   }
 }
 
-PageAnnotator::Subscription PageAnnotator::AddObserver(Observer* observer) {
+void PageAnnotator::AddObserver(Observer* const observer) {
   observers_.AddObserver(observer);
-  return Subscription(observer, weak_ptr_factory_.GetWeakPtr());
+
+  // The new observer has not received any previous messages; inform them now of
+  // all existing images.
+  for (const auto& image : images_) {
+    observer->OnImageAdded(image.second.first);
+  }
 }
 
-void PageAnnotator::RemoveObserver(const Observer* observer) {
-  observers_.RemoveObserver(observer);
+void PageAnnotator::AddNewImage(
+    const ImageMetadata& metadata,
+    base::RepeatingCallback<SkBitmap()> pixels_callback) {
+  images_.emplace(std::piecewise_construct,
+                  std::forward_as_tuple(metadata.node_id),
+                  std::forward_as_tuple(metadata, std::move(pixels_callback)));
 }
 
 }  // namespace page_image_annotation
diff --git a/components/page_image_annotation/core/page_annotator.h b/components/page_image_annotation/core/page_annotator.h
index b6edf1e..4469251 100644
--- a/components/page_image_annotation/core/page_annotator.h
+++ b/components/page_image_annotation/core/page_annotator.h
@@ -5,10 +5,16 @@
 #ifndef COMPONENTS_PAGE_IMAGE_ANNOTATION_CORE_PAGE_ANNOTATOR_H_
 #define COMPONENTS_PAGE_IMAGE_ANNOTATION_CORE_PAGE_ANNOTATOR_H_
 
+#include <map>
+#include <utility>
+
+#include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
+#include "services/image_annotation/public/cpp/image_processor.h"
+#include "third_party/skia/include/core/SkBitmap.h"
 
 namespace page_image_annotation {
 
@@ -19,6 +25,18 @@
 //                         communication with the service).
 class PageAnnotator {
  public:
+  struct ImageMetadata {
+    // A unique ID identifying an image on this page. Two separate images (even
+    // with the same URL / pixels) on one page will be given separate node IDs.
+    uint64_t node_id;
+
+    // The URL or a hash of the data URI of this image. Two (identical) images
+    // can have the same source ID.
+    std::string source_id;
+
+    // TODO(crbug.com/916363): add other useful info (e.g. image dimensions).
+  };
+
   // Clients (i.e. classes that annotate page images) should implement this
   // interface.
   class Observer : public base::CheckedObserver {
@@ -31,56 +49,41 @@
     // Called exactly once per image, at the point that the image appears on the
     // page (or at the point that the observer subscribes to the page annotator,
     // if the image already exists on page).
-    virtual void OnImageAdded(uint64_t node_id) = 0;
+    virtual void OnImageAdded(const ImageMetadata& image) = 0;
 
     // Called at the point that an image source is updated.
-    virtual void OnImageModified(uint64_t node_id) = 0;
+    virtual void OnImageModified(const ImageMetadata& image) = 0;
 
     // Called at the point that an image disappears from the page.
     virtual void OnImageRemoved(uint64_t node_id) = 0;
   };
 
-  // A subscription instance must be held by each observer of the page
-  // annotator; an observer will receive updates from the page annotator until
-  // the Cancel method of the subscription is called (this occurs automatically
-  // on subscription destruction).
-  //
-  // Typically, both the page annotator and its observers are scoped to the
-  // lifetime of a render frame. Destruction of such objects can proceed in an
-  // unspecified order, so subscriptions are used to ensure the page annotator
-  // only communicates with an observers that are still alive.
-  class Subscription {
-   public:
-    Subscription(const Observer* observer,
-                 base::WeakPtr<PageAnnotator> page_annotator);
-    Subscription(Subscription&& subscription);
-    ~Subscription();
-
-    // Unsubscribe from updates from the page annotator.
-    void Cancel();
-
-   private:
-    const Observer* observer_;
-    base::WeakPtr<PageAnnotator> page_annotator_;
-
-    DISALLOW_COPY_AND_ASSIGN(Subscription);
-  };
-
   PageAnnotator();
   ~PageAnnotator();
 
   // Called by platform drivers.
-  void ImageAdded(uint64_t node_id, const std::string& source_id);
-  void ImageModified(uint64_t node_id, const std::string& source_id);
+  void ImageAddedOrPossiblyModified(
+      const ImageMetadata& metadata,
+      base::RepeatingCallback<SkBitmap()> pixels_callback);
   void ImageRemoved(uint64_t node_id);
 
-  Subscription AddObserver(Observer* observer) WARN_UNUSED_RESULT;
+  // An observer must outlive the PageAnnotator, or be destructed synchronously
+  // with the PageAnnotator (e.g. at the same point in the document lifecycle)
+  // and not reference the PageAnnotator in its destructor.
+  void AddObserver(Observer* observer);
 
  private:
-  void RemoveObserver(const Observer* observer);
+  // Add a new entry to |images_|.
+  //
+  // The lack of copy/move constructor for ImageProcessor makes this difficult,
+  // but we limit the complexity to this method.
+  void AddNewImage(const ImageMetadata& metadata,
+                   base::RepeatingCallback<SkBitmap()> pixels_callback);
 
   base::ObserverList<Observer> observers_;
-  base::WeakPtrFactory<PageAnnotator> weak_ptr_factory_;
+
+  std::map<uint64_t, std::pair<ImageMetadata, image_annotation::ImageProcessor>>
+      images_;
 
   DISALLOW_COPY_AND_ASSIGN(PageAnnotator);
 };
diff --git a/components/page_image_annotation/core/page_annotator_unittest.cc b/components/page_image_annotation/core/page_annotator_unittest.cc
index eca2cbb..c16dcec 100644
--- a/components/page_image_annotation/core/page_annotator_unittest.cc
+++ b/components/page_image_annotation/core/page_annotator_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "components/page_image_annotation/core/page_annotator.h"
 
+#include "base/test/scoped_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -11,51 +12,57 @@
 
 using testing::Eq;
 
-// Tests that destroying subscriptions successfully prevents notifications.
-TEST(PageAnnotatorTest, Subscriptions) {
+// Tests that the right messages are sent to observers.
+TEST(PageAnnotatorTest, Observers) {
   class TestObserver : public PageAnnotator::Observer {
    public:
-    TestObserver(PageAnnotator* const page_annotator)
-        : sub_(page_annotator->AddObserver(this)), last_id_(0ul) {}
+    TestObserver() : last_added_(0), last_modified_(0), last_removed_(0) {}
 
-    void OnImageAdded(const uint64_t node_id) override { last_id_ = node_id; }
-    void OnImageModified(const uint64_t node_id) override {
-      last_id_ = node_id;
+    void OnImageAdded(const PageAnnotator::ImageMetadata& metadata) override {
+      last_added_ = metadata.node_id;
     }
-    void OnImageRemoved(const uint64_t node_id) override { last_id_ = node_id; }
 
-    PageAnnotator::Subscription sub_;
-    uint64_t last_id_;
+    void OnImageModified(
+        const PageAnnotator::ImageMetadata& metadata) override {
+      last_modified_ = metadata.node_id;
+    }
+
+    void OnImageRemoved(const uint64_t node_id) override {
+      last_removed_ = node_id;
+    }
+
+    uint64_t last_added_, last_modified_, last_removed_;
   };
 
+  base::test::ScopedTaskEnvironment test_task_env;
+
+  const auto get_pixels = base::BindRepeating([]() { return SkBitmap(); });
+
   PageAnnotator page_annotator;
-  TestObserver o1(&page_annotator), o2(&page_annotator);
 
-  page_annotator.ImageAdded(1ul, "test.jpg");
-  EXPECT_THAT(o1.last_id_, Eq(1ul));
-  EXPECT_THAT(o2.last_id_, Eq(1ul));
+  TestObserver o1;
+  page_annotator.AddObserver(&o1);
 
-  page_annotator.ImageAdded(2ul, "example.png");
-  EXPECT_THAT(o1.last_id_, Eq(2ul));
-  EXPECT_THAT(o2.last_id_, Eq(2ul));
+  page_annotator.ImageAddedOrPossiblyModified({1ul, "test.jpg"}, get_pixels);
+  EXPECT_THAT(o1.last_added_, Eq(1ul));
 
-  page_annotator.ImageModified(1ul, "demo.gif");
-  EXPECT_THAT(o1.last_id_, Eq(1ul));
-  EXPECT_THAT(o2.last_id_, Eq(1ul));
+  page_annotator.ImageAddedOrPossiblyModified({2ul, "example.png"}, get_pixels);
+  EXPECT_THAT(o1.last_added_, Eq(2ul));
+
+  page_annotator.ImageAddedOrPossiblyModified({1ul, "demo.gif"}, get_pixels);
+  EXPECT_THAT(o1.last_added_, Eq(2ul));
+  EXPECT_THAT(o1.last_modified_, Eq(1ul));
 
   page_annotator.ImageRemoved(2ul);
-  EXPECT_THAT(o1.last_id_, Eq(2ul));
-  EXPECT_THAT(o2.last_id_, Eq(2ul));
+  EXPECT_THAT(o1.last_added_, Eq(2ul));
+  EXPECT_THAT(o1.last_modified_, Eq(1ul));
+  EXPECT_THAT(o1.last_removed_, Eq(2ul));
 
-  o1.sub_.Cancel();
-  page_annotator.ImageAdded(3ul, "placeholder.bmp");
-  EXPECT_THAT(o1.last_id_, Eq(2ul));
-  EXPECT_THAT(o2.last_id_, Eq(3ul));
+  TestObserver o2;
+  page_annotator.AddObserver(&o2);
 
-  o2.sub_.Cancel();
-  page_annotator.ImageRemoved(1ul);
-  EXPECT_THAT(o1.last_id_, Eq(2ul));
-  EXPECT_THAT(o2.last_id_, Eq(3ul));
+  EXPECT_THAT(o1.last_added_, Eq(2ul));
+  EXPECT_THAT(o2.last_added_, Eq(1ul));
 }
 
 // TODO(crbug.com/916363): add more tests when behavior is added to the
diff --git a/components/password_manager/core/browser/password_store.cc b/components/password_manager/core/browser/password_store.cc
index 12e3e58..350d7d7 100644
--- a/components/password_manager/core/browser/password_store.cc
+++ b/components/password_manager/core/browser/password_store.cc
@@ -584,6 +584,8 @@
     observers_->Notify(FROM_HERE, &Observer::OnLoginsChanged, changes);
     if (syncable_service_)
       syncable_service_->ActOnPasswordStoreChanges(changes);
+    if (sync_bridge_)
+      sync_bridge_->ActOnPasswordStoreChanges(changes);
 // TODO(crbug.com/706392): Fix password reuse detection for Android.
 #if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
     if (reuse_detector_)
diff --git a/components/password_manager/core/browser/sync/password_sync_bridge.cc b/components/password_manager/core/browser/sync/password_sync_bridge.cc
index f05fd19..e9475fd 100644
--- a/components/password_manager/core/browser/sync/password_sync_bridge.cc
+++ b/components/password_manager/core/browser/sync/password_sync_bridge.cc
@@ -5,6 +5,9 @@
 #include "components/password_manager/core/browser/sync/password_sync_bridge.h"
 
 #include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/autofill/core/common/password_form.h"
 #include "components/sync/model/metadata_change_list.h"
 #include "components/sync/model/model_type_change_processor.h"
 #include "net/base/escape.h"
@@ -12,12 +15,94 @@
 
 namespace password_manager {
 
+namespace {
+
+sync_pb::PasswordSpecifics SpecificsFromPassword(
+    const autofill::PasswordForm& password_form) {
+  sync_pb::PasswordSpecifics specifics;
+  sync_pb::PasswordSpecificsData* password_data =
+      specifics.mutable_client_only_encrypted_data();
+  password_data->set_scheme(password_form.scheme);
+  password_data->set_signon_realm(password_form.signon_realm);
+  password_data->set_origin(password_form.origin.spec());
+  password_data->set_action(password_form.action.spec());
+  password_data->set_username_element(
+      base::UTF16ToUTF8(password_form.username_element));
+  password_data->set_password_element(
+      base::UTF16ToUTF8(password_form.password_element));
+  password_data->set_username_value(
+      base::UTF16ToUTF8(password_form.username_value));
+  password_data->set_password_value(
+      base::UTF16ToUTF8(password_form.password_value));
+  password_data->set_preferred(password_form.preferred);
+  password_data->set_date_created(
+      password_form.date_created.ToDeltaSinceWindowsEpoch().InMicroseconds());
+  password_data->set_blacklisted(password_form.blacklisted_by_user);
+  password_data->set_type(password_form.type);
+  password_data->set_times_used(password_form.times_used);
+  password_data->set_display_name(
+      base::UTF16ToUTF8(password_form.display_name));
+  password_data->set_avatar_url(password_form.icon_url.spec());
+  password_data->set_federation_url(
+      password_form.federation_origin.opaque()
+          ? std::string()
+          : password_form.federation_origin.Serialize());
+  return specifics;
+}
+
+std::unique_ptr<syncer::EntityData> CreateEntityData(
+    const autofill::PasswordForm& form) {
+  auto entity_data = std::make_unique<syncer::EntityData>();
+  *entity_data->specifics.mutable_password() = SpecificsFromPassword(form);
+  entity_data->non_unique_name = form.signon_realm;
+  return entity_data;
+}
+
+}  // namespace
+
 PasswordSyncBridge::PasswordSyncBridge(
     std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor)
     : ModelTypeSyncBridge(std::move(change_processor)) {}
 
 PasswordSyncBridge::~PasswordSyncBridge() = default;
 
+void PasswordSyncBridge::ActOnPasswordStoreChanges(
+    const PasswordStoreChangeList& local_changes) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // It's the responsibility of the callers to call this method within the same
+  // transaction as the data changes to fulfill atomic writes of data and
+  // metadata constraint.
+
+  // TODO(mamir):ActOnPasswordStoreChanges() DCHECK we are inside a
+  // transaction!;
+
+  if (!change_processor()->IsTrackingMetadata()) {
+    return;  // Sync processor not yet ready, don't sync.
+  }
+
+  // TODO(mamir):ActOnPasswordStoreChanges() can be called from
+  // ApplySyncChanges(). Do nothing in this case.
+  std::unique_ptr<syncer::MetadataChangeList> metadata_change_list =
+      CreateMetadataChangeList();
+
+  for (const PasswordStoreChange& change : local_changes) {
+    const std::string storage_key = base::NumberToString(change.primary_key());
+    switch (change.type()) {
+      case PasswordStoreChange::ADD:
+      case PasswordStoreChange::UPDATE: {
+        change_processor()->Put(storage_key, CreateEntityData(change.form()),
+                                metadata_change_list.get());
+        break;
+      }
+      case PasswordStoreChange::REMOVE: {
+        change_processor()->Delete(storage_key, metadata_change_list.get());
+        break;
+      }
+    }
+  }
+  // TODO(mamir): Persist the metadata.
+}
+
 void PasswordSyncBridge::OnSyncStarting(
     const syncer::DataTypeActivationRequest& request) {
   NOTIMPLEMENTED();
diff --git a/components/password_manager/core/browser/sync/password_sync_bridge.h b/components/password_manager/core/browser/sync/password_sync_bridge.h
index da19693..3a2a031 100644
--- a/components/password_manager/core/browser/sync/password_sync_bridge.h
+++ b/components/password_manager/core/browser/sync/password_sync_bridge.h
@@ -6,6 +6,8 @@
 #define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SYNC_PASSWORD_SYNC_BRIDGE_H_
 
 #include "base/macros.h"
+#include "base/sequence_checker.h"
+#include "components/password_manager/core/browser/password_store_change.h"
 #include "components/sync/model/model_type_sync_bridge.h"
 
 namespace syncer {
@@ -29,6 +31,11 @@
       std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor);
   ~PasswordSyncBridge() override;
 
+  // Notifies the bridge of changes to the password database. Callers are
+  // responsible for calling this function within the very same transaction as
+  // the data changes.
+  void ActOnPasswordStoreChanges(const PasswordStoreChangeList& changes);
+
   // ModelTypeSyncBridge implementation.
   void OnSyncStarting(
       const syncer::DataTypeActivationRequest& request) override;
@@ -50,6 +57,8 @@
       override;
 
  private:
+  SEQUENCE_CHECKER(sequence_checker_);
+
   DISALLOW_COPY_AND_ASSIGN(PasswordSyncBridge);
 };
 
diff --git a/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc b/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc
index 0bd537e..3a52cb4 100644
--- a/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc
+++ b/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc
@@ -4,16 +4,30 @@
 
 #include "components/password_manager/core/browser/sync/password_sync_bridge.h"
 
-#include "components/sync/model/fake_model_type_change_processor.h"
+#include "components/sync/model/metadata_batch.h"
+#include "components/sync/model/mock_model_type_change_processor.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using testing::Eq;
-
 namespace password_manager {
 
 namespace {
 
+using testing::_;
+using testing::Eq;
+using testing::Return;
+
+constexpr char kSignonRealm1[] = "abc";
+constexpr char kSignonRealm2[] = "def";
+constexpr char kSignonRealm3[] = "xyz";
+
+// |*args| must be of type EntityData.
+MATCHER_P(HasSignonRealm, expected_signon_realm, "") {
+  return arg->specifics.password()
+             .client_only_encrypted_data()
+             .signon_realm() == expected_signon_realm;
+}
+
 sync_pb::PasswordSpecifics CreateSpecifics(const std::string& origin,
                                            const std::string& username_element,
                                            const std::string& username_value,
@@ -31,17 +45,28 @@
   return password_specifics.password();
 }
 
+autofill::PasswordForm MakePasswordForm(const std::string& signon_realm) {
+  autofill::PasswordForm form;
+  form.signon_realm = signon_realm;
+  return form;
+}
+
 }  // namespace
 
 class PasswordSyncBridgeTest : public testing::Test {
  public:
   PasswordSyncBridgeTest()
-      : bridge_(std::make_unique<syncer::FakeModelTypeChangeProcessor>()) {}
+      : bridge_(mock_processor_.CreateForwardingProcessor()) {}
   ~PasswordSyncBridgeTest() override {}
 
   PasswordSyncBridge* bridge() { return &bridge_; }
 
+  syncer::MockModelTypeChangeProcessor& mock_processor() {
+    return mock_processor_;
+  }
+
  private:
+  testing::NiceMock<syncer::MockModelTypeChangeProcessor> mock_processor_;
   PasswordSyncBridge bridge_;
 };
 
@@ -57,4 +82,40 @@
          "|username_element|username_value|password_element|signon_realm"));
 }
 
+TEST_F(PasswordSyncBridgeTest, ShouldForwardLocalChangesToTheProcessor) {
+  ON_CALL(mock_processor(), IsTrackingMetadata()).WillByDefault(Return(true));
+
+  PasswordStoreChangeList changes;
+  changes.push_back(PasswordStoreChange(
+      PasswordStoreChange::ADD, MakePasswordForm(kSignonRealm1), /*id=*/1));
+  changes.push_back(PasswordStoreChange(
+      PasswordStoreChange::UPDATE, MakePasswordForm(kSignonRealm2), /*id=*/2));
+  changes.push_back(PasswordStoreChange(
+      PasswordStoreChange::REMOVE, MakePasswordForm(kSignonRealm3), /*id=*/3));
+
+  EXPECT_CALL(mock_processor(), Put("1", HasSignonRealm(kSignonRealm1), _));
+  EXPECT_CALL(mock_processor(), Put("2", HasSignonRealm(kSignonRealm2), _));
+  EXPECT_CALL(mock_processor(), Delete("3", _));
+
+  bridge()->ActOnPasswordStoreChanges(changes);
+}
+
+TEST_F(PasswordSyncBridgeTest,
+       ShouldNotForwardLocalChangesToTheProcessorIfSyncDisabled) {
+  ON_CALL(mock_processor(), IsTrackingMetadata()).WillByDefault(Return(false));
+
+  PasswordStoreChangeList changes;
+  changes.push_back(PasswordStoreChange(
+      PasswordStoreChange::ADD, MakePasswordForm(kSignonRealm1), /*id=*/1));
+  changes.push_back(PasswordStoreChange(
+      PasswordStoreChange::UPDATE, MakePasswordForm(kSignonRealm2), /*id=*/2));
+  changes.push_back(PasswordStoreChange(
+      PasswordStoreChange::REMOVE, MakePasswordForm(kSignonRealm3), /*id=*/3));
+
+  EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
+  EXPECT_CALL(mock_processor(), Delete(_, _)).Times(0);
+
+  bridge()->ActOnPasswordStoreChanges(changes);
+}
+
 }  // namespace password_manager
diff --git a/components/viz/service/display/display_resource_provider.cc b/components/viz/service/display/display_resource_provider.cc
index 5dd10c8d..691e2903 100644
--- a/components/viz/service/display/display_resource_provider.cc
+++ b/components/viz/service/display/display_resource_provider.cc
@@ -14,6 +14,7 @@
 #include "components/viz/common/gpu/context_provider.h"
 #include "components/viz/common/resources/resource_sizes.h"
 #include "components/viz/service/display/shared_bitmap_manager.h"
+#include "components/viz/service/display/skia_output_surface.h"
 #include "gpu/command_buffer/client/context_support.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
 #include "third_party/skia/include/gpu/GrBackendSurface.h"
@@ -648,12 +649,12 @@
     // TODO(https://crbug.com/922592): Batch deletion for reduced overhead.
     auto sk_image_it = resource_sk_images_.find(local_id);
     if (sk_image_it != resource_sk_images_.end()) {
-      ResourceSkImage found(std::move(sk_image_it->second));
+      sk_sp<SkImage> found(std::move(sk_image_it->second));
       resource_sk_images_.erase(sk_image_it);
 
-      if (found.destroy_callback.has_value()) {
+      if (external_use_client_) {
         gpu::SyncToken token =
-            found.destroy_callback->Run(std::move(found.image));
+            external_use_client_->QueueReleasePromiseSkImage(std::move(found));
         resource.UpdateSyncToken(token);
       }
     }
@@ -723,6 +724,9 @@
     DeleteResourceInternal(it, style);
   }
 
+  if (external_use_client_)
+    external_use_client_->FlushQueuedReleases();
+
   for (size_t i : to_return_indices_unverified)
     unverified_sync_tokens.push_back(to_return[i].sync_token.GetData());
 
@@ -854,7 +858,7 @@
   // Use cached SkImage if possible.
   auto it = resource_provider_->resource_sk_images_.find(resource_id);
   if (it != resource_provider_->resource_sk_images_.end()) {
-    sk_image_ = it->second.image;
+    sk_image_ = it->second;
     return;
   }
 
@@ -873,7 +877,7 @@
         ResourceFormatToClosestSkColorType(!resource_provider->IsSoftware(),
                                            resource->transferable.format),
         kPremul_SkAlphaType, nullptr);
-    resource_provider_->resource_sk_images_[resource_id].image = sk_image_;
+    resource_provider_->resource_sk_images_[resource_id] = sk_image_;
     return;
   }
 
@@ -893,7 +897,7 @@
   resource_provider->PopulateSkBitmapWithResource(&sk_bitmap, resource);
   sk_bitmap.setImmutable();
   sk_image_ = SkImage::MakeFromBitmap(sk_bitmap);
-  resource_provider_->resource_sk_images_[resource_id].image = sk_image_;
+  resource_provider_->resource_sk_images_[resource_id] = sk_image_;
 }
 
 DisplayResourceProvider::ScopedReadLockSkImage::~ScopedReadLockSkImage() {
@@ -902,11 +906,11 @@
 
 DisplayResourceProvider::LockSetForExternalUse::LockSetForExternalUse(
     DisplayResourceProvider* resource_provider,
-    const CreateSkImageCallback& create_callback,
-    const DestroySkImageCallback& destroy_callback)
-    : resource_provider_(resource_provider),
-      create_sk_image_callback_(create_callback),
-      destroy_sk_image_callback_(destroy_callback) {}
+    SkiaOutputSurface* client)
+    : resource_provider_(resource_provider) {
+  DCHECK(!resource_provider_->external_use_client_);
+  resource_provider_->external_use_client_ = client;
+}
 
 DisplayResourceProvider::LockSetForExternalUse::~LockSetForExternalUse() {
   DCHECK(resources_.empty());
@@ -924,12 +928,12 @@
     ResourceId id) {
   auto metadata = LockResource(id);
   auto& resource_sk_image = resource_provider_->resource_sk_images_[id];
-  if (!resource_sk_image.image) {
-    resource_sk_image.image =
-        create_sk_image_callback_.Run(std::move(metadata));
-    resource_sk_image.destroy_callback = destroy_sk_image_callback_;
+  if (!resource_sk_image) {
+    resource_sk_image =
+        resource_provider_->external_use_client_->MakePromiseSkImage(
+            std::move(metadata));
   }
-  return resource_sk_image.image;
+  return resource_sk_image;
 }
 
 void DisplayResourceProvider::LockSetForExternalUse::UnlockResources(
@@ -997,11 +1001,6 @@
     default;
 DisplayResourceProvider::ChildResource::~ChildResource() = default;
 
-DisplayResourceProvider::ResourceSkImage::ResourceSkImage() = default;
-DisplayResourceProvider::ResourceSkImage::ResourceSkImage(
-    const ResourceSkImage&) = default;
-DisplayResourceProvider::ResourceSkImage::~ResourceSkImage() = default;
-
 void DisplayResourceProvider::ChildResource::SetLocallyUsed() {
   synchronization_state_ = LOCALLY_USED;
   sync_token_.Clear();
diff --git a/components/viz/service/display/display_resource_provider.h b/components/viz/service/display/display_resource_provider.h
index 6a940c881..310e066 100644
--- a/components/viz/service/display/display_resource_provider.h
+++ b/components/viz/service/display/display_resource_provider.h
@@ -43,6 +43,7 @@
 namespace viz {
 class ContextProvider;
 class SharedBitmapManager;
+class SkiaOutputSurface;
 
 // This class provides abstractions for receiving and using resources from other
 // modules/threads/processes. It abstracts away GL textures vs GpuMemoryBuffers
@@ -185,25 +186,21 @@
     DISALLOW_COPY_AND_ASSIGN(ScopedReadLockSkImage);
   };
 
-  // Maintains set of lock for external use.
+  // Maintains set of resources locked for external use by SkiaRenderer.
   class VIZ_SERVICE_EXPORT LockSetForExternalUse {
    public:
-    using CreateSkImageCallback =
-        base::RepeatingCallback<sk_sp<SkImage>(ResourceMetadata)>;
-    // TODO(https://crbug.com/922595): Remove SyncToken once we always use
-    // SharedImage and can rely on SharedImage ref-counting.
-    using DestroySkImageCallback =
-        base::RepeatingCallback<gpu::SyncToken(sk_sp<SkImage>&&)>;
+    // There should be at most one instance of this class per
+    // |resource_provider|. Both |resource_provider| and |client| outlive this
+    // class.
     LockSetForExternalUse(DisplayResourceProvider* resource_provider,
-                          const CreateSkImageCallback& create_callback,
-                          const DestroySkImageCallback& destroy_callback);
+                          SkiaOutputSurface* client);
     ~LockSetForExternalUse();
 
     // Lock a resource for external use.
     ResourceMetadata LockResource(ResourceId resource_id);
 
-    // Lock a resource and create a SkImage from it by using the
-    // CreateSkImageCallback.
+    // Lock a resource and create a SkImage from it by using
+    // Client::CreateImage.
     sk_sp<SkImage> LockResourceAndCreateSkImage(ResourceId resource_id);
 
     // Unlock all locked resources with a |sync_token|.
@@ -213,8 +210,6 @@
 
    private:
     DisplayResourceProvider* const resource_provider_;
-    CreateSkImageCallback create_sk_image_callback_;
-    DestroySkImageCallback destroy_sk_image_callback_;
     std::vector<ResourceId> resources_;
 
     DISALLOW_COPY_AND_ASSIGN(LockSetForExternalUse);
@@ -483,16 +478,10 @@
 
   ResourceMap resources_;
   ChildMap children_;
-  struct ResourceSkImage {
-    ResourceSkImage();
-    ResourceSkImage(const ResourceSkImage&);
-    ~ResourceSkImage();
+  base::flat_map<ResourceId, sk_sp<SkImage>> resource_sk_images_;
+  // If set, all |resource_sk_images_| were created with this client.
+  SkiaOutputSurface* external_use_client_ = nullptr;
 
-    sk_sp<SkImage> image;
-    base::Optional<LockSetForExternalUse::DestroySkImageCallback>
-        destroy_callback;
-  };
-  base::flat_map<ResourceId, ResourceSkImage> resource_sk_images_;
   base::flat_map<int, std::vector<ResourceId>> batched_returning_resources_;
   scoped_refptr<ResourceFence> current_read_lock_fence_;
   // Keep track of whether deleted resources should be batched up or returned
diff --git a/components/viz/service/display/display_resource_provider_unittest.cc b/components/viz/service/display/display_resource_provider_unittest.cc
index 82480d7..9cd37ea 100644
--- a/components/viz/service/display/display_resource_provider_unittest.cc
+++ b/components/viz/service/display/display_resource_provider_unittest.cc
@@ -479,9 +479,7 @@
   unsigned parent_id = resource_map[list.front().id];
 
   DisplayResourceProvider::LockSetForExternalUse lock_set(
-      resource_provider_.get(),
-      DisplayResourceProvider::LockSetForExternalUse::CreateSkImageCallback(),
-      DisplayResourceProvider::LockSetForExternalUse::DestroySkImageCallback());
+      resource_provider_.get(), /*client=*/nullptr);
 
   ResourceMetadata metadata = lock_set.LockResource(parent_id);
   ASSERT_EQ(metadata.mailbox_holder.mailbox, mailbox);
diff --git a/components/viz/service/display/skia_output_surface.h b/components/viz/service/display/skia_output_surface.h
index 1f46a73..3e203932 100644
--- a/components/viz/service/display/skia_output_surface.h
+++ b/components/viz/service/display/skia_output_surface.h
@@ -59,9 +59,13 @@
       SkYUVColorSpace yuv_color_space,
       bool has_alpha) = 0;
 
-  // Release SkImage created by MakePromiseSkImage.  |image| may or may not
-  // have been fulfilled.
-  virtual gpu::SyncToken DestroySkImage(sk_sp<SkImage>&& image) = 0;
+  // Release SkImage created by MakePromiseSkImage on the thread on which
+  // it was fulfilled. SyncToken represents point after which SkImage is
+  // released.
+  virtual gpu::SyncToken QueueReleasePromiseSkImage(sk_sp<SkImage>&& image) = 0;
+
+  // Flush all the queued releases. No-op if none were queued.
+  virtual void FlushQueuedReleases() = 0;
 
   // Swaps the current backbuffer to the screen.
   virtual void SkiaSwapBuffers(OutputSurfaceFrame frame) = 0;
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index bca9aca..54e1614 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -212,12 +212,8 @@
     }
     case DrawMode::DDL: {
       DCHECK(skia_output_surface_);
-      lock_set_for_external_use_.emplace(
-          resource_provider,
-          base::BindRepeating(&SkiaOutputSurface::MakePromiseSkImage,
-                              base::Unretained(skia_output_surface_)),
-          base::BindRepeating(&SkiaOutputSurface::DestroySkImage,
-                              base::Unretained(skia_output_surface_)));
+      lock_set_for_external_use_.emplace(resource_provider,
+                                         skia_output_surface);
       break;
     }
     case DrawMode::SKPRECORD: {
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.cc b/components/viz/service/display_embedder/skia_output_surface_impl.cc
index e2d5e25..c9aaf93 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -455,21 +455,31 @@
       this, yuv_color_space, std::move(metadatas), has_alpha);
 }
 
-gpu::SyncToken SkiaOutputSurfaceImpl::DestroySkImage(sk_sp<SkImage>&& image) {
+gpu::SyncToken SkiaOutputSurfaceImpl::QueueReleasePromiseSkImage(
+    sk_sp<SkImage>&& image) {
+  images_pending_release_.emplace_back(std::move(image));
+
   gpu::SyncToken sync_token(gpu::CommandBufferNamespace::VIZ_OUTPUT_SURFACE,
                             impl_on_gpu_->command_buffer_id(),
                             ++sync_fence_release_);
   sync_token.SetVerifyFlush();
+  return sync_token;
+}
+
+void SkiaOutputSurfaceImpl::FlushQueuedReleases() {
+  if (images_pending_release_.empty())
+    return;
 
   auto sequence_id = gpu_service_->skia_output_surface_sequence_id();
   // impl_on_gpu_ is released on the GPU thread by a posted task from
   // SkiaOutputSurfaceImpl::dtor. So it is safe to use base::Unretained.
-  auto callback = base::BindOnce(&SkiaOutputSurfaceImplOnGpu::DestroySkImage,
-                                 base::Unretained(impl_on_gpu_.get()),
-                                 std::move(image), sync_fence_release_);
+  auto callback =
+      base::BindOnce(&SkiaOutputSurfaceImplOnGpu::DestroySkImages,
+                     base::Unretained(impl_on_gpu_.get()),
+                     std::move(images_pending_release_), sync_fence_release_);
+  images_pending_release_.clear();
   gpu_service_->scheduler()->ScheduleTask(gpu::Scheduler::Task(
       sequence_id, std::move(callback), std::vector<gpu::SyncToken>()));
-  return sync_token;
 }
 
 void SkiaOutputSurfaceImpl::SkiaSwapBuffers(OutputSurfaceFrame frame) {
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.h b/components/viz/service/display_embedder/skia_output_surface_impl.h
index 0355077..5e95e7e 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.h
@@ -74,22 +74,24 @@
 
   // SkiaOutputSurface implementation:
   SkCanvas* BeginPaintCurrentFrame() override;
-  sk_sp<SkImage> MakePromiseSkImage(ResourceMetadata metadata) override;
   sk_sp<SkImage> MakePromiseSkImageFromYUV(
       std::vector<ResourceMetadata> metadatas,
       SkYUVColorSpace yuv_color_space,
       bool has_alpha) override;
-  gpu::SyncToken DestroySkImage(sk_sp<SkImage>&& image) override;
   void SkiaSwapBuffers(OutputSurfaceFrame frame) override;
   SkCanvas* BeginPaintRenderPass(const RenderPassId& id,
                                  const gfx::Size& surface_size,
                                  ResourceFormat format,
                                  bool mipmap) override;
   gpu::SyncToken SubmitPaint() override;
+  sk_sp<SkImage> MakePromiseSkImage(ResourceMetadata metadata) override;
   sk_sp<SkImage> MakePromiseSkImageFromRenderPass(const RenderPassId& id,
                                                   const gfx::Size& size,
                                                   ResourceFormat format,
                                                   bool mipmap) override;
+  gpu::SyncToken QueueReleasePromiseSkImage(sk_sp<SkImage>&& image) override;
+  void FlushQueuedReleases() override;
+
   void RemoveRenderPassResource(std::vector<RenderPassId> ids) override;
   void CopyOutput(RenderPassId id,
                   const gfx::Rect& copy_rect,
@@ -154,6 +156,8 @@
   // Observers for context lost.
   base::ObserverList<ContextLostObserver>::Unchecked observers_;
 
+  std::vector<sk_sp<SkImage>> images_pending_release_;
+
   THREAD_CHECKER(thread_checker_);
 
   base::WeakPtr<SkiaOutputSurfaceImpl> weak_ptr_;
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
index 4d46da15..702069a9 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
@@ -509,10 +509,11 @@
   return gr_context()->threadSafeProxy();
 }
 
-void SkiaOutputSurfaceImplOnGpu::DestroySkImage(sk_sp<SkImage>&& image,
-                                                uint64_t sync_fence_release) {
+void SkiaOutputSurfaceImplOnGpu::DestroySkImages(
+    std::vector<sk_sp<SkImage>>&& images,
+    uint64_t sync_fence_release) {
   MakeCurrent();
-  image.reset();
+  images.clear();
   sync_point_client_state_->ReleaseFenceSync(sync_fence_release);
 }
 
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
index a695636..9e875cf7 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
@@ -124,7 +124,8 @@
   sk_sp<GrContextThreadSafeProxy> GetGrContextThreadSafeProxy();
   const gl::GLVersionInfo* gl_version_info() const { return gl_version_info_; }
 
-  void DestroySkImage(sk_sp<SkImage>&& image, uint64_t sync_fence_release);
+  void DestroySkImages(std::vector<sk_sp<SkImage>>&& images,
+                       uint64_t sync_fence_release);
 
  private:
 // gpu::ImageTransportSurfaceDelegate implementation:
diff --git a/content/browser/appcache/appcache_subresource_url_factory.cc b/content/browser/appcache/appcache_subresource_url_factory.cc
index 7a7ecf0f..b4d0722 100644
--- a/content/browser/appcache/appcache_subresource_url_factory.cc
+++ b/content/browser/appcache/appcache_subresource_url_factory.cc
@@ -4,9 +4,6 @@
 
 #include "content/browser/appcache/appcache_subresource_url_factory.h"
 
-#include <memory>
-#include <utility>
-
 #include "base/bind.h"
 #include "base/logging.h"
 #include "content/browser/appcache/appcache_host.h"
@@ -14,12 +11,9 @@
 #include "content/browser/appcache/appcache_url_loader_job.h"
 #include "content/browser/appcache/appcache_url_loader_request.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/browser/content_browser_client.h"
-#include "content/public/common/content_client.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "mojo/public/cpp/bindings/interface_ptr.h"
-#include "mojo/public/cpp/bindings/message.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "net/url_request/url_request.h"
 #include "services/network/public/cpp/resource_request.h"
@@ -364,21 +358,6 @@
     network::mojom::URLLoaderClientPtr client,
     const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  if (request.request_initiator.has_value() &&
-      !request.request_initiator.value().opaque() &&
-      request.request_initiator.value() != appcache_host_->origin_in_use()) {
-    const char* scheme_exception =
-        GetContentClient()
-            ->browser()
-            ->GetInitiatorSchemeBypassingDocumentBlocking();
-    if (!scheme_exception ||
-        request.request_initiator.value().scheme() != scheme_exception) {
-      mojo::ReportBadMessage(
-          "APP_CACHE_SUBRESOURCE_URL_FACTORY_INVALID_INITIATOR");
-      return;
-    }
-  }
-
   new SubresourceLoader(std::move(url_loader_request), routing_id, request_id,
                         options, request, std::move(client), traffic_annotation,
                         appcache_host_, network_loader_factory_);
diff --git a/content/browser/loader/cross_site_document_blocking_browsertest.cc b/content/browser/loader/cross_site_document_blocking_browsertest.cc
index 05e6cd4..08b603ee 100644
--- a/content/browser/loader/cross_site_document_blocking_browsertest.cc
+++ b/content/browser/loader/cross_site_document_blocking_browsertest.cc
@@ -36,7 +36,6 @@
 #include "content/public/test/url_loader_interceptor.h"
 #include "content/shell/browser/shell.h"
 #include "content/test/test_content_browser_client.h"
-#include "mojo/public/cpp/test_support/test_utils.h"
 #include "net/test/embedded_test_server/controllable_http_response.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "services/network/cross_origin_read_blocking.h"
@@ -107,6 +106,11 @@
     is_restricted_uma_expected = true;
     FetchHistogramsFromChildProcesses();
 
+    // TODO(lukasza): https://crbug.com/910287: Remove the special case below
+    // after ensuring that |request_initiator| coming through AppCache is
+    // trustworthy (today kBrowserProcess will be reported in
+    // NetworkService.URLLoader.RequestInitiatorOriginLockCompatibility UMA when
+    // AppCache is relaying renderer requests through a browser process).
     auto expected_lock_compatibility =
         special_request_initiator_origin_lock_check_for_appcache
             ? network::InitiatorLockCompatibility::kBrowserProcess
@@ -316,10 +320,6 @@
     }
   }
 
-  void InjectRequestInitiator(const url::Origin& request_initiator) {
-    request_initiator_to_inject_ = request_initiator;
-  }
-
  private:
   bool InterceptorCallback(URLLoaderInterceptor::RequestParams* params) {
     DCHECK_CURRENTLY_ON(BrowserThread::IO);
@@ -333,10 +333,6 @@
       return false;
     request_intercepted_ = true;
 
-    // Modify |params| if requested.
-    if (request_initiator_to_inject_.has_value())
-      params->url_request.request_initiator = request_initiator_to_inject_;
-
     // Inject |test_client_| into the request.
     DCHECK(!original_client_);
     original_client_ = std::move(params->client);
@@ -383,8 +379,6 @@
   const GURL url_to_intercept_;
   URLLoaderInterceptor interceptor_;
 
-  base::Optional<url::Origin> request_initiator_to_inject_;
-
   // |test_client_ptr_info_| below is used to transition results of
   // |test_client_.CreateInterfacePtr()| into IO thread.
   network::mojom::URLLoaderClientPtrInfo test_client_ptr_info_;
@@ -773,59 +767,6 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_P(CrossSiteDocumentBlockingTest,
-                       AppCache_InitiatorEnforcement) {
-  embedded_test_server()->StartAcceptingConnections();
-
-  // Verification of |request_initiator| is only done in the NetworkService code
-  // path.
-  if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
-    return;
-
-  // Prepare to intercept the network request at the IPC layer.
-  // in a way, that injects |spoofed_initiator| (simulating a compromised
-  // renderer that pretends to be making the request on behalf of another
-  // origin).
-  //
-  // Note that RequestInterceptor has to be constructed before the
-  // RenderFrameHostImpl is created.
-  GURL cross_site_url("http://cross-origin.com/site_isolation/nosniff.json");
-  RequestInterceptor interceptor(cross_site_url);
-  url::Origin spoofed_initiator =
-      url::Origin::Create(GURL("https://victim.example.com"));
-  interceptor.InjectRequestInitiator(spoofed_initiator);
-
-  // Load the main page twice. The second navigation should have AppCache
-  // initialized for the page.
-  GURL main_url = embedded_test_server()->GetURL(
-      "/appcache/simple_page_with_manifest.html");
-  EXPECT_TRUE(NavigateToURL(shell(), main_url));
-  base::string16 expected_title = base::ASCIIToUTF16("AppCache updated");
-  content::TitleWatcher title_watcher(shell()->web_contents(), expected_title);
-  EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
-  EXPECT_TRUE(NavigateToURL(shell(), main_url));
-
-  // Trigger an AppCache request with an incorrect |request_initiator| and
-  // verify that this will terminate the renderer process.
-  //
-  // Note that during the test, no renderer processes will be actually
-  // terminated, because the malicious/invalid message originates from within
-  // the test process (i.e. from URLLoaderInterceptor::Interceptor's
-  // CreateLoaderAndStart method which forwards the
-  // InjectRequestInitiator-modified request into
-  // AppCacheSubresourceURLFactory).  This necessitates testing via
-  // mojo::test::BadMessageObserver rather than via RenderProcessHostWatcher or
-  // RenderProcessHostKillWaiter.
-  mojo::test::BadMessageObserver bad_message_observer;
-  const char kScriptTemplate[] = R"(
-      var img = document.createElement('img');
-      img.src = $1;
-      document.body.appendChild(img); )";
-  EXPECT_TRUE(ExecJs(shell(), JsReplace(kScriptTemplate, cross_site_url)));
-  EXPECT_EQ("APP_CACHE_SUBRESOURCE_URL_FACTORY_INVALID_INITIATOR",
-            bad_message_observer.WaitForBadMessage());
-}
-
 IN_PROC_BROWSER_TEST_P(CrossSiteDocumentBlockingTest, PrefetchIsNotImpacted) {
   // Prepare for intercepting the resource request for testing prefetching.
   const char* kPrefetchResourcePath = "/prefetch-test";
diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc
index 14bfd604d..8a67808f 100644
--- a/content/browser/loader/resource_dispatcher_host_impl.cc
+++ b/content/browser/loader/resource_dispatcher_host_impl.cc
@@ -924,20 +924,11 @@
     report_raw_headers = false;
   }
 
-  // Do not report raw headers if the request's site needs to be isolated
-  // from the current process.
-  if (report_raw_headers) {
-    bool is_isolated =
-        SiteIsolationPolicy::UseDedicatedProcessesForAllSites() ||
-        // TODO(alexmos): get BrowsingInstance ID from the child_id's
-        // SecurityState.  One way is to expose a version of IsIsolatedOrigin
-        // that can take child_id instead of IsolationContext, and look up
-        // the BrowsingInstance ID internally in ChildProcessSecurityPolicy.
-        policy->IsIsolatedOrigin(IsolationContext(),
-                                 url::Origin::Create(request_data.url));
-    if (is_isolated &&
-        !policy->CanAccessDataForOrigin(child_id, request_data.url))
-      report_raw_headers = false;
+  // Do not report raw headers if the current process isn't permitted to access
+  // data for the request's site.
+  if (report_raw_headers &&
+      !policy->CanAccessDataForOrigin(child_id, request_data.url)) {
+    report_raw_headers = false;
   }
 
   if (DoNotPromptForLogin(static_cast<ResourceType>(request_data.resource_type),
diff --git a/content/browser/media/session/media_session_impl.cc b/content/browser/media/session/media_session_impl.cc
index 74a0e1c..8ec2242 100644
--- a/content/browser/media/session/media_session_impl.cc
+++ b/content/browser/media/session/media_session_impl.cc
@@ -180,6 +180,8 @@
   RenderFrameHost* rfh = navigation_handle->GetRenderFrameHost();
   if (services_.count(rfh))
     services_[rfh]->DidFinishNavigation();
+
+  NotifyMediaSessionMetadataChange();
 }
 
 void MediaSessionImpl::OnWebContentsFocused(
@@ -209,8 +211,9 @@
   observer->MediaSessionStateChanged(IsControllable(), IsActuallyPaused());
 }
 
-void MediaSessionImpl::NotifyMediaSessionMetadataChange(
-    const base::Optional<media_session::MediaMetadata>& metadata) {
+void MediaSessionImpl::NotifyMediaSessionMetadataChange() {
+  const media_session::MediaMetadata& metadata = GetMediaMetadata();
+
   for (auto& observer : observers_)
     observer.MediaSessionMetadataChanged(metadata);
 
@@ -771,8 +774,7 @@
 void MediaSessionImpl::AddObserver(
     media_session::mojom::MediaSessionObserverPtr observer) {
   observer->MediaSessionInfoChanged(GetMediaSessionInfoSync());
-  observer->MediaSessionMetadataChanged(
-      routed_service_ ? routed_service_->metadata() : base::nullopt);
+  observer->MediaSessionMetadataChanged(GetMediaMetadata());
 
   if (routed_service_) {
     std::vector<media_session::mojom::MediaSessionAction> actions(
@@ -928,6 +930,18 @@
   return true;
 }
 
+media_session::MediaMetadata MediaSessionImpl::GetMediaMetadata() const {
+  media_session::MediaMetadata metadata =
+      (routed_service_ && routed_service_->metadata())
+          ? *routed_service_->metadata()
+          : media_session::MediaMetadata();
+
+  metadata.source_title = base::ASCIIToUTF16(
+      web_contents()->GetLastCommittedURL().GetOrigin().host());
+
+  return metadata;
+}
+
 // MediaSessionService-related methods
 
 void MediaSessionImpl::OnServiceCreated(MediaSessionServiceImpl* service) {
@@ -961,7 +975,7 @@
   if (service != routed_service_)
     return;
 
-  NotifyMediaSessionMetadataChange(routed_service_->metadata());
+  NotifyMediaSessionMetadataChange();
 }
 
 void MediaSessionImpl::OnMediaSessionActionsChanged(
@@ -1025,10 +1039,11 @@
     return;
 
   routed_service_ = new_service;
-  if (routed_service_) {
-    NotifyMediaSessionMetadataChange(routed_service_->metadata());
+
+  if (routed_service_)
     RebuildAndNotifyActionsChanged();
-  }
+
+  NotifyMediaSessionMetadataChange();
 }
 
 MediaSessionServiceImpl* MediaSessionImpl::ComputeServiceForRouting() {
diff --git a/content/browser/media/session/media_session_impl.h b/content/browser/media/session/media_session_impl.h
index 1fd2b202..11fd7576 100644
--- a/content/browser/media/session/media_session_impl.h
+++ b/content/browser/media/session/media_session_impl.h
@@ -93,8 +93,7 @@
   }
 #endif  // defined(OS_ANDROID)
 
-  void NotifyMediaSessionMetadataChange(
-      const base::Optional<media_session::MediaMetadata>& metadata);
+  void NotifyMediaSessionMetadataChange();
 
   // Adds the given player to the current media session. Returns whether the
   // player was successfully added. If it returns false, AddPlayer() should be
@@ -336,6 +335,9 @@
   CONTENT_EXPORT bool AddOneShotPlayer(MediaSessionPlayerObserver* observer,
                                        int player_id);
 
+  // Returns the current media metadata associated with this session.
+  media_session::MediaMetadata GetMediaMetadata() const;
+
   // MediaSessionService-related methods
 
   // Called when the routed service may have changed.
diff --git a/content/browser/media/session/media_session_impl_browsertest.cc b/content/browser/media/session/media_session_impl_browsertest.cc
index 7faecb7..99148cd 100644
--- a/content/browser/media/session/media_session_impl_browsertest.cc
+++ b/content/browser/media/session/media_session_impl_browsertest.cc
@@ -235,6 +235,11 @@
 
   bool IsDucking() const { return media_session_->is_ducking_; }
 
+  base::string16 GetExpectedSourceTitle() {
+    return base::ASCIIToUTF16(
+        shell()->web_contents()->GetLastCommittedURL().GetOrigin().host());
+  }
+
  protected:
   MediaSessionImpl* media_session_;
   std::unique_ptr<content::MockMediaSessionObserver>
@@ -1853,7 +1858,10 @@
 IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        AddingMojoObserverNotifiesCurrentInformation_EmptyInfo) {
   media_session::test::MockMediaSessionMojoObserver observer(*media_session_);
-  EXPECT_FALSE(observer.WaitForMetadata());
+
+  media_session::MediaMetadata expected_metadata;
+  expected_metadata.source_title = GetExpectedSourceTitle();
+  EXPECT_EQ(expected_metadata, observer.WaitForMetadata());
 }
 
 IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
@@ -1875,6 +1883,8 @@
     media_session::test::MockMediaSessionMojoObserver observer(*media_session_);
     StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
     ResolveAudioFocusSuccess();
+
+    metadata.source_title = GetExpectedSourceTitle();
     EXPECT_EQ(metadata, observer.WaitForNonEmptyMetadata());
   }
 }
diff --git a/content/browser/media/session/media_session_impl_service_routing_unittest.cc b/content/browser/media/session/media_session_impl_service_routing_unittest.cc
index c4a99ab..103ba3c 100644
--- a/content/browser/media/session/media_session_impl_service_routing_unittest.cc
+++ b/content/browser/media/session/media_session_impl_service_routing_unittest.cc
@@ -131,6 +131,11 @@
         ->RemovePlayer(players_[frame].get(), kPlayerId);
   }
 
+  base::string16 GetExpectedSourceTitle() {
+    return base::ASCIIToUTF16(
+        contents()->GetLastCommittedURL().GetOrigin().host());
+  }
+
   MockMediaSessionPlayerObserver* GetPlayerForFrame(
       TestRenderFrameHost* frame) {
     auto iter = players_.find(frame);
@@ -279,13 +284,17 @@
 
 TEST_F(MediaSessionImplServiceRoutingTest,
        NotifyMetadataAndActionsChangeWhenControllable) {
+  media_session::MediaMetadata empty_metadata;
+  empty_metadata.source_title = GetExpectedSourceTitle();
+
   media_session::MediaMetadata expected_metadata;
   expected_metadata.title = base::ASCIIToUTF16("title");
   expected_metadata.artist = base::ASCIIToUTF16("artist");
   expected_metadata.album = base::ASCIIToUTF16("album");
+  expected_metadata.source_title = GetExpectedSourceTitle();
 
   EXPECT_CALL(*mock_media_session_observer(),
-              MediaSessionMetadataChanged(Eq(base::nullopt)))
+              MediaSessionMetadataChanged(Eq(empty_metadata)))
       .Times(AnyNumber());
   EXPECT_CALL(*mock_media_session_observer(),
               MediaSessionActionsChanged(Eq(default_actions())))
@@ -312,6 +321,7 @@
   expected_metadata.title = base::ASCIIToUTF16("title");
   expected_metadata.artist = base::ASCIIToUTF16("artist");
   expected_metadata.album = base::ASCIIToUTF16("album");
+  expected_metadata.source_title = GetExpectedSourceTitle();
 
   EXPECT_CALL(*mock_media_session_observer(),
               MediaSessionMetadataChanged(Eq(expected_metadata)))
@@ -330,11 +340,15 @@
 }
 
 TEST_F(MediaSessionImplServiceRoutingTest,
-       DontNotifyMetadataAndActionsChangeWhenTurningUncontrollable) {
+       NotifyMetadataAndNotActionsChangeWhenTurningUncontrollable) {
   media_session::MediaMetadata expected_metadata;
   expected_metadata.title = base::ASCIIToUTF16("title");
   expected_metadata.artist = base::ASCIIToUTF16("artist");
   expected_metadata.album = base::ASCIIToUTF16("album");
+  expected_metadata.source_title = GetExpectedSourceTitle();
+
+  media_session::MediaMetadata empty_metadata;
+  empty_metadata.source_title = GetExpectedSourceTitle();
 
   std::set<MediaSessionAction> empty_actions;
   std::set<MediaSessionAction> expected_actions;
@@ -345,8 +359,7 @@
   EXPECT_CALL(*mock_media_session_observer(), MediaSessionActionsChanged(_))
       .Times(AnyNumber());
   EXPECT_CALL(*mock_media_session_observer(),
-              MediaSessionMetadataChanged(Eq(base::nullopt)))
-      .Times(0);
+              MediaSessionMetadataChanged(Eq(empty_metadata)));
   EXPECT_CALL(*mock_media_session_observer(),
               MediaSessionActionsChanged(Eq(empty_actions)))
       .Times(0);
@@ -530,6 +543,7 @@
   expected_metadata.title = base::ASCIIToUTF16("title");
   expected_metadata.artist = base::ASCIIToUTF16("artist");
   expected_metadata.album = base::ASCIIToUTF16("album");
+  expected_metadata.source_title = GetExpectedSourceTitle();
 
   CreateServiceForFrame(main_frame_);
   StartPlayerForFrame(main_frame_);
@@ -547,15 +561,17 @@
   CreateServiceForFrame(main_frame_);
   StartPlayerForFrame(main_frame_);
 
+  media_session::MediaMetadata expected_metadata;
+  expected_metadata.source_title = GetExpectedSourceTitle();
+
   {
     media_session::test::MockMediaSessionMojoObserver observer(
         *GetMediaSession());
     services_[main_frame_]->SetMetadata(base::nullopt);
 
-    // When the session becomes controllable we should receive empty metadata
-    // because we have not set any. The |is_controllable| boolean will also
-    // become true.
-    EXPECT_FALSE(observer.WaitForMetadata());
+    // When the session becomes controllable we should receive default
+    // metadata. The |is_controllable| boolean will also become true.
+    EXPECT_EQ(expected_metadata, observer.WaitForMetadata());
     EXPECT_TRUE(observer.session_info()->is_controllable);
   }
 }
@@ -644,4 +660,14 @@
   EXPECT_EQ(expected_actions, observer.actions_set());
 }
 
+TEST_F(MediaSessionImplServiceRoutingTest, NotifyMojoObserverOnNavigation) {
+  media_session::test::MockMediaSessionMojoObserver observer(
+      *GetMediaSession());
+  contents()->NavigateAndCommit(GURL("http://www.google.com"));
+
+  media_session::MediaMetadata expected_metadata;
+  expected_metadata.source_title = base::ASCIIToUTF16("www.google.com");
+  EXPECT_EQ(expected_metadata, observer.WaitForNonEmptyMetadata());
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/input/mouse_wheel_phase_handler.cc b/content/browser/renderer_host/input/mouse_wheel_phase_handler.cc
index 6056b1f..a326261 100644
--- a/content/browser/renderer_host/input/mouse_wheel_phase_handler.cc
+++ b/content/browser/renderer_host/input/mouse_wheel_phase_handler.cc
@@ -120,7 +120,8 @@
   touchpad_scroll_phase_state_ = TOUCHPAD_SCROLL_STATE_UNKNOWN;
 }
 
-void MouseWheelPhaseHandler::SendWheelEndForTouchpadScrollingIfNeeded() {
+void MouseWheelPhaseHandler::SendWheelEndForTouchpadScrollingIfNeeded(
+    bool should_route_event) {
   if (touchpad_scroll_phase_state_ == TOUCHPAD_SCROLL_IN_PROGRESS) {
     RenderWidgetHostImpl* widget_host = host_view_->host();
     if (!widget_host) {
@@ -128,8 +129,6 @@
       return;
     }
 
-    bool should_route_event = widget_host->delegate() &&
-                              widget_host->delegate()->GetInputEventRouter();
     TRACE_EVENT_INSTANT0("input", "MouseWheelPhaseHandler Sent touchpad end",
                          TRACE_EVENT_SCOPE_THREAD);
     SendSyntheticWheelEventWithPhaseEnded(should_route_event);
diff --git a/content/browser/renderer_host/input/mouse_wheel_phase_handler.h b/content/browser/renderer_host/input/mouse_wheel_phase_handler.h
index 305095f..fc252b6 100644
--- a/content/browser/renderer_host/input/mouse_wheel_phase_handler.h
+++ b/content/browser/renderer_host/input/mouse_wheel_phase_handler.h
@@ -66,7 +66,7 @@
   void DispatchPendingWheelEndEvent();
   void IgnorePendingWheelEndEvent();
   void ResetTouchpadScrollSequence();
-  void SendWheelEndForTouchpadScrollingIfNeeded();
+  void SendWheelEndForTouchpadScrollingIfNeeded(bool should_route_event);
   void TouchpadScrollingMayBegin();
 
   // Used to set the timer timeout for testing.
diff --git a/content/browser/renderer_host/render_widget_host_view_event_handler.cc b/content/browser/renderer_host/render_widget_host_view_event_handler.cc
index 1191efc..c362e6b 100644
--- a/content/browser/renderer_host/render_widget_host_view_event_handler.cc
+++ b/content/browser/renderer_host/render_widget_host_view_event_handler.cc
@@ -120,7 +120,6 @@
     RenderWidgetHostViewBase* host_view,
     Delegate* delegate)
     : accept_return_character_(false),
-      disable_input_event_router_for_testing_(false),
       mouse_locked_(false),
       pinch_zoom_enabled_(content::IsPinchToZoomEnabled()),
       set_focus_on_mouse_down_or_key_event_(false),
@@ -358,10 +357,11 @@
         ui::MakeWebMouseWheelEvent(*event->AsMouseWheelEvent());
 
     if (mouse_wheel_event.delta_x != 0 || mouse_wheel_event.delta_y != 0) {
-      bool should_route_event = ShouldRouteEvent(event);
+      const bool should_route_event = ShouldRouteEvents();
       // End the touchpad scrolling sequence (if such exists) before handling
       // a ui::ET_MOUSEWHEEL event.
-      mouse_wheel_phase_handler_.SendWheelEndForTouchpadScrollingIfNeeded();
+      mouse_wheel_phase_handler_.SendWheelEndForTouchpadScrollingIfNeeded(
+          should_route_event);
 
       mouse_wheel_phase_handler_.AddPhaseIfNeededAndScheduleEndEvent(
           mouse_wheel_event, should_route_event);
@@ -383,7 +383,7 @@
 
       blink::WebMouseEvent mouse_event = ui::MakeWebMouseEvent(*event);
       ModifyEventMovementAndCoords(*event, &mouse_event);
-      if (ShouldRouteEvent(event)) {
+      if (ShouldRouteEvents()) {
         host_->delegate()->GetInputEventRouter()->RouteMouseEvent(
             host_view_, &mouse_event, *event->latency());
       } else {
@@ -415,7 +415,7 @@
 
 void RenderWidgetHostViewEventHandler::OnScrollEvent(ui::ScrollEvent* event) {
   TRACE_EVENT0("input", "RenderWidgetHostViewBase::OnScrollEvent");
-  bool should_route_event = ShouldRouteEvent(event);
+  const bool should_route_event = ShouldRouteEvents();
   if (event->type() == ui::ET_SCROLL) {
 #if !defined(OS_WIN)
     // TODO(ananta)
@@ -513,7 +513,7 @@
   // touchcancel to make sure only send one ack per WebTouchEvent.
   MarkUnchangedTouchPointsAsStationary(&touch_event,
                                        event->pointer_details().id);
-  if (ShouldRouteEvent(event)) {
+  if (ShouldRouteEvents()) {
     host_->delegate()->GetInputEventRouter()->RouteTouchEvent(
         host_view_, &touch_event, *event->latency());
   } else {
@@ -548,7 +548,7 @@
     blink::WebGestureEvent fling_cancel = gesture;
     fling_cancel.SetType(blink::WebInputEvent::kGestureFlingCancel);
     fling_cancel.SetSourceDevice(blink::kWebGestureDeviceTouchscreen);
-    if (ShouldRouteEvent(event)) {
+    if (ShouldRouteEvents()) {
       host_->delegate()->GetInputEventRouter()->RouteGestureEvent(
           host_view_, &fling_cancel,
           ui::LatencyInfo(ui::SourceEventType::TOUCH));
@@ -563,7 +563,8 @@
       // wheel based send a synthetic wheel event with kPhaseEnded to cancel
       // the current scroll.
       mouse_wheel_phase_handler_.DispatchPendingWheelEndEvent();
-      mouse_wheel_phase_handler_.SendWheelEndForTouchpadScrollingIfNeeded();
+      mouse_wheel_phase_handler_.SendWheelEndForTouchpadScrollingIfNeeded(
+          ShouldRouteEvents());
     } else if (event->type() == ui::ET_SCROLL_FLING_START) {
       RecordAction(base::UserMetricsAction("TouchscreenScrollFling"));
     }
@@ -578,7 +579,7 @@
       mouse_wheel_phase_handler_.ResetTouchpadScrollSequence();
     }
 
-    if (ShouldRouteEvent(event)) {
+    if (ShouldRouteEvents()) {
       host_->delegate()->GetInputEventRouter()->RouteGestureEvent(
           host_view_, &gesture, *event->latency());
     } else {
@@ -731,7 +732,7 @@
     blink::WebMouseWheelEvent mouse_wheel_event =
         ui::MakeWebMouseWheelEvent(*event->AsMouseWheelEvent());
     if (mouse_wheel_event.delta_x != 0 || mouse_wheel_event.delta_y != 0) {
-      if (ShouldRouteEvent(event)) {
+      if (ShouldRouteEvents()) {
         host_->delegate()->GetInputEventRouter()->RouteMouseWheelEvent(
             host_view_, &mouse_wheel_event, *event->latency());
       } else {
@@ -791,7 +792,7 @@
       // Forward event to renderer.
       if (CanRendererHandleEvent(event, mouse_locked_, is_selection_popup) &&
           !(event->flags() & ui::EF_FROM_TOUCH)) {
-        if (ShouldRouteEvent(event)) {
+        if (ShouldRouteEvents()) {
           host_->delegate()->GetInputEventRouter()->RouteMouseEvent(
               host_view_, &mouse_event, *event->latency());
         } else {
@@ -897,28 +898,17 @@
          global_mouse_position_.y() > rect.bottom() - border_y;
 }
 
-bool RenderWidgetHostViewEventHandler::ShouldRouteEvent(
-    const ui::Event* event) const {
-  // We should route an event in two cases:
-  // 1) Mouse events are routed only if cross-process frames are possible.
-  // 2) Touch events are always routed. In the absence of a BrowserPlugin
-  //    we expect the routing to always send the event to this view. If
-  //    one or more BrowserPlugins are present, then the event may be targeted
-  //    to one of them, or this view. This allows GuestViews to have access to
-  //    them while still forcing pinch-zoom to be handled by the top-level
-  //    frame. TODO(wjmaclean): At present, this doesn't work for OOPIF, but
-  //    it should be a simple extension to modify RenderWidgetHostViewChildFrame
-  //    in a similar manner to RenderWidgetHostViewGuest.
-  bool result = host_->delegate() && host_->delegate()->GetInputEventRouter() &&
-                !disable_input_event_router_for_testing_;
+bool RenderWidgetHostViewEventHandler::ShouldRouteEvents() const {
+  if (!host_->delegate())
+    return false;
 
   // Do not route events that are currently targeted to page popups such as
   // <select> element drop-downs, since these cannot contain cross-process
   // frames.
-  if (host_->delegate() && !host_->delegate()->IsWidgetForMainFrame(host_))
+  if (!host_->delegate()->IsWidgetForMainFrame(host_))
     return false;
 
-  return result;
+  return !!host_->delegate()->GetInputEventRouter();
 }
 
 void RenderWidgetHostViewEventHandler::ProcessMouseEvent(
diff --git a/content/browser/renderer_host/render_widget_host_view_event_handler.h b/content/browser/renderer_host/render_widget_host_view_event_handler.h
index 1a8d27b..63f2fcb 100644
--- a/content/browser/renderer_host/render_widget_host_view_event_handler.h
+++ b/content/browser/renderer_host/render_widget_host_view_event_handler.h
@@ -131,9 +131,6 @@
 #endif  // defined(OS_WIN)
 
   bool accept_return_character() { return accept_return_character_; }
-  void disable_input_event_router_for_testing() {
-    disable_input_event_router_for_testing_ = true;
-  }
   bool mouse_locked() { return mouse_locked_; }
   const ui::MotionEventAura& pointer_state() const { return pointer_state_; }
   void set_focus_on_mouse_down_or_key_event(
@@ -219,8 +216,9 @@
   // moved to center.
   bool ShouldMoveToCenter();
 
-  // Returns true when we can do SurfaceHitTesting for the event type.
-  bool ShouldRouteEvent(const ui::Event* event) const;
+  // Returns true when we can hit test input events with location data to be
+  // sent to the targeted RenderWidgetHost.
+  bool ShouldRouteEvents() const;
 
   // Directs events to the |host_|.
   void ProcessMouseEvent(const blink::WebMouseEvent& event,
@@ -236,11 +234,6 @@
   // Whether return characters should be passed on to the RenderWidgetHostImpl.
   bool accept_return_character_;
 
-  // Allows tests to send gesture events for testing without first sending a
-  // corresponding touch sequence, as would be required by
-  // RenderWidgetHostInputEventRouter.
-  bool disable_input_event_router_for_testing_;
-
   // Deactivates keyboard lock when destroyed.
   std::unique_ptr<aura::ScopedKeyboardHook> scoped_keyboard_hook_;
 
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 2714255..4c9994c 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.h
+++ b/content/browser/renderer_host/render_widget_host_view_mac.h
@@ -186,8 +186,9 @@
   const viz::FrameSinkId& GetFrameSinkId() const override;
   const viz::LocalSurfaceIdAllocation& GetLocalSurfaceIdAllocation()
       const override;
-  // Returns true when we can do SurfaceHitTesting for the event type.
-  bool ShouldRouteEvent(const blink::WebInputEvent& event) const;
+  // Returns true when we can hit test input events with location data to be
+  // sent to the targeted RenderWidgetHost.
+  bool ShouldRouteEvents() const;
   // This method checks |event| to see if a GesturePinch or double tap event
   // can be routed according to ShouldRouteEvent, and if not, sends it directly
   // to the view's RenderWidgetHost.
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 6a40bd5..7bdf6790 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -666,7 +666,7 @@
 
   ui::LatencyInfo latency_info(ui::SourceEventType::TOUCH);
 
-  if (ShouldRouteEvent(web_gesture)) {
+  if (ShouldRouteEvents()) {
     blink::WebGestureEvent gesture_event(web_gesture);
     host()->delegate()->GetInputEventRouter()->RouteGestureEvent(
         this, &gesture_event, latency_info);
@@ -1241,25 +1241,20 @@
   return browser_compositor_->GetDelegatedFrameHost()->frame_sink_id();
 }
 
-bool RenderWidgetHostViewMac::ShouldRouteEvent(
-    const WebInputEvent& event) const {
+bool RenderWidgetHostViewMac::ShouldRouteEvents() const {
   // Event routing requires a valid frame sink (that is, that we be connected to
   // a ui::Compositor), which is not guaranteed to be the case.
   // https://crbug.com/844095
   if (!browser_compositor_->GetRootFrameSinkId().is_valid())
     return false;
-  // See also RenderWidgetHostViewAura::ShouldRouteEvent.
-  // TODO(wjmaclean): Update this function if RenderWidgetHostViewMac implements
-  // OnTouchEvent(), to match what we are doing in RenderWidgetHostViewAura.
-  // The only touch events and touch gesture events expected here are
-  // injected synthetic events.
+
   return host()->delegate() && host()->delegate()->GetInputEventRouter();
 }
 
 void RenderWidgetHostViewMac::SendTouchpadZoomEvent(
     const WebGestureEvent* event) {
   DCHECK(event->IsTouchpadZoomEvent());
-  if (ShouldRouteEvent(*event)) {
+  if (ShouldRouteEvents()) {
     host()->delegate()->GetInputEventRouter()->RouteGestureEvent(
         this, event, ui::LatencyInfo(ui::SourceEventType::TOUCHPAD));
     return;
@@ -1275,7 +1270,7 @@
   if (!result.succeeded)
     return;
 
-  if (ShouldRouteEvent(event)) {
+  if (ShouldRouteEvents()) {
     WebTouchEvent touch_event(event);
     host()->delegate()->GetInputEventRouter()->RouteTouchEvent(
         this, &touch_event, latency_info);
@@ -1580,7 +1575,7 @@
   blink::WebMouseEvent web_event = const_web_event;
   ui::LatencyInfo latency_info(ui::SourceEventType::OTHER);
   latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT);
-  if (ShouldRouteEvent(web_event)) {
+  if (ShouldRouteEvents()) {
     host()->delegate()->GetInputEventRouter()->RouteMouseEvent(this, &web_event,
                                                                latency_info);
   } else {
@@ -1598,7 +1593,7 @@
 
   ui::LatencyInfo latency_info(ui::SourceEventType::OTHER);
   latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT);
-  if (ShouldRouteEvent(web_event)) {
+  if (ShouldRouteEvents()) {
     host()->delegate()->GetInputEventRouter()->RouteTouchEvent(this, &web_event,
                                                                latency_info);
   } else {
@@ -1612,14 +1607,14 @@
   ui::LatencyInfo latency_info(ui::SourceEventType::WHEEL);
   latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_UI_COMPONENT);
   mouse_wheel_phase_handler_.AddPhaseIfNeededAndScheduleEndEvent(
-      web_event, ShouldRouteEvent(web_event));
+      web_event, ShouldRouteEvents());
   if (web_event.phase == blink::WebMouseWheelEvent::kPhaseEnded) {
     // A wheel end event is scheduled and will get dispatched if momentum
     // phase doesn't start in 100ms. Don't sent the wheel end event
     // immediately.
     return;
   }
-  if (ShouldRouteEvent(web_event)) {
+  if (ShouldRouteEvents()) {
     host()->delegate()->GetInputEventRouter()->RouteMouseWheelEvent(
         this, &web_event, latency_info);
   } else {
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 1714e14..fe29b7f 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -259,6 +259,10 @@
       base::FeatureList::IsEnabled(features::kSecMetadata) ||
       enable_experimental_web_platform_features);
 
+  WebRuntimeFeatures::EnableUserActivationSameOriginVisibility(
+      base::FeatureList::IsEnabled(
+          features::kUserActivationSameOriginVisibility));
+
   WebRuntimeFeatures::EnableUserActivationV2(
       base::FeatureList::IsEnabled(features::kUserActivationV2));
 
diff --git a/content/renderer/media/stream/webmediaplayer_ms.cc b/content/renderer/media/stream/webmediaplayer_ms.cc
index 3e0f2da..e3041ad 100644
--- a/content/renderer/media/stream/webmediaplayer_ms.cc
+++ b/content/renderer/media/stream/webmediaplayer_ms.cc
@@ -1097,7 +1097,7 @@
       base::BindOnce(
           &WebMediaPlayerMSCompositor::EnableSubmission, compositor_,
           bridge_->GetSurfaceId(), bridge_->GetLocalSurfaceIdAllocationTime(),
-          video_rotation_, IsInPictureInPicture(), opaque_,
+          video_rotation_, IsInPictureInPicture(),
           media::BindToCurrentLoop(base::BindRepeating(
               &WebMediaPlayerMS::OnFrameSinkDestroyed, AsWeakPtr()))));
 
diff --git a/content/renderer/media/stream/webmediaplayer_ms_compositor.cc b/content/renderer/media/stream/webmediaplayer_ms_compositor.cc
index 64d6174..b74a226 100644
--- a/content/renderer/media/stream/webmediaplayer_ms_compositor.cc
+++ b/content/renderer/media/stream/webmediaplayer_ms_compositor.cc
@@ -215,7 +215,6 @@
     base::TimeTicks local_surface_id_allocation_time,
     media::VideoRotation rotation,
     bool force_submit,
-    bool is_opaque,
     blink::WebFrameSinkDestroyedCallback frame_sink_destroyed_callback) {
   DCHECK(video_frame_compositor_task_runner_->BelongsToCurrentThread());
 
@@ -227,7 +226,6 @@
 
   submitter_->SetRotation(rotation);
   submitter_->SetForceSubmit(force_submit);
-  submitter_->SetIsOpaque(is_opaque);
   submitter_->EnableSubmission(id, local_surface_id_allocation_time,
                                std::move(frame_sink_destroyed_callback));
   video_frame_provider_client_ = submitter_.get();
@@ -573,8 +571,6 @@
     main_task_runner_->PostTask(
         FROM_HERE, base::BindOnce(&WebMediaPlayerMS::OnOpacityChanged, player_,
                                   new_frame_is_opaque));
-    if (submitter_)
-      submitter_->SetIsOpaque(new_frame_is_opaque);
   }
   if (old_frame->natural_size() != new_frame->natural_size()) {
     main_task_runner_->PostTask(
diff --git a/content/renderer/media/stream/webmediaplayer_ms_compositor.h b/content/renderer/media/stream/webmediaplayer_ms_compositor.h
index a99484b..c3a01b6 100644
--- a/content/renderer/media/stream/webmediaplayer_ms_compositor.h
+++ b/content/renderer/media/stream/webmediaplayer_ms_compositor.h
@@ -90,7 +90,6 @@
       base::TimeTicks local_surface_id_allocation_time,
       media::VideoRotation rotation,
       bool force_submit,
-      bool is_opaque,
       blink::WebFrameSinkDestroyedCallback frame_sink_destroyed_callback);
 
   // Notifies the |submitter_| that the frames must be submitted.
diff --git a/content/renderer/media/stream/webmediaplayer_ms_unittest.cc b/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
index 3bd0681..128d2a2 100644
--- a/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
+++ b/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
@@ -427,7 +427,6 @@
   MOCK_METHOD0(StopRendering, void());
   MOCK_METHOD1(MockInitialize, void(cc::VideoFrameProvider*));
   MOCK_METHOD1(SetRotation, void(media::VideoRotation));
-  MOCK_METHOD1(MockSetIsOpaque, void(bool));
   MOCK_METHOD1(SetIsSurfaceVisible, void(bool));
   MOCK_METHOD1(SetIsPageVisible, void(bool));
   MOCK_METHOD1(SetForceSubmit, void(bool));
@@ -438,13 +437,6 @@
     MockInitialize(provider);
   }
 
-  // This method may try accessing frames, see deadlock case in
-  // https://crbug.com/901744.
-  void SetIsOpaque(bool opaque) override {
-    auto frame = provider_->GetCurrentFrame();
-    MockSetIsOpaque(opaque);
-  }
-
  private:
   cc::VideoFrameProvider* provider_;
 };
@@ -1188,7 +1180,6 @@
   provider->QueueFrames(timestamps, false);
   if (enable_surface_layer_for_video_) {
     EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
-    EXPECT_CALL(*submitter_ptr_, MockSetIsOpaque(false));
   }
   message_loop_controller_.RunAndWaitForStatus(
       media::PipelineStatus::PIPELINE_OK);
@@ -1203,7 +1194,6 @@
   provider->QueueFrames(timestamps, true);
   if (enable_surface_layer_for_video_) {
     EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(true));
-    EXPECT_CALL(*submitter_ptr_, MockSetIsOpaque(true));
   }
   message_loop_controller_.RunAndWaitForStatus(
       media::PipelineStatus::PIPELINE_OK);
diff --git a/content/shell/BUILD.gn b/content/shell/BUILD.gn
index 657cc79..1a90666 100644
--- a/content/shell/BUILD.gn
+++ b/content/shell/BUILD.gn
@@ -10,7 +10,6 @@
 import("//media/media_options.gni")
 import("//mojo/public/tools/bindings/mojom.gni")
 import("//ppapi/buildflags/buildflags.gni")
-import("//services/service_manager/public/service_manifest.gni")
 import("//tools/grit/grit_rule.gni")
 import("//tools/grit/repack.gni")
 import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
@@ -261,7 +260,6 @@
     "//services/network:network_service",
   ]
   deps = [
-    ":content_shell_packaged_services_manifest_overlay",
     ":resources",
     ":web_test_switches",
     ":web_test_utils",
@@ -296,6 +294,7 @@
     "//device/bluetooth",
     "//device/bluetooth:fake_bluetooth",
     "//device/bluetooth:mocks",
+    "//device/bluetooth/public/mojom:fake_bluetooth_interfaces",
     "//gin",
     "//gpu",
     "//media",
@@ -312,6 +311,7 @@
     "//services/service_manager/embedder:embedder_result_codes",
     "//services/service_manager/public/cpp",
     "//services/test/echo:lib",
+    "//services/test/echo:manifest",
     "//services/test/echo/public/mojom",
     "//skia",
     "//storage/browser",
@@ -441,6 +441,7 @@
     deps += [
       "//chromeos/dbus",
       "//services/ws/test_ws:lib",
+      "//services/ws/test_ws:manifest",
       "//services/ws/test_ws:mojom",
       "//ui/wm:test_support",
     ]
@@ -472,16 +473,6 @@
     "grit/shell_resources.h",
     "shell_resources.pak",
   ]
-
-  # Mojo manifest overlays are generated.
-  source_is_generated = true
-  grit_flags = [
-    "-E",
-    "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir),
-  ]
-  deps = [
-    ":content_shell_packaged_services_manifest_overlay",
-  ]
 }
 
 copy("copy_shell_resources") {
@@ -929,17 +920,6 @@
   ]
 }
 
-service_manifest("content_shell_packaged_services_manifest_overlay") {
-  testonly = true
-
-  source = "//content/shell/browser/content_shell_packaged_services_manifest_overlay.json"
-  packaged_services = [ "//services/test/echo:manifest" ]
-
-  if (is_chromeos) {
-    packaged_services += [ "//services/ws/test_ws:manifest" ]
-  }
-}
-
 group("content_shell_crash_test") {
   testonly = true
   data_deps = [
diff --git a/content/shell/browser/DEPS b/content/shell/browser/DEPS
index 1d4f62d..0e08b66 100644
--- a/content/shell/browser/DEPS
+++ b/content/shell/browser/DEPS
@@ -3,9 +3,10 @@
   "+components/keyed_service/content",
   "+components/network_session_configurator/common",
   "+services/device/public/cpp",
+  "+services/network/public",
   "+services/service_manager/public/cpp",
   "+services/service_manager/sandbox",
-  "+services/network/public",
+  "+services/ws/public",
   "+ui/ozone/public",
 ]
 
diff --git a/content/shell/browser/OWNERS b/content/shell/browser/OWNERS
index f606772..ae466884 100644
--- a/content/shell/browser/OWNERS
+++ b/content/shell/browser/OWNERS
@@ -1,13 +1,2 @@
 per-file shell_android.cc=tedchoc@chromium.org
 per-file shell_web_contents_view_delegate_android.cc=tedchoc@chromium.org
-
-per-file content_shell_browser_manifest_overlay.json=set noparent
-per-file content_shell_browser_manifest_overlay.json=file://ipc/SECURITY_OWNERS
-per-file content_shell_gpu_manifest_overlay.json=set noparent
-per-file content_shell_gpu_manifest_overlay.json=file://ipc/SECURITY_OWNERS
-per-file content_shell_packaged_services_manifest_overlay.json=set noparent
-per-file content_shell_packaged_services_manifest_overlay.json=file://ipc/SECURITY_OWNERS
-per-file content_shell_renderer_manifest_overlay.json=set noparent
-per-file content_shell_renderer_manifest_overlay.json=file://ipc/SECURITY_OWNERS
-per-file content_shell_utility_manifest_overlay.json=set noparent
-per-file content_shell_utility_manifest_overlay.json=file://ipc/SECURITY_OWNERS
diff --git a/content/shell/browser/content_shell_browser_manifest_overlay.json b/content/shell/browser/content_shell_browser_manifest_overlay.json
deleted file mode 100644
index 48bf90c..0000000
--- a/content/shell/browser/content_shell_browser_manifest_overlay.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
-  "name": "content_browser",
-  "interface_provider_specs": {
-    "service_manager:connector": {
-      "provides": {
-        "renderer": [
-          "content.mojom.MojoWebTestHelper",
-          "content.mojom.FakeBluetoothChooser",
-          "content.mojom.WebTestBluetoothFakeAdapterSetter",
-          "bluetooth.mojom.FakeBluetooth"
-        ]
-      },
-      "requires": {
-        "echo": [
-          "echo"
-        ],
-        "ui": [ "test" ],
-        "test_ws": [ "test" ]
-      }
-    },
-    "navigation:frame": {
-      "provides": {
-        "renderer": [
-          "content.mojom.MojoWebTestHelper"
-        ]
-      }
-    }
-  }
-}
diff --git a/content/shell/browser/content_shell_gpu_manifest_overlay.json b/content/shell/browser/content_shell_gpu_manifest_overlay.json
deleted file mode 100644
index b3ce24a..0000000
--- a/content/shell/browser/content_shell_gpu_manifest_overlay.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "name": "content_gpu",
-  "interface_provider_specs": {
-    "service_manager:connector": {
-      "provides": {
-        "browser": [
-          "content.mojom.PowerMonitorTest"
-        ]
-      }
-    }
-  }
-}
diff --git a/content/shell/browser/content_shell_packaged_services_manifest_overlay.json b/content/shell/browser/content_shell_packaged_services_manifest_overlay.json
deleted file mode 100644
index cbd4dec..0000000
--- a/content/shell/browser/content_shell_packaged_services_manifest_overlay.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "name": "content_packaged_services",
-  "display_name": "Content Shell Packaged Services",
-  "interface_provider_specs": {}
-}
diff --git a/content/shell/browser/content_shell_renderer_manifest_overlay.json b/content/shell/browser/content_shell_renderer_manifest_overlay.json
deleted file mode 100644
index 47b29360..0000000
--- a/content/shell/browser/content_shell_renderer_manifest_overlay.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-  "name": "content_renderer",
-  "interface_provider_specs": {
-    "service_manager:connector": {
-      "provides": {
-        "browser": [
-          "content.mojom.PowerMonitorTest",
-          "content.mojom.TestService"
-        ]
-      }
-    },
-    "navigation:frame": {
-      "provides": {
-        "browser": [
-          "content.mojom.WebTestControl"
-        ]
-      }
-    }
-  }
-}
diff --git a/content/shell/browser/content_shell_utility_manifest_overlay.json b/content/shell/browser/content_shell_utility_manifest_overlay.json
deleted file mode 100644
index 1beaf4e..0000000
--- a/content/shell/browser/content_shell_utility_manifest_overlay.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "name": "content_utility",
-  "interface_provider_specs": {
-    "service_manager:connector": {
-      "provides": {
-        "browser": [
-          "content.mojom.PowerMonitorTest",
-          "content.mojom.TestService"
-        ]
-      }
-    }
-  }
-}
diff --git a/content/shell/browser/shell_content_browser_client.cc b/content/shell/browser/shell_content_browser_client.cc
index d216b50..40dd1b2 100644
--- a/content/shell/browser/shell_content_browser_client.cc
+++ b/content/shell/browser/shell_content_browser_client.cc
@@ -12,7 +12,7 @@
 #include "base/command_line.h"
 #include "base/files/file.h"
 #include "base/files/file_util.h"
-#include "base/json/json_reader.h"
+#include "base/no_destructor.h"
 #include "base/path_service.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -40,19 +40,26 @@
 #include "content/shell/browser/shell_quota_permission_context.h"
 #include "content/shell/browser/shell_url_request_context_getter.h"
 #include "content/shell/browser/shell_web_contents_view_delegate_creator.h"
+#include "content/shell/common/power_monitor_test.mojom.h"
 #include "content/shell/common/shell_messages.h"
 #include "content/shell/common/shell_switches.h"
+#include "content/shell/common/web_test.mojom.h"
+#include "content/shell/common/web_test/fake_bluetooth_chooser.mojom.h"
+#include "content/shell/common/web_test/web_test_bluetooth_fake_adapter_setter.mojom.h"
 #include "content/shell/common/web_test/web_test_switches.h"
-#include "content/shell/grit/shell_resources.h"
+#include "content/test/data/mojo_web_test_helper_test.mojom.h"
+#include "device/bluetooth/public/mojom/test/fake_bluetooth.mojom.h"
 #include "media/mojo/buildflags.h"
 #include "net/ssl/client_cert_identity.h"
 #include "net/url_request/url_request.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/mojom/network_service.mojom.h"
+#include "services/service_manager/public/cpp/manifest.h"
+#include "services/service_manager/public/cpp/manifest_builder.h"
+#include "services/test/echo/manifest.h"
 #include "services/test/echo/public/mojom/echo.mojom.h"
 #include "storage/browser/quota/quota_settings.h"
-#include "ui/base/resource/resource_bundle.h"
 #include "ui/base/ui_base_features.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -68,6 +75,8 @@
 // TODO(https://crbug.com/784179): Remove nogncheck.
 #include "content/public/browser/context_factory.h"
 #include "content/public/browser/gpu_interface_provider_factory.h"
+#include "services/ws/public/mojom/constants.mojom.h"         // nogncheck
+#include "services/ws/test_ws/manifest.h"                     // nogncheck
 #include "services/ws/test_ws/test_window_service_factory.h"  // nogncheck
 #include "services/ws/test_ws/test_ws.mojom.h"                // nogncheck
 #endif
@@ -146,6 +155,75 @@
 }
 #endif  // defined(OS_ANDROID)
 
+const service_manager::Manifest& GetContentBrowserOverlayManifest() {
+  static base::NoDestructor<service_manager::Manifest> manifest {
+    service_manager::ManifestBuilder()
+        .ExposeCapability(
+            "renderer",
+            service_manager::Manifest::InterfaceList<
+                mojom::MojoWebTestHelper, mojom::FakeBluetoothChooser,
+                mojom::WebTestBluetoothFakeAdapterSetter,
+                bluetooth::mojom::FakeBluetooth>())
+        .RequireCapability(echo::mojom::kServiceName, "echo")
+#if defined(OS_CHROMEOS)
+        .RequireCapability(ws::mojom::kServiceName, "test")
+        .RequireCapability("test_ws", "test")
+#endif
+        .ExposeInterfaceFilterCapability_Deprecated(
+            "navigation:frame", "renderer",
+            service_manager::Manifest::InterfaceList<
+                mojom::MojoWebTestHelper>())
+        .Build()
+  };
+  return *manifest;
+}
+
+const service_manager::Manifest& GetContentGpuOverlayManifest() {
+  static base::NoDestructor<service_manager::Manifest> manifest{
+      service_manager::ManifestBuilder()
+          .ExposeCapability("browser", service_manager::Manifest::InterfaceList<
+                                           mojom::PowerMonitorTest>())
+          .Build()};
+  return *manifest;
+}
+
+const service_manager::Manifest& GetContentPackagedServicesOverlayManifest() {
+  static base::NoDestructor<service_manager::Manifest> manifest {
+    service_manager::ManifestBuilder()
+        .PackageService(echo::GetManifest())
+#if defined(OS_CHROMEOS)
+        .PackageService(test_ws::GetManifest())
+#endif
+        .Build()
+  };
+  return *manifest;
+}
+
+const service_manager::Manifest& GetContentRendererOverlayManifest() {
+  static base::NoDestructor<service_manager::Manifest> manifest{
+      service_manager::ManifestBuilder()
+          .ExposeCapability(
+              "browser",
+              service_manager::Manifest::InterfaceList<mojom::PowerMonitorTest,
+                                                       mojom::TestService>())
+          .ExposeInterfaceFilterCapability_Deprecated(
+              "navigation:frame", "browser",
+              service_manager::Manifest::InterfaceList<mojom::WebTestControl>())
+          .Build()};
+  return *manifest;
+}
+
+const service_manager::Manifest& GetContentUtilityOverlayManifest() {
+  static base::NoDestructor<service_manager::Manifest> manifest{
+      service_manager::ManifestBuilder()
+          .ExposeCapability(
+              "browser",
+              service_manager::Manifest::InterfaceList<mojom::PowerMonitorTest,
+                                                       mojom::TestService>())
+          .Build()};
+  return *manifest;
+}
+
 }  // namespace
 
 std::string GetShellUserAgent() {
@@ -254,25 +332,18 @@
 
 base::Optional<service_manager::Manifest>
 ShellContentBrowserClient::GetServiceManifestOverlay(base::StringPiece name) {
-  int id = -1;
   if (name == content::mojom::kBrowserServiceName)
-    id = IDR_CONTENT_SHELL_BROWSER_MANIFEST_OVERLAY;
-  else if (name == content::mojom::kPackagedServicesServiceName)
-    id = IDR_CONTENT_SHELL_PACKAGED_SERVICES_MANIFEST_OVERLAY;
-  else if (name == content::mojom::kGpuServiceName)
-    id = IDR_CONTENT_SHELL_GPU_MANIFEST_OVERLAY;
-  else if (name == content::mojom::kRendererServiceName)
-    id = IDR_CONTENT_SHELL_RENDERER_MANIFEST_OVERLAY;
-  else if (name == content::mojom::kUtilityServiceName)
-    id = IDR_CONTENT_SHELL_UTILITY_MANIFEST_OVERLAY;
-  if (id == -1)
-    return base::nullopt;
+    return GetContentBrowserOverlayManifest();
+  if (name == content::mojom::kPackagedServicesServiceName)
+    return GetContentPackagedServicesOverlayManifest();
+  if (name == content::mojom::kGpuServiceName)
+    return GetContentGpuOverlayManifest();
+  if (name == content::mojom::kRendererServiceName)
+    return GetContentRendererOverlayManifest();
+  if (name == content::mojom::kUtilityServiceName)
+    return GetContentUtilityOverlayManifest();
 
-  base::StringPiece manifest_contents =
-      ui::ResourceBundle::GetSharedInstance().GetRawDataResourceForScale(
-          id, ui::ScaleFactor::SCALE_FACTOR_NONE);
-  return service_manager::Manifest::FromValueDeprecated(
-      base::JSONReader::Read(manifest_contents));
+  return base::nullopt;
 }
 
 void ShellContentBrowserClient::AppendExtraCommandLineSwitches(
diff --git a/content/shell/shell_resources.grd b/content/shell/shell_resources.grd
index f1843a2..166fc598 100644
--- a/content/shell/shell_resources.grd
+++ b/content/shell/shell_resources.grd
@@ -13,12 +13,6 @@
       <include name="IDR_CONTENT_SHELL_DEVTOOLS_DISCOVERY_PAGE" file="resources/shell_devtools_discovery_page.html" type="BINDATA" />
       <include name="IDR_CONTENT_SHELL_MISSING_IMAGE_GIF" file="resources/missingImage.gif" type="BINDATA" />
       <include name="IDR_CONTENT_SHELL_MISSING_IMAGE_PNG" file="resources/missingImage.png" type="BINDATA" />
-      <include name="IDR_CONTENT_SHELL_BROWSER_MANIFEST_OVERLAY" file="browser/content_shell_browser_manifest_overlay.json" type="BINDATA" />
-      <include name="IDR_CONTENT_SHELL_GPU_MANIFEST_OVERLAY" file="browser/content_shell_gpu_manifest_overlay.json" type="BINDATA" />
-      <include name="IDR_CONTENT_SHELL_RENDERER_MANIFEST_OVERLAY" file="browser/content_shell_renderer_manifest_overlay.json" type="BINDATA" />
-      <include name="IDR_CONTENT_SHELL_UTILITY_MANIFEST_OVERLAY" file="browser/content_shell_utility_manifest_overlay.json" type="BINDATA" />
-      <!-- Generated resources. -->
-      <include name="IDR_CONTENT_SHELL_PACKAGED_SERVICES_MANIFEST_OVERLAY" file="${root_gen_dir}/content/shell/content_shell_packaged_services_manifest_overlay.json" type="BINDATA" use_base_dir="false"/>
     </includes>
   </release>
 </grit>
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 5a0aab28..42abd1b3 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -510,6 +510,8 @@
     self.Flaky('conformance2/textures/image_bitmap_from_video/' +
         'tex-2d-rgba16f-rgba-half_float.html',
         ['mac', ('nvidia', 0xfe9)], bug=682834)
+    self.Flaky('conformance2/textures/canvas/tex-3d-rg16f-rg-float.html',
+        ['mac', ('nvidia', 0xfe9)], bug=922517)
     self.Fail('conformance/glsl/bugs/init-array-with-loop.html',
         ['mac', ('nvidia', 0xfe9)], bug=784817)
 
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
index 3219940d3..699d05a 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
@@ -553,6 +553,8 @@
     # Mac Retina NVidia failures
     self.Fail('conformance/attribs/gl-disabled-vertex-attrib.html',
         ['mac', ('nvidia', 0xfe9)], bug=635081)
+    self.Flaky('conformance/ogles/GL/exp2/exp2_001_to_008.html',
+        ['mac', ('nvidia', 0xfe9)], bug=923080)
     self.Fail('conformance/programs/' +
         'gl-bind-attrib-location-long-names-test.html',
         ['mac', ('nvidia', 0xfe9)], bug=635081)
diff --git a/content/test/mock_overscroll_refresh_handler_android.cc b/content/test/mock_overscroll_refresh_handler_android.cc
index 7337058..0d480642 100644
--- a/content/test/mock_overscroll_refresh_handler_android.cc
+++ b/content/test/mock_overscroll_refresh_handler_android.cc
@@ -13,7 +13,7 @@
 
 MockOverscrollRefreshHandlerAndroid::~MockOverscrollRefreshHandlerAndroid() {}
 
-bool MockOverscrollRefreshHandlerAndroid::PullStart() {
+bool MockOverscrollRefreshHandlerAndroid::PullStart(float, float) {
   // The first GestureScrollUpdate starts the pull, but does not update the
   // pull. For the purpose of testing, we'll be consistent with aura
   // overscroll and consider this an update.
@@ -21,7 +21,7 @@
   return true;
 }
 
-void MockOverscrollRefreshHandlerAndroid::PullUpdate(float) {
+void MockOverscrollRefreshHandlerAndroid::PullUpdate(float, float) {
   OnPullUpdate();
 }
 
diff --git a/content/test/mock_overscroll_refresh_handler_android.h b/content/test/mock_overscroll_refresh_handler_android.h
index e74cdb07..708bfc2 100644
--- a/content/test/mock_overscroll_refresh_handler_android.h
+++ b/content/test/mock_overscroll_refresh_handler_android.h
@@ -22,8 +22,8 @@
   ~MockOverscrollRefreshHandlerAndroid() override;
 
   // ui::OverscrollRefreshHandler:
-  bool PullStart() override;
-  void PullUpdate(float) override;
+  bool PullStart(float, float) override;
+  void PullUpdate(float, float) override;
   void PullRelease(bool) override;
   void PullReset() override;
 
diff --git a/docs/security/rule-of-2.md b/docs/security/rule-of-2.md
new file mode 100644
index 0000000..935bd72
--- /dev/null
+++ b/docs/security/rule-of-2.md
@@ -0,0 +1,125 @@
+# The Rule Of 2
+
+When you write code to parse, evaluate, or otherwise handle untrustworthy inputs
+from the Internet — which is almost everything we do in a web browser! — we like
+to follow a simple rule to make sure it's safe enough to do so. The Rule Of 2
+is: Pick no more than 2 of
+
+  * untrustworthy inputs;
+  * unsafe implementation language; and
+  * high privilege.
+
+## Why?
+
+When code that handles untrustworthy inputs at high privilege has bugs, the
+resulting vulnerabilities are typically of Critical or High severity. (See our
+[Severity Guidelines](severity-guidelines.md).) We'd love to reduce the severity
+of such bugs by reducing the amount of damage they can do (lowering their
+privilege), avoiding the classes of memory corruption bugs (using a safe
+language), or reducing the likelihood that the input is malicious (asserting the
+trustworthiness of the source).
+
+## What?
+
+_Untrustworthy inputs_ are inputs that
+
+  * have non-trivial grammars; or
+  * come from untrustworthy sources.
+
+Unfortunately, essentially no format you will ever come across has a trivial
+grammar. And, of course, any arbitrary peer on the Internet is an untrustworthy
+source.
+
+_Unsafe implementation languages_ are languages that lack
+[memory safety](https://en.wikipedia.org/wiki/Memory_safety), including at least
+C, C++, and assembly language. Memory-safe languages include Go, Rust, Python,
+Java, JavaScript, Kotlin, and Swift.
+
+_High privilege_ is a relative term. The very highest-privilege programs are the
+computer's firmware, the bootloader, the kernel, any hypervisor or virtual
+machine monitor, and so on. Below that are processes that run as an OS-level
+account representing a person; this includes the Chrome browser process. We
+consider such processes to have high privilege. (After all, they can do anything
+the person can do, with any and all of the person's valuable data and accounts.)
+
+Processes with slightly reduced privilege include (as of January 2019) the
+network process and the GPU process. These are still pretty high-privilege
+processes. We are always looking for ways to reduce their privilege without
+breaking them.
+
+Low-privilege processes include sandboxed utility processes and renderer
+processes with [Site
+Isolation](https://www.chromium.org/Home/chromium-security/site-isolation) (very
+good) or [origin
+isolation](https://www.chromium.org/administrators/policy-list-3#IsolateOrigins)
+(even better).
+
+## Solutions To This Puzzle
+
+Chrome Security Team will generally not approve for landing a CL or new feature
+that involves all 3 of untrustworthy inputs, unsafe language, and high
+privilege. To solve this problem, you need to get rid of at least 1 of those 3
+things. Here are some ways to do that.
+
+### Privilege Reduction
+
+Also known as [_sandboxing_](https://cs.chromium.org/chromium/src/sandbox/),
+privilege reduction means running the code in a process that has had some or
+many of its privileges revoked.
+
+When appropriate, try to handle the inputs in a renderer process that is Site
+Isolated to the same site as the inputs come from. Take care to validate the
+parsed (processed) inputs in the browser, since the semantics of the data are
+not necessarily trustworthy yet.
+
+Equivalently, you can launch a sandboxed utility process to handle the data, and
+return a well-formed response back to the caller in an IPC message. An example
+of launching a utility process to parse an untrustworthy input is [Safe
+Browsing's ZIP
+analyzer](https://cs.chromium.org/chromium/src/chrome/common/safe_browsing/zip_analyzer.h).
+
+### Verifying The Trustworthiness Of A Source
+
+If you can be sure that the input comes from a trustworthy source, it can be OK
+to parse/evaluate it at high privilege in an unsafe language. A "trustworthy
+source" meets all of these criteria:
+
+  * communication happens via validly-authenticated TLS, HTTPS, or QUIC;
+  * peer's keys are [pinned in Chrome](https://cs.chromium.org/chromium/src/net/http/transport_security_state_static.json?sq=package:chromium&g=0); and
+  * peer is operated by a business entity that Chrome should trust (e.g. an [Alphabet](https://abc.xyz) company).
+
+### Normalization
+
+You can 'defang' a potentially-malicious input by transforming it into a
+_normalized_ or minimal form. For example, consider the PNG image format, which
+is complex and whose [C implementation has suffered from memory corruption bugs
+in the
+past](https://www.cvedetails.com/vulnerability-list/vendor_id-7294/Libpng.html).
+An attacker would craft a malicious PNG that could trigger such a bug. But if
+you transform the image into a another format (in another, in a low-privilege
+process, of course), the malicious nature of the PNG 'should' be eliminated and
+then safe for reading at a higher privilege level.
+
+### Safe Languages
+
+Where possible, it's great to use a memory-safe language. Of the currently
+approved set of implementation languages in Chromium, the most likely candidates
+are Java (on Android only) and JavaScript (although we don't currently use it in
+high-privilege processes like the browser). One can imagine Swift on iOS or
+Kotlin on Android, too, although they are not currently used in Chromium.
+
+## Existing Code That Violates The Rule
+
+Obviously, we still have a lot of code that violates this rule. For example,
+until very recently, all of the network stack was in the browser process, and
+its whole job is to parse complex and untrustworthy inputs (TLS, QUIC, HTTP,
+DNS, X.509, and more). This dangerous combination is why bugs in that area of
+code are often of Critical severity:
+
+  * [OOB Write in `QuicStreamSequencerBuffer::OnStreamData`](https://bugs.chromium.org/p/chromium/issues/detail?id=778505)
+  * [Stack Buffer Overflow in `QuicClientPromisedInfo::OnPromiseHeaders`](https://bugs.chromium.org/p/chromium/issues/detail?id=777728)
+
+We now have the network stack in its own dedicated process, and have begun the
+process of reducing that process' privilege. ([macOS
+bug](https://bugs.chromium.org/p/chromium/issues/detail?id=915910), [Windows
+bug](https://bugs.chromium.org/p/chromium/issues/detail?id=841001))
diff --git a/extensions/common/csp_validator.cc b/extensions/common/csp_validator.cc
index 270f829..1b29ff2 100644
--- a/extensions/common/csp_validator.cc
+++ b/extensions/common/csp_validator.cc
@@ -9,6 +9,7 @@
 #include <algorithm>
 #include <initializer_list>
 #include <iterator>
+#include <utility>
 #include <vector>
 
 #include "base/bind.h"
@@ -202,14 +203,11 @@
   return false;
 }
 
-InstallWarning CSPInstallWarning(const std::string& csp_warning) {
-  return InstallWarning(csp_warning, manifest_keys::kContentSecurityPolicy);
-}
-
 std::string GetSecureDirectiveValues(
     int options,
     const std::string& directive_name,
     const std::vector<base::StringPiece>& directive_values,
+    const std::string& manifest_key,
     std::vector<InstallWarning>* warnings) {
   std::vector<base::StringPiece> sane_csp_parts{directive_name};
   for (base::StringPiece source_literal : directive_values) {
@@ -248,9 +246,11 @@
     if (is_secure_csp_token) {
       sane_csp_parts.push_back(source_literal);
     } else if (warnings) {
-      warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
-          manifest_errors::kInvalidCSPInsecureValue, source_literal.as_string(),
-          directive_name)));
+      warnings->push_back(InstallWarning(
+          ErrorUtils::FormatErrorMessage(
+              manifest_errors::kInvalidCSPInsecureValue, manifest_key,
+              source_literal.as_string(), directive_name),
+          manifest_key));
     }
   }
   // End of CSP directive that was started at the beginning of this method. If
@@ -269,6 +269,7 @@
 std::string GetAppSandboxSecureDirectiveValues(
     const std::string& directive_name,
     const std::vector<base::StringPiece>& directive_values,
+    const std::string& manifest_key,
     std::vector<InstallWarning>* warnings) {
   std::vector<std::string> sane_csp_parts{directive_name};
   bool seen_self_or_none = false;
@@ -283,9 +284,11 @@
       seen_self_or_none |= source_lower == "'none'" || source_lower == "'self'";
       sane_csp_parts.push_back(source_lower);
     } else if (warnings) {
-      warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
-          manifest_errors::kInvalidCSPInsecureValue, source_literal.as_string(),
-          directive_name)));
+      warnings->push_back(InstallWarning(
+          ErrorUtils::FormatErrorMessage(
+              manifest_errors::kInvalidCSPInsecureValue, manifest_key,
+              source_literal.as_string(), directive_name),
+          manifest_key));
     }
   }
 
@@ -332,6 +335,7 @@
 using SecureDirectiveValueFunction = base::Callback<std::string(
     const std::string& directive_name,
     const std::vector<base::StringPiece>& directive_values,
+    const std::string& manifest_key,
     std::vector<InstallWarning>* warnings)>;
 
 // Represents a token in CSP string.
@@ -347,6 +351,7 @@
   // directive values are secured by |secure_function|.
   bool MatchAndUpdateStatus(DirectiveStatus* status,
                             const SecureDirectiveValueFunction& secure_function,
+                            const std::string& manifest_key,
                             std::vector<InstallWarning>* warnings) {
     if (!status->Matches(directive_.directive_name))
       return false;
@@ -355,7 +360,7 @@
     status->set_seen_in_policy();
 
     secure_value_ = secure_function.Run(
-        directive_.directive_name, directive_.directive_values,
+        directive_.directive_name, directive_.directive_values, manifest_key,
         // Don't show any errors for duplicate CSP directives, because it will
         // be ignored by the CSP parser
         // (http://www.w3.org/TR/CSP2/#policy-parsing). Therefore, set warnings
@@ -387,9 +392,11 @@
 // will use default secure values (via GetDefaultCSPValue).
 class CSPEnforcer {
  public:
-  CSPEnforcer(bool show_missing_csp_warnings,
+  CSPEnforcer(std::string manifest_key,
+              bool show_missing_csp_warnings,
               const SecureDirectiveValueFunction& secure_function)
-      : show_missing_csp_warnings_(show_missing_csp_warnings),
+      : manifest_key_(std::move(manifest_key)),
+        show_missing_csp_warnings_(show_missing_csp_warnings),
         secure_function_(secure_function) {}
   virtual ~CSPEnforcer() {}
 
@@ -407,6 +414,7 @@
   std::vector<std::unique_ptr<DirectiveStatus>> secure_directives_;
 
  private:
+  const std::string manifest_key_;
   const bool show_missing_csp_warnings_;
   const SecureDirectiveValueFunction secure_function_;
 
@@ -428,14 +436,15 @@
     bool matches_enforcing_directive = false;
     for (const std::unique_ptr<DirectiveStatus>& status : secure_directives_) {
       if (csp_directive_token.MatchAndUpdateStatus(
-              status.get(), secure_function_, warnings)) {
+              status.get(), secure_function_, manifest_key_, warnings)) {
         matches_enforcing_directive = true;
         break;
       }
     }
     if (!matches_enforcing_directive) {
-      csp_directive_token.MatchAndUpdateStatus(
-          &default_src_status, secure_function_, &default_src_csp_warnings);
+      csp_directive_token.MatchAndUpdateStatus(&default_src_status,
+                                               secure_function_, manifest_key_,
+                                               &default_src_csp_warnings);
     }
 
     enforced_csp_parts.push_back(csp_directive_token.ToString());
@@ -464,8 +473,11 @@
       enforced_csp_parts.push_back(GetDefaultCSPValue(*status));
 
       if (warnings && show_missing_csp_warnings_) {
-        warnings->push_back(CSPInstallWarning(ErrorUtils::FormatErrorMessage(
-            manifest_errors::kInvalidCSPMissingSecureSrc, status->name())));
+        warnings->push_back(
+            InstallWarning(ErrorUtils::FormatErrorMessage(
+                               manifest_errors::kInvalidCSPMissingSecureSrc,
+                               manifest_key_, status->name()),
+                           manifest_key_));
       }
     }
   }
@@ -475,8 +487,12 @@
 
 class ExtensionCSPEnforcer : public CSPEnforcer {
  public:
-  ExtensionCSPEnforcer(bool allow_insecure_object_src, int options)
-      : CSPEnforcer(true, base::Bind(&GetSecureDirectiveValues, options)) {
+  ExtensionCSPEnforcer(std::string manifest_key,
+                       bool allow_insecure_object_src,
+                       int options)
+      : CSPEnforcer(std::move(manifest_key),
+                    true,
+                    base::Bind(&GetSecureDirectiveValues, options)) {
     secure_directives_.emplace_back(new DirectiveStatus({kScriptSrc}));
     if (!allow_insecure_object_src)
       secure_directives_.emplace_back(new DirectiveStatus({kObjectSrc}));
@@ -496,8 +512,10 @@
 
 class AppSandboxPageCSPEnforcer : public CSPEnforcer {
  public:
-  AppSandboxPageCSPEnforcer()
-      : CSPEnforcer(false, base::Bind(&GetAppSandboxSecureDirectiveValues)) {
+  AppSandboxPageCSPEnforcer(std::string manifest_key)
+      : CSPEnforcer(std::move(manifest_key),
+                    false,
+                    base::Bind(&GetAppSandboxSecureDirectiveValues)) {
     secure_directives_.emplace_back(
         new DirectiveStatus({kChildSrc, kFrameSrc}));
     secure_directives_.emplace_back(new DirectiveStatus({kScriptSrc}));
@@ -574,6 +592,7 @@
 
 std::string SanitizeContentSecurityPolicy(
     const std::string& policy,
+    std::string manifest_key,
     int options,
     std::vector<InstallWarning>* warnings) {
   CSPParser csp_parser(policy);
@@ -581,14 +600,16 @@
   bool allow_insecure_object_src =
       AllowedToHaveInsecureObjectSrc(options, csp_parser.directives());
 
-  ExtensionCSPEnforcer csp_enforcer(allow_insecure_object_src, options);
+  ExtensionCSPEnforcer csp_enforcer(std::move(manifest_key),
+                                    allow_insecure_object_src, options);
   return csp_enforcer.Enforce(csp_parser.directives(), warnings);
 }
 
 std::string GetEffectiveSandoxedPageCSP(const std::string& policy,
+                                        std::string manifest_key,
                                         std::vector<InstallWarning>* warnings) {
   CSPParser csp_parser(policy);
-  AppSandboxPageCSPEnforcer csp_enforcer;
+  AppSandboxPageCSPEnforcer csp_enforcer(std::move(manifest_key));
   return csp_enforcer.Enforce(csp_parser.directives(), warnings);
 }
 
diff --git a/extensions/common/csp_validator.h b/extensions/common/csp_validator.h
index 6bda17b..dbf9b37 100644
--- a/extensions/common/csp_validator.h
+++ b/extensions/common/csp_validator.h
@@ -105,6 +105,7 @@
 // Returns the sanitized policy.
 std::string SanitizeContentSecurityPolicy(
     const std::string& policy,
+    std::string manifest_key,
     int options,
     std::vector<InstallWarning>* warnings);
 
@@ -118,6 +119,7 @@
 // If |warnings| is not nullptr, any validation errors are appended to
 // |warnings|.
 std::string GetEffectiveSandoxedPageCSP(const std::string& policy,
+                                        std::string manifest_key,
                                         std::vector<InstallWarning>* warnings);
 
 // Checks whether the given |policy| enforces a unique origin sandbox as
diff --git a/extensions/common/csp_validator_unittest.cc b/extensions/common/csp_validator_unittest.cc
index 3599efb..744f48cd 100644
--- a/extensions/common/csp_validator_unittest.cc
+++ b/extensions/common/csp_validator_unittest.cc
@@ -26,15 +26,21 @@
 
 namespace {
 
-std::string InsecureValueWarning(const std::string& directive,
-                                 const std::string& value) {
+std::string InsecureValueWarning(
+    const std::string& directive,
+    const std::string& value,
+    const std::string& manifest_key =
+        extensions::manifest_keys::kContentSecurityPolicy) {
   return ErrorUtils::FormatErrorMessage(
-      extensions::manifest_errors::kInvalidCSPInsecureValue, value, directive);
+      extensions::manifest_errors::kInvalidCSPInsecureValue, manifest_key,
+      value, directive);
 }
 
-std::string MissingSecureSrcWarning(const std::string& directive) {
+std::string MissingSecureSrcWarning(const std::string& manifest_key,
+                                    const std::string& directive) {
   return ErrorUtils::FormatErrorMessage(
-      extensions::manifest_errors::kInvalidCSPMissingSecureSrc, directive);
+      extensions::manifest_errors::kInvalidCSPMissingSecureSrc, manifest_key,
+      directive);
 }
 
 bool CSPEquals(const std::string& csp1, const std::string& csp2) {
@@ -54,13 +60,16 @@
 
 SanitizedCSPResult SanitizeCSP(const std::string& policy, int options) {
   SanitizedCSPResult result;
-  result.csp = SanitizeContentSecurityPolicy(policy, options, &result.warnings);
+  result.csp = SanitizeContentSecurityPolicy(
+      policy, extensions::manifest_keys::kContentSecurityPolicy, options,
+      &result.warnings);
   return result;
 }
 
 SanitizedCSPResult SanitizeSandboxPageCSP(const std::string& policy) {
   SanitizedCSPResult result;
-  result.csp = GetEffectiveSandoxedPageCSP(policy, &result.warnings);
+  result.csp = GetEffectiveSandoxedPageCSP(
+      policy, extensions::manifest_keys::kSandboxedPagesCSP, &result.warnings);
   return result;
 }
 
@@ -146,62 +155,67 @@
 }
 
 TEST(ExtensionCSPValidator, IsSecure) {
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(std::string(), OPTIONS_ALLOW_UNSAFE_EVAL),
-      "script-src 'self'; object-src 'self';",
-      MissingSecureSrcWarning("script-src"),
-      MissingSecureSrcWarning("object-src")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "img-src https://google.com", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "img-src https://google.com; script-src 'self'; object-src 'self';",
-      MissingSecureSrcWarning("script-src"),
-      MissingSecureSrcWarning("object-src")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "script-src a b", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "script-src; object-src 'self';",
-      InsecureValueWarning("script-src", "a"),
-      InsecureValueWarning("script-src", "b"),
-      MissingSecureSrcWarning("object-src")));
+  auto missing_secure_src_warning = [](const std::string& directive) {
+    return MissingSecureSrcWarning(
+        extensions::manifest_keys::kContentSecurityPolicy, directive);
+  };
 
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src *", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src;",
-      InsecureValueWarning("default-src", "*")));
+  EXPECT_TRUE(CheckCSP(SanitizeCSP(std::string(), OPTIONS_ALLOW_UNSAFE_EVAL),
+                       "script-src 'self'; object-src 'self';",
+                       missing_secure_src_warning("script-src"),
+                       missing_secure_src_warning("object-src")));
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("img-src https://google.com", OPTIONS_ALLOW_UNSAFE_EVAL),
+      "img-src https://google.com; script-src 'self'; object-src 'self';",
+      missing_secure_src_warning("script-src"),
+      missing_secure_src_warning("object-src")));
+  EXPECT_TRUE(CheckCSP(SanitizeCSP("script-src a b", OPTIONS_ALLOW_UNSAFE_EVAL),
+                       "script-src; object-src 'self';",
+                       InsecureValueWarning("script-src", "a"),
+                       InsecureValueWarning("script-src", "b"),
+                       missing_secure_src_warning("object-src")));
+
+  EXPECT_TRUE(CheckCSP(SanitizeCSP("default-src *", OPTIONS_ALLOW_UNSAFE_EVAL),
+                       "default-src;",
+                       InsecureValueWarning("default-src", "*")));
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "default-src 'self';", OPTIONS_ALLOW_UNSAFE_EVAL)));
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "default-src 'none';", OPTIONS_ALLOW_UNSAFE_EVAL)));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' ftp://google.com", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "ftp://google.com")));
+  EXPECT_TRUE(
+      CheckCSP(SanitizeCSP("default-src 'self' ftp://google.com",
+                           OPTIONS_ALLOW_UNSAFE_EVAL),
+               "default-src 'self';",
+               InsecureValueWarning("default-src", "ftp://google.com")));
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "default-src 'self' https://google.com;", OPTIONS_ALLOW_UNSAFE_EVAL)));
 
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src *; default-src 'self'", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src; default-src 'self';",
-      InsecureValueWarning("default-src", "*")));
+  EXPECT_TRUE(CheckCSP(SanitizeCSP("default-src *; default-src 'self'",
+                                   OPTIONS_ALLOW_UNSAFE_EVAL),
+                       "default-src; default-src 'self';",
+                       InsecureValueWarning("default-src", "*")));
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "default-src 'self'; default-src *;", OPTIONS_ALLOW_UNSAFE_EVAL),
       "default-src 'self'; default-src;"));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self'; default-src *; script-src *; script-src 'self'",
-      OPTIONS_ALLOW_UNSAFE_EVAL),
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP(
+          "default-src 'self'; default-src *; script-src *; script-src 'self'",
+          OPTIONS_ALLOW_UNSAFE_EVAL),
       "default-src 'self'; default-src; script-src; script-src 'self';",
       InsecureValueWarning("script-src", "*")));
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "default-src 'self'; default-src *; script-src 'self'; script-src *;",
       OPTIONS_ALLOW_UNSAFE_EVAL),
       "default-src 'self'; default-src; script-src 'self'; script-src;"));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src *; script-src 'self'", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src; script-src 'self';",
-      InsecureValueWarning("default-src", "*")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src *; script-src 'self'; img-src 'self'",
-      OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src; script-src 'self'; img-src 'self';",
-      InsecureValueWarning("default-src", "*")));
+  EXPECT_TRUE(CheckCSP(SanitizeCSP("default-src *; script-src 'self'",
+                                   OPTIONS_ALLOW_UNSAFE_EVAL),
+                       "default-src; script-src 'self';",
+                       InsecureValueWarning("default-src", "*")));
+  EXPECT_TRUE(
+      CheckCSP(SanitizeCSP("default-src *; script-src 'self'; img-src 'self'",
+                           OPTIONS_ALLOW_UNSAFE_EVAL),
+               "default-src; script-src 'self'; img-src 'self';",
+               InsecureValueWarning("default-src", "*")));
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "default-src *; script-src 'self'; object-src 'self';",
       OPTIONS_ALLOW_UNSAFE_EVAL),
@@ -211,22 +225,21 @@
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "default-src 'unsafe-eval';", OPTIONS_ALLOW_UNSAFE_EVAL)));
 
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'unsafe-eval'", OPTIONS_NONE),
-      "default-src;",
-      InsecureValueWarning("default-src", "'unsafe-eval'")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'unsafe-inline'", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src;",
-      InsecureValueWarning("default-src", "'unsafe-inline'")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'unsafe-inline' 'none'", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'none';",
-      InsecureValueWarning("default-src", "'unsafe-inline'")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' http://google.com", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "http://google.com")));
+  EXPECT_TRUE(CheckCSP(SanitizeCSP("default-src 'unsafe-eval'", OPTIONS_NONE),
+                       "default-src;",
+                       InsecureValueWarning("default-src", "'unsafe-eval'")));
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'unsafe-inline'", OPTIONS_ALLOW_UNSAFE_EVAL),
+      "default-src;", InsecureValueWarning("default-src", "'unsafe-inline'")));
+  EXPECT_TRUE(CheckCSP(SanitizeCSP("default-src 'unsafe-inline' 'none'",
+                                   OPTIONS_ALLOW_UNSAFE_EVAL),
+                       "default-src 'none';",
+                       InsecureValueWarning("default-src", "'unsafe-inline'")));
+  EXPECT_TRUE(
+      CheckCSP(SanitizeCSP("default-src 'self' http://google.com",
+                           OPTIONS_ALLOW_UNSAFE_EVAL),
+               "default-src 'self';",
+               InsecureValueWarning("default-src", "http://google.com")));
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "default-src 'self' https://google.com;", OPTIONS_ALLOW_UNSAFE_EVAL)));
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
@@ -237,84 +250,83 @@
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "default-src 'self' chrome-extension-resource://aabbcc;",
       OPTIONS_ALLOW_UNSAFE_EVAL)));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' https:", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "https:")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' http:", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "http:")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' google.com", OPTIONS_ALLOW_UNSAFE_EVAL),
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' https:", OPTIONS_ALLOW_UNSAFE_EVAL),
+      "default-src 'self';", InsecureValueWarning("default-src", "https:")));
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' http:", OPTIONS_ALLOW_UNSAFE_EVAL),
+      "default-src 'self';", InsecureValueWarning("default-src", "http:")));
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' google.com", OPTIONS_ALLOW_UNSAFE_EVAL),
       "default-src 'self';",
       InsecureValueWarning("default-src", "google.com")));
 
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' *", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "*")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' *:*", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "*:*")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' *:*/", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "*:*/")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' *:*/path", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "*:*/path")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' https://", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "https://")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' https://*:*", OPTIONS_ALLOW_UNSAFE_EVAL),
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' *", OPTIONS_ALLOW_UNSAFE_EVAL),
+      "default-src 'self';", InsecureValueWarning("default-src", "*")));
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' *:*", OPTIONS_ALLOW_UNSAFE_EVAL),
+      "default-src 'self';", InsecureValueWarning("default-src", "*:*")));
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' *:*/", OPTIONS_ALLOW_UNSAFE_EVAL),
+      "default-src 'self';", InsecureValueWarning("default-src", "*:*/")));
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' *:*/path", OPTIONS_ALLOW_UNSAFE_EVAL),
+      "default-src 'self';", InsecureValueWarning("default-src", "*:*/path")));
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' https://", OPTIONS_ALLOW_UNSAFE_EVAL),
+      "default-src 'self';", InsecureValueWarning("default-src", "https://")));
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' https://*:*", OPTIONS_ALLOW_UNSAFE_EVAL),
       "default-src 'self';",
       InsecureValueWarning("default-src", "https://*:*")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' https://*:*/", OPTIONS_ALLOW_UNSAFE_EVAL),
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' https://*:*/", OPTIONS_ALLOW_UNSAFE_EVAL),
       "default-src 'self';",
       InsecureValueWarning("default-src", "https://*:*/")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' https://*:*/path", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "https://*:*/path")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' https://*.com", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "https://*.com")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' https://*.*.google.com/", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "https://*.*.google.com/")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' https://*.*.google.com:*/", OPTIONS_ALLOW_UNSAFE_EVAL),
+  EXPECT_TRUE(
+      CheckCSP(SanitizeCSP("default-src 'self' https://*:*/path",
+                           OPTIONS_ALLOW_UNSAFE_EVAL),
+               "default-src 'self';",
+               InsecureValueWarning("default-src", "https://*:*/path")));
+  EXPECT_TRUE(CheckCSP(SanitizeCSP("default-src 'self' https://*.com",
+                                   OPTIONS_ALLOW_UNSAFE_EVAL),
+                       "default-src 'self';",
+                       InsecureValueWarning("default-src", "https://*.com")));
+  EXPECT_TRUE(
+      CheckCSP(SanitizeCSP("default-src 'self' https://*.*.google.com/",
+                           OPTIONS_ALLOW_UNSAFE_EVAL),
+               "default-src 'self';",
+               InsecureValueWarning("default-src", "https://*.*.google.com/")));
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' https://*.*.google.com:*/",
+                  OPTIONS_ALLOW_UNSAFE_EVAL),
       "default-src 'self';",
       InsecureValueWarning("default-src", "https://*.*.google.com:*/")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' https://www.*.google.com/", OPTIONS_ALLOW_UNSAFE_EVAL),
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' https://www.*.google.com/",
+                  OPTIONS_ALLOW_UNSAFE_EVAL),
       "default-src 'self';",
       InsecureValueWarning("default-src", "https://www.*.google.com/")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' https://www.*.google.com:*/",
-      OPTIONS_ALLOW_UNSAFE_EVAL),
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' https://www.*.google.com:*/",
+                  OPTIONS_ALLOW_UNSAFE_EVAL),
       "default-src 'self';",
       InsecureValueWarning("default-src", "https://www.*.google.com:*/")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' chrome://*", OPTIONS_ALLOW_UNSAFE_EVAL),
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' chrome://*", OPTIONS_ALLOW_UNSAFE_EVAL),
       "default-src 'self';",
       InsecureValueWarning("default-src", "chrome://*")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' chrome-extension://*", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "chrome-extension://*")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' chrome-extension://", OPTIONS_ALLOW_UNSAFE_EVAL),
-      "default-src 'self';",
-      InsecureValueWarning("default-src", "chrome-extension://")));
+  EXPECT_TRUE(
+      CheckCSP(SanitizeCSP("default-src 'self' chrome-extension://*",
+                           OPTIONS_ALLOW_UNSAFE_EVAL),
+               "default-src 'self';",
+               InsecureValueWarning("default-src", "chrome-extension://*")));
+  EXPECT_TRUE(
+      CheckCSP(SanitizeCSP("default-src 'self' chrome-extension://",
+                           OPTIONS_ALLOW_UNSAFE_EVAL),
+               "default-src 'self';",
+               InsecureValueWarning("default-src", "chrome-extension://")));
 
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "default-src 'self' https://*.google.com;", OPTIONS_ALLOW_UNSAFE_EVAL)));
@@ -342,28 +354,30 @@
       "default-src 'self' http://127.0.0.1:9999;", OPTIONS_ALLOW_UNSAFE_EVAL)));
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "default-src 'self' http://localhost:8888;", OPTIONS_ALLOW_UNSAFE_EVAL)));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' http://127.0.0.1.example.com",
-      OPTIONS_ALLOW_UNSAFE_EVAL),
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' http://127.0.0.1.example.com",
+                  OPTIONS_ALLOW_UNSAFE_EVAL),
       "default-src 'self';",
       InsecureValueWarning("default-src", "http://127.0.0.1.example.com")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' http://localhost.example.com",
-      OPTIONS_ALLOW_UNSAFE_EVAL),
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' http://localhost.example.com",
+                  OPTIONS_ALLOW_UNSAFE_EVAL),
       "default-src 'self';",
       InsecureValueWarning("default-src", "http://localhost.example.com")));
 
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "default-src 'self' blob:;", OPTIONS_ALLOW_UNSAFE_EVAL)));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' blob:http://example.com/XXX",
-      OPTIONS_ALLOW_UNSAFE_EVAL), "default-src 'self';",
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' blob:http://example.com/XXX",
+                  OPTIONS_ALLOW_UNSAFE_EVAL),
+      "default-src 'self';",
       InsecureValueWarning("default-src", "blob:http://example.com/XXX")));
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "default-src 'self' filesystem:;", OPTIONS_ALLOW_UNSAFE_EVAL)));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src 'self' filesystem:http://example.com/XX",
-      OPTIONS_ALLOW_UNSAFE_EVAL), "default-src 'self';",
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src 'self' filesystem:http://example.com/XX",
+                  OPTIONS_ALLOW_UNSAFE_EVAL),
+      "default-src 'self';",
       InsecureValueWarning("default-src", "filesystem:http://example.com/XX")));
 
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
@@ -373,28 +387,27 @@
       "default-src 'self' https://x.googleapis.com;",
       OPTIONS_ALLOW_UNSAFE_EVAL)));
 
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "script-src 'self'; object-src *", OPTIONS_NONE),
-      "script-src 'self'; object-src;",
-      InsecureValueWarning("object-src", "*")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "script-src 'self'; object-src *", OPTIONS_ALLOW_INSECURE_OBJECT_SRC),
-      "script-src 'self'; object-src;",
-      InsecureValueWarning("object-src", "*")));
+  EXPECT_TRUE(
+      CheckCSP(SanitizeCSP("script-src 'self'; object-src *", OPTIONS_NONE),
+               "script-src 'self'; object-src;",
+               InsecureValueWarning("object-src", "*")));
+  EXPECT_TRUE(CheckCSP(SanitizeCSP("script-src 'self'; object-src *",
+                                   OPTIONS_ALLOW_INSECURE_OBJECT_SRC),
+                       "script-src 'self'; object-src;",
+                       InsecureValueWarning("object-src", "*")));
   EXPECT_TRUE(CheckCSP(SanitizeCSP(
       "script-src 'self'; object-src *; plugin-types application/pdf;",
       OPTIONS_ALLOW_INSECURE_OBJECT_SRC)));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "script-src 'self'; object-src *; "
-      "plugin-types application/x-shockwave-flash",
-      OPTIONS_ALLOW_INSECURE_OBJECT_SRC),
-      "script-src 'self'; object-src; "
-      "plugin-types application/x-shockwave-flash;",
-      InsecureValueWarning("object-src", "*")));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "script-src 'self'; object-src *; "
-      "plugin-types application/x-shockwave-flash application/pdf;",
-      OPTIONS_ALLOW_INSECURE_OBJECT_SRC),
+  EXPECT_TRUE(CheckCSP(SanitizeCSP("script-src 'self'; object-src *; "
+                                   "plugin-types application/x-shockwave-flash",
+                                   OPTIONS_ALLOW_INSECURE_OBJECT_SRC),
+                       "script-src 'self'; object-src; "
+                       "plugin-types application/x-shockwave-flash;",
+                       InsecureValueWarning("object-src", "*")));
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("script-src 'self'; object-src *; "
+                  "plugin-types application/x-shockwave-flash application/pdf;",
+                  OPTIONS_ALLOW_INSECURE_OBJECT_SRC),
       "script-src 'self'; object-src; "
       "plugin-types application/x-shockwave-flash application/pdf;",
       InsecureValueWarning("object-src", "*")));
@@ -410,9 +423,9 @@
       "script-src 'self'; object-src http://*.example.com; "
       "plugin-types application/pdf;",
       OPTIONS_ALLOW_INSECURE_OBJECT_SRC)));
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "script-src *; object-src *; plugin-types application/pdf;",
-      OPTIONS_ALLOW_INSECURE_OBJECT_SRC),
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("script-src *; object-src *; plugin-types application/pdf;",
+                  OPTIONS_ALLOW_INSECURE_OBJECT_SRC),
       "script-src; object-src *; plugin-types application/pdf;",
       InsecureValueWarning("script-src", "*")));
 
@@ -426,16 +439,20 @@
       OPTIONS_NONE)));
 
   // Reject non-standard algorithms, even if they are still supported by Blink.
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src; script-src 'sha1-eYyYGmKWdhpUewohaXk9o8IaLSw=';",
-      OPTIONS_NONE), "default-src; script-src;",
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP(
+          "default-src; script-src 'sha1-eYyYGmKWdhpUewohaXk9o8IaLSw=';",
+          OPTIONS_NONE),
+      "default-src; script-src;",
       InsecureValueWarning("script-src",
                            "'sha1-eYyYGmKWdhpUewohaXk9o8IaLSw='")));
 
-  EXPECT_TRUE(CheckCSP(SanitizeCSP(
-      "default-src; script-src 'sha256-hndjYvzUzy2Ykuad81Cwsl1FOXX/qYs/aDVyUyNZ"
-      "wBw= sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng=';",
-      OPTIONS_NONE), "default-src; script-src;",
+  EXPECT_TRUE(CheckCSP(
+      SanitizeCSP("default-src; script-src "
+                  "'sha256-hndjYvzUzy2Ykuad81Cwsl1FOXX/qYs/aDVyUyNZ"
+                  "wBw= sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng=';",
+                  OPTIONS_NONE),
+      "default-src; script-src;",
       InsecureValueWarning(
           "script-src", "'sha256-hndjYvzUzy2Ykuad81Cwsl1FOXX/qYs/aDVyUyNZwBw="),
       InsecureValueWarning(
@@ -478,17 +495,23 @@
 }
 
 TEST(ExtensionCSPValidator, EffectiveSandboxedPageCSP) {
+  auto insecure_value_warning = [](const std::string& directive,
+                                   const std::string& value) {
+    return InsecureValueWarning(directive, value,
+                                extensions::manifest_keys::kSandboxedPagesCSP);
+  };
+
   EXPECT_TRUE(CheckCSP(
       SanitizeSandboxPageCSP(""),
       "child-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval';"));
   EXPECT_TRUE(CheckCSP(
       SanitizeSandboxPageCSP("child-src http://www.google.com"),
       "child-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval';",
-      InsecureValueWarning("child-src", "http://www.google.com")));
+      insecure_value_warning("child-src", "http://www.google.com")));
   EXPECT_TRUE(CheckCSP(
       SanitizeSandboxPageCSP("child-src *"),
       "child-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval';",
-      InsecureValueWarning("child-src", "*")));
+      insecure_value_warning("child-src", "*")));
   EXPECT_TRUE(CheckCSP(
       SanitizeSandboxPageCSP("child-src 'none'"),
       "child-src 'none'; script-src 'self' 'unsafe-inline' 'unsafe-eval';"));
@@ -501,7 +524,7 @@
       SanitizeSandboxPageCSP(
           "script-src 'none'; frame-src 'self' http://www.google.com;"),
       "frame-src 'self'; script-src 'none';",
-      InsecureValueWarning("frame-src", "http://www.google.com")));
+      insecure_value_warning("frame-src", "http://www.google.com")));
 
   // script-src will add 'unsafe-inline' and 'unsafe-eval' only if script-src is
   // not specified.
@@ -521,20 +544,20 @@
       SanitizeSandboxPageCSP(
           "script-src 'none'; frame-src 'self' http://www.google.com;"),
       "frame-src 'self'; script-src 'none';",
-      InsecureValueWarning("frame-src", "http://www.google.com")));
+      insecure_value_warning("frame-src", "http://www.google.com")));
   EXPECT_TRUE(CheckCSP(
       SanitizeSandboxPageCSP(
           "script-src 'none'; child-src 'self' http://www.google.com;"),
       "child-src 'self'; script-src 'none';",
-      InsecureValueWarning("child-src", "http://www.google.com")));
+      insecure_value_warning("child-src", "http://www.google.com")));
 
   // Multiple insecure values.
   EXPECT_TRUE(CheckCSP(
       SanitizeSandboxPageCSP(
           "script-src 'none'; child-src http://bar.com 'self' http://foo.com;"),
       "child-src 'self'; script-src 'none';",
-      InsecureValueWarning("child-src", "http://bar.com"),
-      InsecureValueWarning("child-src", "http://foo.com")));
+      insecure_value_warning("child-src", "http://bar.com"),
+      insecure_value_warning("child-src", "http://foo.com")));
 }
 
 namespace extensions {
diff --git a/extensions/common/manifest_constants.cc b/extensions/common/manifest_constants.cc
index b44d3e1..7b198aae 100644
--- a/extensions/common/manifest_constants.cc
+++ b/extensions/common/manifest_constants.cc
@@ -358,10 +358,10 @@
 const char kInvalidContentScriptsList[] =
     "Invalid value for 'content_scripts'.";
 const char kInvalidCSPInsecureValue[] =
-    "Ignored insecure CSP value \"*\" in directive '*'.";
+    "'*': Ignored insecure CSP value \"*\" in directive '*'.";
 const char kInvalidCSPMissingSecureSrc[] =
-    "CSP directive '*' must be specified (either explicitly, or implicitly via"
-    " 'default-src') and must whitelist only secure resources.";
+    "'*': CSP directive '*' must be specified (either explicitly, or "
+    "implicitly via 'default-src') and must whitelist only secure resources.";
 const char kInvalidCss[] =
     "Invalid value for 'content_scripts[*].css[*]'.";
 const char kInvalidCssList[] =
diff --git a/extensions/common/manifest_constants.h b/extensions/common/manifest_constants.h
index a9615a3..761075a 100644
--- a/extensions/common/manifest_constants.h
+++ b/extensions/common/manifest_constants.h
@@ -470,7 +470,6 @@
 extern const char kInvalidWebURL[];
 extern const char kInvalidWebURLs[];
 extern const char kInvalidZipHash[];
-extern const char kInsecureContentSecurityPolicy[];
 extern const char kKeyIsDeprecatedWithReplacement[];
 extern const char kLauncherPagePageRequired[];
 extern const char kLaunchPathAndExtentAreExclusive[];
diff --git a/extensions/common/manifest_handlers/csp_info.cc b/extensions/common/manifest_handlers/csp_info.cc
index 39b6ac9c..1a55589 100644
--- a/extensions/common/manifest_handlers/csp_info.cc
+++ b/extensions/common/manifest_handlers/csp_info.cc
@@ -189,7 +189,7 @@
     base::StringPiece manifest_key,
     const base::Value* content_security_policy) {
   if (!content_security_policy)
-    return SetDefaultExtensionPagesCSP(extension);
+    return SetDefaultExtensionPagesCSP(extension, manifest_key);
 
   if (!content_security_policy->is_string()) {
     *error = GetInvalidManifestKeyError(manifest_key);
@@ -208,7 +208,8 @@
   // extension provided csp value and raising install warnings, see if we want
   // to raise errors and prevent the extension from loading.
   std::string sanitized_content_security_policy = SanitizeContentSecurityPolicy(
-      content_security_policy_str, GetValidatorOptions(extension), &warnings);
+      content_security_policy_str, manifest_key.as_string(),
+      GetValidatorOptions(extension), &warnings);
   extension->AddInstallWarnings(std::move(warnings));
 
   extension->SetManifestData(
@@ -241,13 +242,15 @@
 
   std::vector<InstallWarning> warnings;
   std::string effective_sandbox_csp =
-      csp_validator::GetEffectiveSandoxedPageCSP(sandbox_csp_str, &warnings);
+      csp_validator::GetEffectiveSandoxedPageCSP(
+          sandbox_csp_str, manifest_key.as_string(), &warnings);
   SetSandboxCSP(extension, std::move(effective_sandbox_csp));
   extension->AddInstallWarnings(std::move(warnings));
   return true;
 }
 
-bool CSPHandler::SetDefaultExtensionPagesCSP(Extension* extension) {
+bool CSPHandler::SetDefaultExtensionPagesCSP(Extension* extension,
+                                             base::StringPiece manifest_key) {
   // TODO(abarth): Should we continue to let extensions override the
   //               default Content-Security-Policy?
   const char* content_security_policy =
@@ -255,10 +258,10 @@
           ? kDefaultPlatformAppContentSecurityPolicy
           : kDefaultContentSecurityPolicy;
 
-  DCHECK_EQ(
-      content_security_policy,
-      SanitizeContentSecurityPolicy(content_security_policy,
-                                    GetValidatorOptions(extension), nullptr));
+  DCHECK_EQ(content_security_policy,
+            SanitizeContentSecurityPolicy(
+                content_security_policy, manifest_key.as_string(),
+                GetValidatorOptions(extension), nullptr));
   extension->SetManifestData(
       keys::kContentSecurityPolicy,
       std::make_unique<CSPInfo>(content_security_policy));
diff --git a/extensions/common/manifest_handlers/csp_info.h b/extensions/common/manifest_handlers/csp_info.h
index b4c219f..8b4e7d1 100644
--- a/extensions/common/manifest_handlers/csp_info.h
+++ b/extensions/common/manifest_handlers/csp_info.h
@@ -76,7 +76,8 @@
                        const base::Value* sandbox_csp);
 
   // Sets the default CSP value for the extension.
-  bool SetDefaultExtensionPagesCSP(Extension* extension);
+  bool SetDefaultExtensionPagesCSP(Extension* extension,
+                                   base::StringPiece manifest_key);
 
   // Helper to set the sandbox content security policy manifest data.
   void SetSandboxCSP(Extension* extension, std::string sandbox_csp);
diff --git a/webrunner/BUILD.gn b/fuchsia/BUILD.gn
similarity index 96%
rename from webrunner/BUILD.gn
rename to fuchsia/BUILD.gn
index 8f973d0..7c93edd 100644
--- a/webrunner/BUILD.gn
+++ b/fuchsia/BUILD.gn
@@ -215,9 +215,9 @@
 
 source_set("mem_buffer_common") {
   sources = [
+    "common/fuchsia_export.h",
     "common/mem_buffer_util.cc",
     "common/mem_buffer_util.h",
-    "common/webrunner_export.h",
   ]
   deps = [
     "//base",
@@ -297,6 +297,10 @@
   binary = ":service_exe"
   package_name_override = "chromium"
   sandbox_policy = "service/sandbox_policy"
+  excluded_files = [
+    "lib/libswiftshader_libEGL.so",
+    "lib/libswiftshader_libGLESv2.so",
+  ]
 }
 
 executable("service_exe") {
@@ -587,7 +591,7 @@
     "license_file",
     rebase_path(_license_path, root_build_dir),
     "--gn-target",
-    "//webrunner:webrunner_pkg",
+    "//fuchsia:webrunner_pkg",
     "--gn-out-dir",
     ".",
   ]
@@ -611,11 +615,11 @@
 # Puts copies of files at the top level of the CIPD archive's structure.
 copy("restaged_packages") {
   sources = [
-    "$root_gen_dir/webrunner/application_config_manager/application_config_manager.far",
-    "$root_gen_dir/webrunner/cast_runner/cast_runner.far",
-    "$root_gen_dir/webrunner/chromium/chromium.far",
-    "$root_gen_dir/webrunner/net_http/http/http.far",
-    "$root_gen_dir/webrunner/web_runner/web_runner.far",
+    "$root_gen_dir/fuchsia/application_config_manager/application_config_manager.far",
+    "$root_gen_dir/fuchsia/cast_runner/cast_runner.far",
+    "$root_gen_dir/fuchsia/chromium/chromium.far",
+    "$root_gen_dir/fuchsia/net_http/http/http.far",
+    "$root_gen_dir/fuchsia/web_runner/web_runner.far",
   ]
   outputs = [
     "$_artifact_root/{{source_file_part}}",
diff --git a/webrunner/DEPS b/fuchsia/DEPS
similarity index 100%
rename from webrunner/DEPS
rename to fuchsia/DEPS
diff --git a/webrunner/OWNERS b/fuchsia/OWNERS
similarity index 100%
rename from webrunner/OWNERS
rename to fuchsia/OWNERS
diff --git a/webrunner/README.md b/fuchsia/README.md
similarity index 100%
rename from webrunner/README.md
rename to fuchsia/README.md
diff --git a/webrunner/app/cast/application_config_manager/application_config_manager.cc b/fuchsia/app/cast/application_config_manager/application_config_manager.cc
similarity index 91%
rename from webrunner/app/cast/application_config_manager/application_config_manager.cc
rename to fuchsia/app/cast/application_config_manager/application_config_manager.cc
index f0aaada..62c0bb1c 100644
--- a/webrunner/app/cast/application_config_manager/application_config_manager.cc
+++ b/fuchsia/app/cast/application_config_manager/application_config_manager.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/app/cast/application_config_manager/application_config_manager.h"
+#include "fuchsia/app/cast/application_config_manager/application_config_manager.h"
 
 #include "base/logging.h"
 
diff --git a/webrunner/app/cast/application_config_manager/application_config_manager.h b/fuchsia/app/cast/application_config_manager/application_config_manager.h
similarity index 72%
rename from webrunner/app/cast/application_config_manager/application_config_manager.h
rename to fuchsia/app/cast/application_config_manager/application_config_manager.h
index e760190..64b8232e 100644
--- a/webrunner/app/cast/application_config_manager/application_config_manager.h
+++ b/fuchsia/app/cast/application_config_manager/application_config_manager.h
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_APP_CAST_APPLICATION_CONFIG_MANAGER_APPLICATION_CONFIG_MANAGER_H_
-#define WEBRUNNER_APP_CAST_APPLICATION_CONFIG_MANAGER_APPLICATION_CONFIG_MANAGER_H_
+#ifndef FUCHSIA_APP_CAST_APPLICATION_CONFIG_MANAGER_APPLICATION_CONFIG_MANAGER_H_
+#define FUCHSIA_APP_CAST_APPLICATION_CONFIG_MANAGER_APPLICATION_CONFIG_MANAGER_H_
 
-#include <webrunner/fidl/chromium/cast/cpp/fidl.h>
+#include <fuchsia/fidl/chromium/cast/cpp/fidl.h>
 
 #include "base/macros.h"
 
@@ -28,4 +28,4 @@
 
 }  // namespace castrunner
 
-#endif  // WEBRUNNER_APP_CAST_APPLICATION_CONFIG_MANAGER_APPLICATION_CONFIG_MANAGER_H_
+#endif  // FUCHSIA_APP_CAST_APPLICATION_CONFIG_MANAGER_APPLICATION_CONFIG_MANAGER_H_
diff --git a/webrunner/app/cast/application_config_manager/application_config_manager_unittest.cc b/fuchsia/app/cast/application_config_manager/application_config_manager_unittest.cc
similarity index 96%
rename from webrunner/app/cast/application_config_manager/application_config_manager_unittest.cc
rename to fuchsia/app/cast/application_config_manager/application_config_manager_unittest.cc
index 06a5e69d..0d7fbda 100644
--- a/webrunner/app/cast/application_config_manager/application_config_manager_unittest.cc
+++ b/fuchsia/app/cast/application_config_manager/application_config_manager_unittest.cc
@@ -7,8 +7,8 @@
 
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
+#include "fuchsia/app/cast/application_config_manager/application_config_manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "webrunner/app/cast/application_config_manager/application_config_manager.h"
 
 namespace castrunner {
 
diff --git a/webrunner/app/cast/application_config_manager/main.cc b/fuchsia/app/cast/application_config_manager/main.cc
similarity index 92%
rename from webrunner/app/cast/application_config_manager/main.cc
rename to fuchsia/app/cast/application_config_manager/main.cc
index f752ad49..0e53daae2 100644
--- a/webrunner/app/cast/application_config_manager/main.cc
+++ b/fuchsia/app/cast/application_config_manager/main.cc
@@ -7,7 +7,7 @@
 #include "base/fuchsia/service_directory.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
-#include "webrunner/app/cast/application_config_manager/application_config_manager.h"
+#include "fuchsia/app/cast/application_config_manager/application_config_manager.h"
 
 int main(int argc, char** argv) {
   base::MessageLoopForIO message_loop;
diff --git a/webrunner/app/cast/application_config_manager/sandbox_policy b/fuchsia/app/cast/application_config_manager/sandbox_policy
similarity index 100%
rename from webrunner/app/cast/application_config_manager/sandbox_policy
rename to fuchsia/app/cast/application_config_manager/sandbox_policy
diff --git a/webrunner/app/cast/application_config_manager/test/fake_application_config_manager.cc b/fuchsia/app/cast/application_config_manager/test/fake_application_config_manager.cc
similarity index 92%
rename from webrunner/app/cast/application_config_manager/test/fake_application_config_manager.cc
rename to fuchsia/app/cast/application_config_manager/test/fake_application_config_manager.cc
index 75a2b50..1f5fddce 100644
--- a/webrunner/app/cast/application_config_manager/test/fake_application_config_manager.cc
+++ b/fuchsia/app/cast/application_config_manager/test/fake_application_config_manager.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/app/cast/application_config_manager/test/fake_application_config_manager.h"
+#include "fuchsia/app/cast/application_config_manager/test/fake_application_config_manager.h"
 
 #include "base/logging.h"
 
diff --git a/webrunner/app/cast/application_config_manager/test/fake_application_config_manager.h b/fuchsia/app/cast/application_config_manager/test/fake_application_config_manager.h
similarity index 74%
rename from webrunner/app/cast/application_config_manager/test/fake_application_config_manager.h
rename to fuchsia/app/cast/application_config_manager/test/fake_application_config_manager.h
index 6d275e7..7a490b4 100644
--- a/webrunner/app/cast/application_config_manager/test/fake_application_config_manager.h
+++ b/fuchsia/app/cast/application_config_manager/test/fake_application_config_manager.h
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_APP_CAST_APPLICATION_CONFIG_MANAGER_TEST_FAKE_APPLICATION_CONFIG_MANAGER_H_
-#define WEBRUNNER_APP_CAST_APPLICATION_CONFIG_MANAGER_TEST_FAKE_APPLICATION_CONFIG_MANAGER_H_
+#ifndef FUCHSIA_APP_CAST_APPLICATION_CONFIG_MANAGER_TEST_FAKE_APPLICATION_CONFIG_MANAGER_H_
+#define FUCHSIA_APP_CAST_APPLICATION_CONFIG_MANAGER_TEST_FAKE_APPLICATION_CONFIG_MANAGER_H_
 
-#include <webrunner/fidl/chromium/cast/cpp/fidl.h>
+#include <fuchsia/fidl/chromium/cast/cpp/fidl.h>
 
 #include "base/macros.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
@@ -36,4 +36,4 @@
 }  // namespace test
 }  // namespace castrunner
 
-#endif  // WEBRUNNER_APP_CAST_APPLICATION_CONFIG_MANAGER_TEST_FAKE_APPLICATION_CONFIG_MANAGER_H_
+#endif  // FUCHSIA_APP_CAST_APPLICATION_CONFIG_MANAGER_TEST_FAKE_APPLICATION_CONFIG_MANAGER_H_
diff --git a/webrunner/app/cast/bindings/cast_channel.cc b/fuchsia/app/cast/bindings/cast_channel.cc
similarity index 92%
rename from webrunner/app/cast/bindings/cast_channel.cc
rename to fuchsia/app/cast/bindings/cast_channel.cc
index e5a6af1..99980cb 100644
--- a/webrunner/app/cast/bindings/cast_channel.cc
+++ b/fuchsia/app/cast/bindings/cast_channel.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/app/cast/bindings/cast_channel.h"
+#include "fuchsia/app/cast/bindings/cast_channel.h"
 
 #include <lib/fit/function.h>
 #include <string>
@@ -15,9 +15,9 @@
 #include "base/macros.h"
 #include "base/path_service.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "webrunner/common/mem_buffer_util.h"
-#include "webrunner/common/named_message_port_connector.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/common/named_message_port_connector.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 
 namespace castrunner {
 
@@ -43,7 +43,7 @@
   base::FilePath assets_path;
   CHECK(base::PathService::Get(base::DIR_ASSETS, &assets_path));
   fuchsia::mem::Buffer bindings_buf = webrunner::MemBufferFromFile(base::File(
-      assets_path.AppendASCII("webrunner/app/cast/bindings/cast_channel.js"),
+      assets_path.AppendASCII("fuchsia/app/cast/bindings/cast_channel.js"),
       base::File::FLAG_OPEN | base::File::FLAG_READ));
   CHECK(bindings_buf.vmo);
 
diff --git a/webrunner/app/cast/bindings/cast_channel.h b/fuchsia/app/cast/bindings/cast_channel.h
similarity index 84%
rename from webrunner/app/cast/bindings/cast_channel.h
rename to fuchsia/app/cast/bindings/cast_channel.h
index 04bbd0f..f7bb683 100644
--- a/webrunner/app/cast/bindings/cast_channel.h
+++ b/fuchsia/app/cast/bindings/cast_channel.h
@@ -2,17 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_APP_CAST_BINDINGS_CAST_CHANNEL_H_
-#define WEBRUNNER_APP_CAST_BINDINGS_CAST_CHANNEL_H_
+#ifndef FUCHSIA_APP_CAST_BINDINGS_CAST_CHANNEL_H_
+#define FUCHSIA_APP_CAST_BINDINGS_CAST_CHANNEL_H_
 
 #include <string>
 
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/strings/string_piece.h"
-#include "webrunner/common/webrunner_export.h"
-#include "webrunner/fidl/chromium/cast/cpp/fidl.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/common/fuchsia_export.h"
+#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 
 namespace webrunner {
 class NamedMessagePortConnector;
@@ -23,7 +23,7 @@
 // Handles the injection of cast.__platform__.channel bindings into pages'
 // scripting context, and establishes a bidirectional message pipe over
 // which the two communicate.
-class WEBRUNNER_EXPORT CastChannelImpl : public chromium::cast::CastChannel {
+class FUCHSIA_EXPORT CastChannelImpl : public chromium::cast::CastChannel {
  public:
   // Attaches CastChannel bindings and port to a |frame|.
   // |frame|: The frame to be provided with a CastChannel.
@@ -68,4 +68,4 @@
 
 }  // namespace castrunner
 
-#endif  // WEBRUNNER_APP_CAST_BINDINGS_CAST_CHANNEL_H_
+#endif  // FUCHSIA_APP_CAST_BINDINGS_CAST_CHANNEL_H_
diff --git a/webrunner/app/cast/bindings/cast_channel.js b/fuchsia/app/cast/bindings/cast_channel.js
similarity index 100%
rename from webrunner/app/cast/bindings/cast_channel.js
rename to fuchsia/app/cast/bindings/cast_channel.js
diff --git a/webrunner/app/cast/bindings/cast_channel_browsertest.cc b/fuchsia/app/cast/bindings/cast_channel_browsertest.cc
similarity index 94%
rename from webrunner/app/cast/bindings/cast_channel_browsertest.cc
rename to fuchsia/app/cast/bindings/cast_channel_browsertest.cc
index eec926a..9c146bc 100644
--- a/webrunner/app/cast/bindings/cast_channel_browsertest.cc
+++ b/fuchsia/app/cast/bindings/cast_channel_browsertest.cc
@@ -13,12 +13,12 @@
 #include "base/threading/thread_restrictions.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/url_constants.h"
-#include "webrunner/app/cast/bindings/cast_channel.h"
-#include "webrunner/common/mem_buffer_util.h"
-#include "webrunner/common/named_message_port_connector.h"
-#include "webrunner/common/test/test_common.h"
-#include "webrunner/common/test/webrunner_browser_test.h"
-#include "webrunner/test/promise.h"
+#include "fuchsia/app/cast/bindings/cast_channel.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/common/named_message_port_connector.h"
+#include "fuchsia/common/test/test_common.h"
+#include "fuchsia/common/test/webrunner_browser_test.h"
+#include "fuchsia/test/promise.h"
 
 namespace castrunner {
 
@@ -30,7 +30,7 @@
                             public chromium::web::NavigationEventObserver {
  public:
   CastChannelImplTest() : run_timeout_(TestTimeouts::action_timeout()) {
-    set_test_server_root(base::FilePath("webrunner/app/cast/test/data"));
+    set_test_server_root(base::FilePath("fuchsia/app/cast/test/data"));
   }
 
   ~CastChannelImplTest() override = default;
diff --git a/webrunner/app/cast/cast_runner.cc b/fuchsia/app/cast/cast_runner.cc
similarity index 96%
rename from webrunner/app/cast/cast_runner.cc
rename to fuchsia/app/cast/cast_runner.cc
index 86f32cf..8482ace 100644
--- a/webrunner/app/cast/cast_runner.cc
+++ b/fuchsia/app/cast/cast_runner.cc
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/app/cast/cast_runner.h"
+#include "fuchsia/app/cast/cast_runner.h"
 
 #include <fuchsia/sys/cpp/fidl.h>
 #include <utility>
 
 #include "base/logging.h"
+#include "fuchsia/app/common/web_component.h"
 #include "url/gurl.h"
-#include "webrunner/app/common/web_component.h"
 
 namespace castrunner {
 
diff --git a/webrunner/app/cast/cast_runner.h b/fuchsia/app/cast/cast_runner.h
similarity index 82%
rename from webrunner/app/cast/cast_runner.h
rename to fuchsia/app/cast/cast_runner.h
index a90f710b00..47cbe92 100644
--- a/webrunner/app/cast/cast_runner.h
+++ b/fuchsia/app/cast/cast_runner.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_APP_CAST_CAST_RUNNER_H_
-#define WEBRUNNER_APP_CAST_CAST_RUNNER_H_
+#ifndef FUCHSIA_APP_CAST_CAST_RUNNER_H_
+#define FUCHSIA_APP_CAST_CAST_RUNNER_H_
 
 #include "base/callback.h"
 #include "base/macros.h"
-#include "webrunner/app/common/web_content_runner.h"
-#include "webrunner/fidl/chromium/cast/cpp/fidl.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/app/common/web_content_runner.h"
+#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 
 namespace castrunner {
 
@@ -43,4 +43,4 @@
 
 }  // namespace castrunner
 
-#endif  // WEBRUNNER_APP_CAST_CAST_RUNNER_H_
+#endif  // FUCHSIA_APP_CAST_CAST_RUNNER_H_
diff --git a/webrunner/app/cast/cast_runner_integration_test.cc b/fuchsia/app/cast/cast_runner_integration_test.cc
similarity index 94%
rename from webrunner/app/cast/cast_runner_integration_test.cc
rename to fuchsia/app/cast/cast_runner_integration_test.cc
index ec7cc35..80ebc6a 100644
--- a/webrunner/app/cast/cast_runner_integration_test.cc
+++ b/fuchsia/app/cast/cast_runner_integration_test.cc
@@ -11,13 +11,13 @@
 #include "base/message_loop/message_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/test_timeouts.h"
+#include "fuchsia/app/cast/application_config_manager/test/fake_application_config_manager.h"
+#include "fuchsia/app/cast/cast_runner.h"
+#include "fuchsia/app/cast/test_common.h"
+#include "fuchsia/app/common/web_component.h"
+#include "fuchsia/app/common/web_content_runner.h"
+#include "fuchsia/test/promise.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "webrunner/app/cast/application_config_manager/test/fake_application_config_manager.h"
-#include "webrunner/app/cast/cast_runner.h"
-#include "webrunner/app/cast/test_common.h"
-#include "webrunner/app/common/web_component.h"
-#include "webrunner/app/common/web_content_runner.h"
-#include "webrunner/test/promise.h"
 
 namespace castrunner {
 
diff --git a/webrunner/app/cast/cast_runner_unittest.cc b/fuchsia/app/cast/cast_runner_unittest.cc
similarity index 95%
rename from webrunner/app/cast/cast_runner_unittest.cc
rename to fuchsia/app/cast/cast_runner_unittest.cc
index eb69b1f..c10ba17 100644
--- a/webrunner/app/cast/cast_runner_unittest.cc
+++ b/fuchsia/app/cast/cast_runner_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/app/cast/cast_runner.h"
+#include "fuchsia/app/cast/cast_runner.h"
 
 #include <lib/fidl/cpp/binding.h>
 #include <lib/zx/channel.h>
@@ -13,10 +13,10 @@
 #include "base/fuchsia/service_directory.h"
 #include "base/message_loop/message_loop.h"
 #include "base/strings/stringprintf.h"
+#include "fuchsia/app/cast/application_config_manager/test/fake_application_config_manager.h"
+#include "fuchsia/app/cast/test_common.h"
+#include "fuchsia/test/fake_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "webrunner/app/cast/application_config_manager/test/fake_application_config_manager.h"
-#include "webrunner/app/cast/test_common.h"
-#include "webrunner/test/fake_context.h"
 
 namespace castrunner {
 
diff --git a/webrunner/app/cast/main.cc b/fuchsia/app/cast/main.cc
similarity index 94%
rename from webrunner/app/cast/main.cc
rename to fuchsia/app/cast/main.cc
index 14844f2..73ff569 100644
--- a/webrunner/app/cast/main.cc
+++ b/fuchsia/app/cast/main.cc
@@ -6,7 +6,7 @@
 #include "base/fuchsia/service_directory.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
-#include "webrunner/app/cast/cast_runner.h"
+#include "fuchsia/app/cast/cast_runner.h"
 
 int main(int argc, char** argv) {
   base::MessageLoopForIO message_loop;
diff --git a/webrunner/app/cast/sandbox_policy b/fuchsia/app/cast/sandbox_policy
similarity index 100%
rename from webrunner/app/cast/sandbox_policy
rename to fuchsia/app/cast/sandbox_policy
diff --git a/webrunner/app/cast/test/data/cast_channel.html b/fuchsia/app/cast/test/data/cast_channel.html
similarity index 100%
rename from webrunner/app/cast/test/data/cast_channel.html
rename to fuchsia/app/cast/test/data/cast_channel.html
diff --git a/webrunner/app/cast/test/data/cast_channel_reconnect.html b/fuchsia/app/cast/test/data/cast_channel_reconnect.html
similarity index 100%
rename from webrunner/app/cast/test/data/cast_channel_reconnect.html
rename to fuchsia/app/cast/test/data/cast_channel_reconnect.html
diff --git a/webrunner/app/cast/test_common.cc b/fuchsia/app/cast/test_common.cc
similarity index 96%
rename from webrunner/app/cast/test_common.cc
rename to fuchsia/app/cast/test_common.cc
index c1f5f02..cf134bb 100644
--- a/webrunner/app/cast/test_common.cc
+++ b/fuchsia/app/cast/test_common.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/app/cast/test_common.h"
+#include "fuchsia/app/cast/test_common.h"
 
 #include "base/fuchsia/fuchsia_logging.h"
 
diff --git a/webrunner/app/cast/test_common.h b/fuchsia/app/cast/test_common.h
similarity index 83%
rename from webrunner/app/cast/test_common.h
rename to fuchsia/app/cast/test_common.h
index daf93cd9..f60295e 100644
--- a/webrunner/app/cast/test_common.h
+++ b/fuchsia/app/cast/test_common.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_APP_CAST_TEST_COMMON_H_
-#define WEBRUNNER_APP_CAST_TEST_COMMON_H_
+#ifndef FUCHSIA_APP_CAST_TEST_COMMON_H_
+#define FUCHSIA_APP_CAST_TEST_COMMON_H_
 
 #include <fuchsia/sys/cpp/fidl.h>
 
@@ -21,4 +21,4 @@
 
 }  // namespace castrunner
 
-#endif  // WEBRUNNER_APP_CAST_TEST_COMMON_H_
\ No newline at end of file
+#endif  // FUCHSIA_APP_CAST_TEST_COMMON_H_
\ No newline at end of file
diff --git a/webrunner/app/common/web_component.cc b/fuchsia/app/common/web_component.cc
similarity index 92%
rename from webrunner/app/common/web_component.cc
rename to fuchsia/app/common/web_component.cc
index 0c451c3cf..0c94772 100644
--- a/webrunner/app/common/web_component.cc
+++ b/fuchsia/app/common/web_component.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/app/common/web_component.h"
+#include "fuchsia/app/common/web_component.h"
 
 #include <fuchsia/sys/cpp/fidl.h>
 #include <lib/fidl/cpp/binding_set.h>
@@ -12,8 +12,8 @@
 #include "base/fuchsia/scoped_service_binding.h"
 #include "base/fuchsia/service_directory.h"
 #include "base/logging.h"
-#include "webrunner/app/common/web_content_runner.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/app/common/web_content_runner.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 
 namespace webrunner {
 
@@ -36,7 +36,9 @@
   chromium::web::NavigationControllerPtr navigation_controller;
   component->frame()->GetNavigationController(
       navigation_controller.NewRequest());
-  navigation_controller->LoadUrl(url.spec(), nullptr);
+  auto params = std::make_unique<chromium::web::LoadUrlParams>();
+  params->was_activated = true;
+  navigation_controller->LoadUrl(url.spec(), std::move(params));
   return component;
 }
 
diff --git a/webrunner/app/common/web_component.h b/fuchsia/app/common/web_component.h
similarity index 94%
rename from webrunner/app/common/web_component.h
rename to fuchsia/app/common/web_component.h
index b53681c..be0ae57 100644
--- a/webrunner/app/common/web_component.h
+++ b/fuchsia/app/common/web_component.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_APP_COMMON_WEB_COMPONENT_H_
-#define WEBRUNNER_APP_COMMON_WEB_COMPONENT_H_
+#ifndef FUCHSIA_APP_COMMON_WEB_COMPONENT_H_
+#define FUCHSIA_APP_COMMON_WEB_COMPONENT_H_
 
 #include <fuchsia/sys/cpp/fidl.h>
 #include <fuchsia/ui/app/cpp/fidl.h>
@@ -17,8 +17,8 @@
 #include "base/fuchsia/scoped_service_binding.h"
 #include "base/fuchsia/service_directory.h"
 #include "base/logging.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 #include "url/gurl.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
 
 namespace webrunner {
 
@@ -111,4 +111,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_APP_COMMON_COMPONENT_CONTROLLER_IMPL_H_
+#endif  // FUCHSIA_APP_COMMON_COMPONENT_CONTROLLER_IMPL_H_
diff --git a/webrunner/app/common/web_content_runner.cc b/fuchsia/app/common/web_content_runner.cc
similarity index 95%
rename from webrunner/app/common/web_content_runner.cc
rename to fuchsia/app/common/web_content_runner.cc
index 6e37d5f..3d78e56 100644
--- a/webrunner/app/common/web_content_runner.cc
+++ b/fuchsia/app/common/web_content_runner.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/app/common/web_content_runner.h"
+#include "fuchsia/app/common/web_content_runner.h"
 
 #include <fuchsia/sys/cpp/fidl.h>
 #include <lib/fidl/cpp/binding_set.h>
@@ -15,9 +15,9 @@
 #include "base/fuchsia/scoped_service_binding.h"
 #include "base/fuchsia/service_directory.h"
 #include "base/logging.h"
+#include "fuchsia/app/common/web_component.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 #include "url/gurl.h"
-#include "webrunner/app/common/web_component.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
 
 namespace webrunner {
 
diff --git a/webrunner/app/common/web_content_runner.h b/fuchsia/app/common/web_content_runner.h
similarity index 93%
rename from webrunner/app/common/web_content_runner.h
rename to fuchsia/app/common/web_content_runner.h
index df1499d..50db91f 100644
--- a/webrunner/app/common/web_content_runner.h
+++ b/fuchsia/app/common/web_content_runner.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_APP_COMMON_WEB_CONTENT_RUNNER_H_
-#define WEBRUNNER_APP_COMMON_WEB_CONTENT_RUNNER_H_
+#ifndef FUCHSIA_APP_COMMON_WEB_CONTENT_RUNNER_H_
+#define FUCHSIA_APP_COMMON_WEB_CONTENT_RUNNER_H_
 
 #include <fuchsia/sys/cpp/fidl.h>
 #include <memory>
@@ -14,7 +14,7 @@
 #include "base/fuchsia/scoped_service_binding.h"
 #include "base/fuchsia/service_directory.h"
 #include "base/macros.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 
 namespace webrunner {
 
@@ -82,4 +82,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_APP_COMMON_WEB_CONTENT_RUNNER_H_
+#endif  // FUCHSIA_APP_COMMON_WEB_CONTENT_RUNNER_H_
diff --git a/webrunner/app/web/main.cc b/fuchsia/app/web/main.cc
similarity index 92%
rename from webrunner/app/web/main.cc
rename to fuchsia/app/web/main.cc
index 29ba321..b640ce0 100644
--- a/webrunner/app/web/main.cc
+++ b/fuchsia/app/web/main.cc
@@ -5,7 +5,7 @@
 #include "base/fuchsia/service_directory.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
-#include "webrunner/app/common/web_content_runner.h"
+#include "fuchsia/app/common/web_content_runner.h"
 
 int main(int argc, char** argv) {
   base::MessageLoopForIO message_loop;
diff --git a/webrunner/app/web/sandbox_policy b/fuchsia/app/web/sandbox_policy
similarity index 100%
rename from webrunner/app/web/sandbox_policy
rename to fuchsia/app/web/sandbox_policy
diff --git a/webrunner/app/web/webrunner_smoke_test.cc b/fuchsia/app/web/webrunner_smoke_test.cc
similarity index 100%
rename from webrunner/app/web/webrunner_smoke_test.cc
rename to fuchsia/app/web/webrunner_smoke_test.cc
diff --git a/webrunner/browser/DEPS b/fuchsia/browser/DEPS
similarity index 100%
rename from webrunner/browser/DEPS
rename to fuchsia/browser/DEPS
diff --git a/webrunner/browser/context_impl.cc b/fuchsia/browser/context_impl.cc
similarity index 95%
rename from webrunner/browser/context_impl.cc
rename to fuchsia/browser/context_impl.cc
index c3b1ad8..d1f471ad 100644
--- a/webrunner/browser/context_impl.cc
+++ b/fuchsia/browser/context_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/browser/context_impl.h"
+#include "fuchsia/browser/context_impl.h"
 
 #include <lib/zx/object.h>
 #include <memory>
@@ -10,7 +10,7 @@
 
 #include "base/fuchsia/fuchsia_logging.h"
 #include "content/public/browser/web_contents.h"
-#include "webrunner/browser/frame_impl.h"
+#include "fuchsia/browser/frame_impl.h"
 
 namespace webrunner {
 
diff --git a/webrunner/browser/context_impl.h b/fuchsia/browser/context_impl.h
similarity index 83%
rename from webrunner/browser/context_impl.h
rename to fuchsia/browser/context_impl.h
index f46701f..cfa95da 100644
--- a/webrunner/browser/context_impl.h
+++ b/fuchsia/browser/context_impl.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_BROWSER_CONTEXT_IMPL_H_
-#define WEBRUNNER_BROWSER_CONTEXT_IMPL_H_
+#ifndef FUCHSIA_BROWSER_CONTEXT_IMPL_H_
+#define FUCHSIA_BROWSER_CONTEXT_IMPL_H_
 
 #include <lib/fidl/cpp/binding_set.h>
 #include <memory>
@@ -11,8 +11,8 @@
 
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/macros.h"
-#include "webrunner/common/webrunner_export.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/common/fuchsia_export.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 
 namespace content {
 class BrowserContext;
@@ -22,10 +22,10 @@
 
 class FrameImpl;
 
-// Implementation of Context from //webrunner/fidl/context.fidl.
+// Implementation of Context from //fuchsia/fidl/context.fidl.
 // Owns a BrowserContext instance and uses it to create new WebContents/Frames.
 // All created Frames are owned by this object.
-class WEBRUNNER_EXPORT ContextImpl : public chromium::web::Context {
+class FUCHSIA_EXPORT ContextImpl : public chromium::web::Context {
  public:
   // |browser_context| must outlive ContextImpl.
   explicit ContextImpl(content::BrowserContext* browser_context);
@@ -66,4 +66,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_BROWSER_CONTEXT_IMPL_H_
+#endif  // FUCHSIA_BROWSER_CONTEXT_IMPL_H_
diff --git a/webrunner/browser/context_impl_browsertest.cc b/fuchsia/browser/context_impl_browsertest.cc
similarity index 97%
rename from webrunner/browser/context_impl_browsertest.cc
rename to fuchsia/browser/context_impl_browsertest.cc
index 0b6d335ac..797be530 100644
--- a/webrunner/browser/context_impl_browsertest.cc
+++ b/fuchsia/browser/context_impl_browsertest.cc
@@ -11,15 +11,15 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
+#include "fuchsia/common/test/test_common.h"
+#include "fuchsia/common/test/webrunner_browser_test.h"
+#include "fuchsia/service/common.h"
 #include "net/cookies/cookie_store.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/url_constants.h"
-#include "webrunner/common/test/test_common.h"
-#include "webrunner/common/test/webrunner_browser_test.h"
-#include "webrunner/service/common.h"
 
 namespace webrunner {
 
diff --git a/webrunner/browser/frame_impl.cc b/fuchsia/browser/frame_impl.cc
similarity index 97%
rename from webrunner/browser/frame_impl.cc
rename to fuchsia/browser/frame_impl.cc
index 2e26b4d..e6ca823 100644
--- a/webrunner/browser/frame_impl.cc
+++ b/fuchsia/browser/frame_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/browser/frame_impl.h"
+#include "fuchsia/browser/frame_impl.h"
 
 #include <zircon/syscalls.h>
 
@@ -18,6 +18,10 @@
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/renderer_preferences_util.h"
+#include "content/public/common/was_activated_option.h"
+#include "fuchsia/browser/context_impl.h"
+#include "fuchsia/browser/message_port_impl.h"
+#include "fuchsia/common/mem_buffer_util.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "ui/aura/layout_manager.h"
@@ -27,9 +31,6 @@
 #include "ui/platform_window/platform_window_init_properties.h"
 #include "ui/wm/core/base_focus_rules.h"
 #include "url/gurl.h"
-#include "webrunner/browser/context_impl.h"
-#include "webrunner/browser/message_port_impl.h"
-#include "webrunner/common/mem_buffer_util.h"
 
 namespace webrunner {
 
@@ -299,6 +300,9 @@
 
   params_converted.transition_type = ui::PageTransitionFromInt(
       ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
+  params_converted.was_activated = (params && params->was_activated)
+                                       ? content::WasActivatedOption::kYes
+                                       : content::WasActivatedOption::kNo;
   web_contents_->GetController().LoadURLWithParams(params_converted);
 }
 
diff --git a/webrunner/browser/frame_impl.h b/fuchsia/browser/frame_impl.h
similarity index 94%
rename from webrunner/browser/frame_impl.h
rename to fuchsia/browser/frame_impl.h
index 920e4e9..65faf0a 100644
--- a/webrunner/browser/frame_impl.h
+++ b/fuchsia/browser/frame_impl.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_BROWSER_FRAME_IMPL_H_
-#define WEBRUNNER_BROWSER_FRAME_IMPL_H_
+#ifndef FUCHSIA_BROWSER_FRAME_IMPL_H_
+#define FUCHSIA_BROWSER_FRAME_IMPL_H_
 
 #include <lib/fidl/cpp/binding_set.h>
 #include <lib/zx/channel.h>
@@ -17,11 +17,11 @@
 #include "base/memory/platform_shared_memory_region.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "fuchsia/common/on_load_script_injector.mojom.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/wm/core/focus_controller.h"
 #include "url/gurl.h"
-#include "webrunner/common/on_load_script_injector.mojom.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
 
 namespace aura {
 class WindowTreeHost;
@@ -35,7 +35,7 @@
 
 class ContextImpl;
 
-// Implementation of Frame from //webrunner/fidl/frame.fidl.
+// Implementation of Frame from //fuchsia/fidl/frame.fidl.
 // Implements a Frame service, which is a wrapper for a WebContents instance.
 class FrameImpl : public chromium::web::Frame,
                   public chromium::web::NavigationController,
@@ -158,4 +158,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_BROWSER_FRAME_IMPL_H_
+#endif  // FUCHSIA_BROWSER_FRAME_IMPL_H_
diff --git a/webrunner/browser/frame_impl_browsertest.cc b/fuchsia/browser/frame_impl_browsertest.cc
similarity index 98%
rename from webrunner/browser/frame_impl_browsertest.cc
rename to fuchsia/browser/frame_impl_browsertest.cc
index 874c366..f2bc371d 100644
--- a/webrunner/browser/frame_impl_browsertest.cc
+++ b/fuchsia/browser/frame_impl_browsertest.cc
@@ -10,18 +10,18 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "fuchsia/browser/frame_impl.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/common/test/test_common.h"
+#include "fuchsia/common/test/webrunner_browser_test.h"
+#include "fuchsia/service/common.h"
+#include "fuchsia/test/promise.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/url_request/url_request_context.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/url_constants.h"
-#include "webrunner/browser/frame_impl.h"
-#include "webrunner/common/mem_buffer_util.h"
-#include "webrunner/common/test/test_common.h"
-#include "webrunner/common/test/webrunner_browser_test.h"
-#include "webrunner/service/common.h"
-#include "webrunner/test/promise.h"
 
 namespace webrunner {
 
@@ -42,7 +42,7 @@
 const char kPage2Title[] = "title 2";
 const char kDataUrl[] =
     "data:text/html;base64,PGI+SGVsbG8sIHdvcmxkLi4uPC9iPg==";
-const char kTestServerRoot[] = FILE_PATH_LITERAL("webrunner/browser/test/data");
+const char kTestServerRoot[] = FILE_PATH_LITERAL("fuchsia/browser/test/data");
 
 MATCHER(IsSet, "Checks if an optional field is set.") {
   return !arg.is_null();
diff --git a/webrunner/browser/message_port_impl.cc b/fuchsia/browser/message_port_impl.cc
similarity index 96%
rename from webrunner/browser/message_port_impl.cc
rename to fuchsia/browser/message_port_impl.cc
index 7bd3fa3..d416022d 100644
--- a/webrunner/browser/message_port_impl.cc
+++ b/fuchsia/browser/message_port_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/browser/message_port_impl.h"
+#include "fuchsia/browser/message_port_impl.h"
 
 #include <stdint.h>
 
@@ -14,13 +14,13 @@
 
 #include "base/fuchsia/fuchsia_logging.h"
 #include "base/macros.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 #include "mojo/public/cpp/system/message_pipe.h"
 #include "third_party/blink/public/common/messaging/message_port_channel.h"
 #include "third_party/blink/public/common/messaging/string_message_codec.h"
 #include "third_party/blink/public/common/messaging/transferable_message_struct_traits.h"
 #include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h"
-#include "webrunner/common/mem_buffer_util.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
 
 namespace webrunner {
 namespace {
@@ -149,7 +149,7 @@
   if (!pending_client_read_cb_ || message_queue_.empty())
     return;
 
-  base::ResetAndReturn(&pending_client_read_cb_)(
+  base::ResetAndReturn (&pending_client_read_cb_)(
       std::move(message_queue_.front()));
   message_queue_.pop_front();
 }
diff --git a/webrunner/browser/message_port_impl.h b/fuchsia/browser/message_port_impl.h
similarity index 92%
rename from webrunner/browser/message_port_impl.h
rename to fuchsia/browser/message_port_impl.h
index ddb7070..835d9fc 100644
--- a/webrunner/browser/message_port_impl.h
+++ b/fuchsia/browser/message_port_impl.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_BROWSER_MESSAGE_PORT_IMPL_H_
-#define WEBRUNNER_BROWSER_MESSAGE_PORT_IMPL_H_
+#ifndef FUCHSIA_BROWSER_MESSAGE_PORT_IMPL_H_
+#define FUCHSIA_BROWSER_MESSAGE_PORT_IMPL_H_
 
 #include <lib/fidl/cpp/binding.h>
 #include <deque>
@@ -11,10 +11,10 @@
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 #include "mojo/public/cpp/bindings/connector.h"
 #include "mojo/public/cpp/bindings/message.h"
 #include "mojo/public/cpp/system/message_pipe.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
 
 namespace webrunner {
 
@@ -72,4 +72,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_BROWSER_MESSAGE_PORT_IMPL_H_
+#endif  // FUCHSIA_BROWSER_MESSAGE_PORT_IMPL_H_
diff --git a/webrunner/browser/test/data/dynamic_title.html b/fuchsia/browser/test/data/dynamic_title.html
similarity index 100%
rename from webrunner/browser/test/data/dynamic_title.html
rename to fuchsia/browser/test/data/dynamic_title.html
diff --git a/webrunner/browser/test/data/message_port.html b/fuchsia/browser/test/data/message_port.html
similarity index 100%
rename from webrunner/browser/test/data/message_port.html
rename to fuchsia/browser/test/data/message_port.html
diff --git a/webrunner/browser/test/data/title1.html b/fuchsia/browser/test/data/title1.html
similarity index 100%
rename from webrunner/browser/test/data/title1.html
rename to fuchsia/browser/test/data/title1.html
diff --git a/webrunner/browser/test/data/title2.html b/fuchsia/browser/test/data/title2.html
similarity index 100%
rename from webrunner/browser/test/data/title2.html
rename to fuchsia/browser/test/data/title2.html
diff --git a/webrunner/browser/test/data/window_post_message.html b/fuchsia/browser/test/data/window_post_message.html
similarity index 100%
rename from webrunner/browser/test/data/window_post_message.html
rename to fuchsia/browser/test/data/window_post_message.html
diff --git a/webrunner/browser/webrunner_browser_context.cc b/fuchsia/browser/webrunner_browser_context.cc
similarity index 95%
rename from webrunner/browser/webrunner_browser_context.cc
rename to fuchsia/browser/webrunner_browser_context.cc
index 8eb7fd0..93775d2 100644
--- a/webrunner/browser/webrunner_browser_context.cc
+++ b/fuchsia/browser/webrunner_browser_context.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/browser/webrunner_browser_context.h"
+#include "fuchsia/browser/webrunner_browser_context.h"
 
 #include <memory>
 #include <utility>
@@ -16,11 +16,11 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/resource_context.h"
+#include "fuchsia/browser/webrunner_net_log.h"
+#include "fuchsia/browser/webrunner_url_request_context_getter.h"
+#include "fuchsia/service/common.h"
 #include "net/url_request/url_request_context.h"
 #include "services/network/public/cpp/network_switches.h"
-#include "webrunner/browser/webrunner_net_log.h"
-#include "webrunner/browser/webrunner_url_request_context_getter.h"
-#include "webrunner/service/common.h"
 
 namespace webrunner {
 
diff --git a/webrunner/browser/webrunner_browser_context.h b/fuchsia/browser/webrunner_browser_context.h
similarity index 94%
rename from webrunner/browser/webrunner_browser_context.h
rename to fuchsia/browser/webrunner_browser_context.h
index a4c672d..d1be10e 100644
--- a/webrunner/browser/webrunner_browser_context.h
+++ b/fuchsia/browser/webrunner_browser_context.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_CONTEXT_H_
-#define WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_CONTEXT_H_
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_BROWSER_CONTEXT_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_BROWSER_CONTEXT_H_
 
 #include <memory>
 
@@ -70,4 +70,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_CONTEXT_H_
+#endif  // FUCHSIA_BROWSER_WEBRUNNER_BROWSER_CONTEXT_H_
diff --git a/webrunner/browser/webrunner_browser_main.cc b/fuchsia/browser/webrunner_browser_main.cc
similarity index 92%
rename from webrunner/browser/webrunner_browser_main.cc
rename to fuchsia/browser/webrunner_browser_main.cc
index f4e183a1..aaca77df 100644
--- a/webrunner/browser/webrunner_browser_main.cc
+++ b/fuchsia/browser/webrunner_browser_main.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/browser/webrunner_browser_main.h"
+#include "fuchsia/browser/webrunner_browser_main.h"
 
 #include <memory>
 
diff --git a/webrunner/browser/webrunner_browser_main.h b/fuchsia/browser/webrunner_browser_main.h
similarity index 70%
rename from webrunner/browser/webrunner_browser_main.h
rename to fuchsia/browser/webrunner_browser_main.h
index 37428a1..50a0858 100644
--- a/webrunner/browser/webrunner_browser_main.h
+++ b/fuchsia/browser/webrunner_browser_main.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_MAIN_H_
-#define WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_MAIN_H_
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_BROWSER_MAIN_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_BROWSER_MAIN_H_
 
 #include <memory>
 
@@ -15,4 +15,4 @@
 int WebRunnerBrowserMain(const content::MainFunctionParams& parameters);
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_MAIN_H_
+#endif  // FUCHSIA_BROWSER_WEBRUNNER_BROWSER_MAIN_H_
diff --git a/webrunner/browser/webrunner_browser_main_parts.cc b/fuchsia/browser/webrunner_browser_main_parts.cc
similarity index 91%
rename from webrunner/browser/webrunner_browser_main_parts.cc
rename to fuchsia/browser/webrunner_browser_main_parts.cc
index b5402c3..75581a2 100644
--- a/webrunner/browser/webrunner_browser_main_parts.cc
+++ b/fuchsia/browser/webrunner_browser_main_parts.cc
@@ -2,19 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/browser/webrunner_browser_main_parts.h"
+#include "fuchsia/browser/webrunner_browser_main_parts.h"
 
 #include <utility>
 
 #include "base/command_line.h"
 #include "base/files/file_util.h"
 #include "content/public/browser/render_frame_host.h"
+#include "fuchsia/browser/context_impl.h"
+#include "fuchsia/browser/webrunner_browser_context.h"
+#include "fuchsia/browser/webrunner_screen.h"
+#include "fuchsia/service/common.h"
 #include "ui/aura/screen_ozone.h"
 #include "ui/ozone/public/ozone_platform.h"
-#include "webrunner/browser/context_impl.h"
-#include "webrunner/browser/webrunner_browser_context.h"
-#include "webrunner/browser/webrunner_screen.h"
-#include "webrunner/service/common.h"
 
 namespace webrunner {
 
diff --git a/webrunner/browser/webrunner_browser_main_parts.h b/fuchsia/browser/webrunner_browser_main_parts.h
similarity index 82%
rename from webrunner/browser/webrunner_browser_main_parts.h
rename to fuchsia/browser/webrunner_browser_main_parts.h
index 871c7e2..7c25109 100644
--- a/webrunner/browser/webrunner_browser_main_parts.h
+++ b/fuchsia/browser/webrunner_browser_main_parts.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_MAIN_PARTS_H_
-#define WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_MAIN_PARTS_H_
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_BROWSER_MAIN_PARTS_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_BROWSER_MAIN_PARTS_H_
 
 #include <lib/fidl/cpp/binding.h>
 #include <memory>
@@ -11,8 +11,8 @@
 #include "base/macros.h"
 #include "base/optional.h"
 #include "content/public/browser/browser_main_parts.h"
-#include "webrunner/browser/context_impl.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/browser/context_impl.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 
 namespace display {
 class Screen;
@@ -49,4 +49,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_BROWSER_WEBRUNNER_BROWSER_MAIN_PARTS_H_
+#endif  // FUCHSIA_BROWSER_WEBRUNNER_BROWSER_MAIN_PARTS_H_
diff --git a/webrunner/browser/webrunner_content_browser_client.cc b/fuchsia/browser/webrunner_content_browser_client.cc
similarity index 88%
rename from webrunner/browser/webrunner_content_browser_client.cc
rename to fuchsia/browser/webrunner_content_browser_client.cc
index 7545784..d7cb950b 100644
--- a/webrunner/browser/webrunner_content_browser_client.cc
+++ b/fuchsia/browser/webrunner_content_browser_client.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/browser/webrunner_content_browser_client.h"
+#include "fuchsia/browser/webrunner_content_browser_client.h"
 
 #include <utility>
 
 #include "components/version_info/version_info.h"
 #include "content/public/common/user_agent.h"
-#include "webrunner/browser/webrunner_browser_main_parts.h"
+#include "fuchsia/browser/webrunner_browser_main_parts.h"
 
 namespace webrunner {
 
diff --git a/webrunner/browser/webrunner_content_browser_client.h b/fuchsia/browser/webrunner_content_browser_client.h
similarity index 83%
rename from webrunner/browser/webrunner_content_browser_client.h
rename to fuchsia/browser/webrunner_content_browser_client.h
index f489d29..84a1ddd 100644
--- a/webrunner/browser/webrunner_content_browser_client.h
+++ b/fuchsia/browser/webrunner_content_browser_client.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_BROWSER_WEBRUNNER_CONTENT_BROWSER_CLIENT_H_
-#define WEBRUNNER_BROWSER_WEBRUNNER_CONTENT_BROWSER_CLIENT_H_
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_CONTENT_BROWSER_CLIENT_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_CONTENT_BROWSER_CLIENT_H_
 
 #include <lib/zx/channel.h>
 
@@ -35,4 +35,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_BROWSER_WEBRUNNER_CONTENT_BROWSER_CLIENT_H_
+#endif  // FUCHSIA_BROWSER_WEBRUNNER_CONTENT_BROWSER_CLIENT_H_
diff --git a/webrunner/browser/webrunner_net_log.cc b/fuchsia/browser/webrunner_net_log.cc
similarity index 96%
rename from webrunner/browser/webrunner_net_log.cc
rename to fuchsia/browser/webrunner_net_log.cc
index 7342b0a..ba60913 100644
--- a/webrunner/browser/webrunner_net_log.cc
+++ b/fuchsia/browser/webrunner_net_log.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/browser/webrunner_net_log.h"
+#include "fuchsia/browser/webrunner_net_log.h"
 
 #include <string>
 #include <utility>
diff --git a/webrunner/browser/webrunner_net_log.h b/fuchsia/browser/webrunner_net_log.h
similarity index 82%
rename from webrunner/browser/webrunner_net_log.h
rename to fuchsia/browser/webrunner_net_log.h
index ae6ac92..00d736e 100644
--- a/webrunner/browser/webrunner_net_log.h
+++ b/fuchsia/browser/webrunner_net_log.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_BROWSER_WEBRUNNER_NET_LOG_H_
-#define WEBRUNNER_BROWSER_WEBRUNNER_NET_LOG_H_
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_NET_LOG_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_NET_LOG_H_
 
 #include <memory>
 
@@ -33,4 +33,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_BROWSER_WEBRUNNER_NET_LOG_H_
+#endif  // FUCHSIA_BROWSER_WEBRUNNER_NET_LOG_H_
diff --git a/webrunner/browser/webrunner_screen.cc b/fuchsia/browser/webrunner_screen.cc
similarity index 90%
rename from webrunner/browser/webrunner_screen.cc
rename to fuchsia/browser/webrunner_screen.cc
index 0a3af0c..18fe947 100644
--- a/webrunner/browser/webrunner_screen.cc
+++ b/fuchsia/browser/webrunner_screen.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/browser/webrunner_screen.h"
+#include "fuchsia/browser/webrunner_screen.h"
 
 #include "ui/display/display.h"
 
diff --git a/webrunner/browser/webrunner_screen.h b/fuchsia/browser/webrunner_screen.h
similarity index 78%
rename from webrunner/browser/webrunner_screen.h
rename to fuchsia/browser/webrunner_screen.h
index c028f2a..f44dd9e 100644
--- a/webrunner/browser/webrunner_screen.h
+++ b/fuchsia/browser/webrunner_screen.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_BROWSER_WEBRUNNER_SCREEN_H_
-#define WEBRUNNER_BROWSER_WEBRUNNER_SCREEN_H_
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_SCREEN_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_SCREEN_H_
 
 #include "base/macros.h"
 
@@ -23,4 +23,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_BROWSER_WEBRUNNER_SCREEN_H_
+#endif  // FUCHSIA_BROWSER_WEBRUNNER_SCREEN_H_
diff --git a/webrunner/browser/webrunner_url_request_context_getter.cc b/fuchsia/browser/webrunner_url_request_context_getter.cc
similarity index 97%
rename from webrunner/browser/webrunner_url_request_context_getter.cc
rename to fuchsia/browser/webrunner_url_request_context_getter.cc
index d4ddc36..32d2b2d 100644
--- a/webrunner/browser/webrunner_url_request_context_getter.cc
+++ b/fuchsia/browser/webrunner_url_request_context_getter.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/browser/webrunner_url_request_context_getter.h"
+#include "fuchsia/browser/webrunner_url_request_context_getter.h"
 
 #include <utility>
 
diff --git a/webrunner/browser/webrunner_url_request_context_getter.h b/fuchsia/browser/webrunner_url_request_context_getter.h
similarity index 88%
rename from webrunner/browser/webrunner_url_request_context_getter.h
rename to fuchsia/browser/webrunner_url_request_context_getter.h
index fc94e4c..d89ec0b 100644
--- a/webrunner/browser/webrunner_url_request_context_getter.h
+++ b/fuchsia/browser/webrunner_url_request_context_getter.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_BROWSER_WEBRUNNER_URL_REQUEST_CONTEXT_GETTER_H_
-#define WEBRUNNER_BROWSER_WEBRUNNER_URL_REQUEST_CONTEXT_GETTER_H_
+#ifndef FUCHSIA_BROWSER_WEBRUNNER_URL_REQUEST_CONTEXT_GETTER_H_
+#define FUCHSIA_BROWSER_WEBRUNNER_URL_REQUEST_CONTEXT_GETTER_H_
 
 #include <memory>
 
@@ -54,4 +54,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_BROWSER_WEBRUNNER_URL_REQUEST_CONTEXT_GETTER_H_
+#endif  // FUCHSIA_BROWSER_WEBRUNNER_URL_REQUEST_CONTEXT_GETTER_H_
diff --git a/webrunner/cipd/build_id.template b/fuchsia/cipd/build_id.template
similarity index 100%
rename from webrunner/cipd/build_id.template
rename to fuchsia/cipd/build_id.template
diff --git a/webrunner/cipd/castrunner.yaml b/fuchsia/cipd/castrunner.yaml
similarity index 94%
rename from webrunner/cipd/castrunner.yaml
rename to fuchsia/cipd/castrunner.yaml
index 41070b4..371fd451 100644
--- a/webrunner/cipd/castrunner.yaml
+++ b/fuchsia/cipd/castrunner.yaml
@@ -15,7 +15,7 @@
 # To create a CIPD package, run the following command from the build output
 # directory.
 #
-# $ cipd create --pkg-def ../../webrunner/cipd/castrunner.yaml \
+# $ cipd create --pkg-def ../../fuchsia/cipd/castrunner.yaml \
 #               -pkg-var targetarch:$TARGET_ARCH \
 #               -pkg-var outdir:`pwd` \
 #               -ref latest \
diff --git a/webrunner/cipd/fidl.yaml b/fuchsia/cipd/fidl.yaml
similarity index 91%
rename from webrunner/cipd/fidl.yaml
rename to fuchsia/cipd/fidl.yaml
index c607b34b..5be7aa6 100644
--- a/webrunner/cipd/fidl.yaml
+++ b/fuchsia/cipd/fidl.yaml
@@ -7,7 +7,7 @@
 # To create a CIPD package, run the following command from the build output
 # directory.
 #
-# $ cipd create --pkg-def ../../webrunner/cipd/fidl.yaml \
+# $ cipd create --pkg-def ../../fuchsia/cipd/fidl.yaml \
 #               -ref latest \
 #               -tag version:$(cat fuchsia_artifacts/build_id.txt)
 #
diff --git a/webrunner/cipd/http.yaml b/fuchsia/cipd/http.yaml
similarity index 94%
rename from webrunner/cipd/http.yaml
rename to fuchsia/cipd/http.yaml
index a8d8a43..702c6fa 100644
--- a/webrunner/cipd/http.yaml
+++ b/fuchsia/cipd/http.yaml
@@ -14,7 +14,7 @@
 # To create a CIPD package, run the following command from the build output
 # directory.
 #
-# $ cipd create --pkg-def ../../webrunner/cipd/http.yaml \
+# $ cipd create --pkg-def ../../fuchsia/cipd/http.yaml \
 #               -pkg-var targetarch:$TARGET_ARCH \
 #               -pkg-var outdir:`pwd` \
 #               -ref latest \
diff --git a/webrunner/cipd/webrunner.yaml b/fuchsia/cipd/webrunner.yaml
similarity index 94%
rename from webrunner/cipd/webrunner.yaml
rename to fuchsia/cipd/webrunner.yaml
index 31cd527..9b82992 100644
--- a/webrunner/cipd/webrunner.yaml
+++ b/fuchsia/cipd/webrunner.yaml
@@ -14,7 +14,7 @@
 # To create a CIPD package, run the following command from the build output
 # directory.
 #
-# $ cipd create --pkg-def ../../webrunner/cipd/webrunner.yaml \
+# $ cipd create --pkg-def ../../fuchsia/cipd/webrunner.yaml \
 #               -pkg-var targetarch:$TARGET_ARCH \
 #               -pkg-var outdir:`pwd` \
 #               -ref latest \
diff --git a/webrunner/common/DEPS b/fuchsia/common/DEPS
similarity index 100%
rename from webrunner/common/DEPS
rename to fuchsia/common/DEPS
diff --git a/webrunner/common/OWNERS b/fuchsia/common/OWNERS
similarity index 100%
rename from webrunner/common/OWNERS
rename to fuchsia/common/OWNERS
diff --git a/fuchsia/common/fuchsia_export.h b/fuchsia/common/fuchsia_export.h
new file mode 100644
index 0000000..3a7f4761
--- /dev/null
+++ b/fuchsia/common/fuchsia_export.h
@@ -0,0 +1,20 @@
+// 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 FUCHSIA_COMMON_FUCHSIA_EXPORT_H_
+#define FUCHSIA_COMMON_FUCHSIA_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+
+#if defined(WEBRUNNER_IMPLEMENTATION)
+#define FUCHSIA_EXPORT __attribute__((visibility("default")))
+#else
+#define FUCHSIA_EXPORT
+#endif
+
+#else  // defined(COMPONENT_BUILD)
+#define FUCHSIA_EXPORT
+#endif
+
+#endif  // FUCHSIA_COMMON_FUCHSIA_EXPORT_H_
diff --git a/webrunner/common/mem_buffer_util.cc b/fuchsia/common/mem_buffer_util.cc
similarity index 97%
rename from webrunner/common/mem_buffer_util.cc
rename to fuchsia/common/mem_buffer_util.cc
index 1354525..837e4c2c 100644
--- a/webrunner/common/mem_buffer_util.cc
+++ b/fuchsia/common/mem_buffer_util.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/common/mem_buffer_util.h"
+#include "fuchsia/common/mem_buffer_util.h"
 
 #include <lib/fdio/io.h>
 
diff --git a/webrunner/common/mem_buffer_util.h b/fuchsia/common/mem_buffer_util.h
similarity index 91%
rename from webrunner/common/mem_buffer_util.h
rename to fuchsia/common/mem_buffer_util.h
index ae18873..c320fcb 100644
--- a/webrunner/common/mem_buffer_util.h
+++ b/fuchsia/common/mem_buffer_util.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_COMMON_MEM_BUFFER_UTIL_H_
-#define WEBRUNNER_COMMON_MEM_BUFFER_UTIL_H_
+#ifndef FUCHSIA_COMMON_MEM_BUFFER_UTIL_H_
+#define FUCHSIA_COMMON_MEM_BUFFER_UTIL_H_
 
 #include <fuchsia/mem/cpp/fidl.h>
 #include <lib/zx/channel.h>
@@ -39,4 +39,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_COMMON_MEM_BUFFER_UTIL_H_
+#endif  // FUCHSIA_COMMON_MEM_BUFFER_UTIL_H_
diff --git a/webrunner/common/named_message_port_connector.cc b/fuchsia/common/named_message_port_connector.cc
similarity index 94%
rename from webrunner/common/named_message_port_connector.cc
rename to fuchsia/common/named_message_port_connector.cc
index 5c62a00..41a0eaf1 100644
--- a/webrunner/common/named_message_port_connector.cc
+++ b/fuchsia/common/named_message_port_connector.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/common/named_message_port_connector.h"
+#include "fuchsia/common/named_message_port_connector.h"
 
 #include <memory>
 #include <utility>
@@ -13,14 +13,14 @@
 #include "base/macros.h"
 #include "base/path_service.h"
 #include "base/threading/thread_restrictions.h"
-#include "webrunner/common/mem_buffer_util.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 
 namespace webrunner {
 namespace {
 
 const char kBindingsJsPath[] =
-    FILE_PATH_LITERAL("webrunner/common/named_message_port_connector.js");
+    FILE_PATH_LITERAL("fuchsia/common/named_message_port_connector.js");
 const char kConnectedMessage[] = "connected";
 
 }  // namespace
diff --git a/webrunner/common/named_message_port_connector.h b/fuchsia/common/named_message_port_connector.h
similarity index 86%
rename from webrunner/common/named_message_port_connector.h
rename to fuchsia/common/named_message_port_connector.h
index f51b388..352903b8 100644
--- a/webrunner/common/named_message_port_connector.h
+++ b/fuchsia/common/named_message_port_connector.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_COMMON_NAMED_MESSAGE_PORT_CONNECTOR_H_
-#define WEBRUNNER_COMMON_NAMED_MESSAGE_PORT_CONNECTOR_H_
+#ifndef FUCHSIA_COMMON_NAMED_MESSAGE_PORT_CONNECTOR_H_
+#define FUCHSIA_COMMON_NAMED_MESSAGE_PORT_CONNECTOR_H_
 
 #include <deque>
 #include <map>
@@ -12,8 +12,8 @@
 #include "base/callback.h"
 #include "base/files/file_path.h"
 #include "base/macros.h"
-#include "webrunner/common/webrunner_export.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/common/fuchsia_export.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 
 namespace webrunner {
 
@@ -21,7 +21,7 @@
 // passing them all to the web container every time a page-load occurs. It then
 // waits for the page to acknowledge each MessagePort, before invoking its
 // registered |handler| to notify the called that it is ready for use.
-class WEBRUNNER_EXPORT NamedMessagePortConnector {
+class FUCHSIA_EXPORT NamedMessagePortConnector {
  public:
   using PortConnectedCallback =
       base::RepeatingCallback<void(chromium::web::MessagePortPtr)>;
@@ -67,4 +67,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_COMMON_NAMED_MESSAGE_PORT_CONNECTOR_H_
+#endif  // FUCHSIA_COMMON_NAMED_MESSAGE_PORT_CONNECTOR_H_
diff --git a/webrunner/common/named_message_port_connector.js b/fuchsia/common/named_message_port_connector.js
similarity index 100%
rename from webrunner/common/named_message_port_connector.js
rename to fuchsia/common/named_message_port_connector.js
diff --git a/webrunner/common/named_message_port_connector_browsertest.cc b/fuchsia/common/named_message_port_connector_browsertest.cc
similarity index 91%
rename from webrunner/common/named_message_port_connector_browsertest.cc
rename to fuchsia/common/named_message_port_connector_browsertest.cc
index c11aa56..ff5cf38c 100644
--- a/webrunner/common/named_message_port_connector_browsertest.cc
+++ b/fuchsia/common/named_message_port_connector_browsertest.cc
@@ -9,15 +9,15 @@
 #include "base/macros.h"
 #include "base/path_service.h"
 #include "base/test/test_timeouts.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/common/named_message_port_connector.h"
+#include "fuchsia/common/test/test_common.h"
+#include "fuchsia/common/test/webrunner_browser_test.h"
+#include "fuchsia/test/promise.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/url_constants.h"
-#include "webrunner/common/mem_buffer_util.h"
-#include "webrunner/common/named_message_port_connector.h"
-#include "webrunner/common/test/test_common.h"
-#include "webrunner/common/test/webrunner_browser_test.h"
-#include "webrunner/test/promise.h"
 
 namespace webrunner {
 
@@ -31,7 +31,7 @@
  public:
   NamedMessagePortConnectorTest()
       : run_timeout_(TestTimeouts::action_timeout()) {
-    set_test_server_root(base::FilePath("webrunner/common/test/data"));
+    set_test_server_root(base::FilePath("fuchsia/common/test/data"));
   }
 
   ~NamedMessagePortConnectorTest() override = default;
diff --git a/webrunner/common/on_load_script_injector.mojom b/fuchsia/common/on_load_script_injector.mojom
similarity index 100%
rename from webrunner/common/on_load_script_injector.mojom
rename to fuchsia/common/on_load_script_injector.mojom
diff --git a/webrunner/common/test/data/connector.html b/fuchsia/common/test/data/connector.html
similarity index 100%
rename from webrunner/common/test/data/connector.html
rename to fuchsia/common/test/data/connector.html
diff --git a/webrunner/common/test/test_common.cc b/fuchsia/common/test/test_common.cc
similarity index 91%
rename from webrunner/common/test/test_common.cc
rename to fuchsia/common/test/test_common.cc
index 9a426e4..94e23ff 100644
--- a/webrunner/common/test/test_common.cc
+++ b/fuchsia/common/test/test_common.cc
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/common/test/test_common.h"
+#include "fuchsia/common/test/test_common.h"
 
 #include <string>
 #include <utility>
 
 #include "base/fuchsia/fuchsia_logging.h"
 #include "base/run_loop.h"
-#include "webrunner/common/mem_buffer_util.h"
+#include "fuchsia/common/mem_buffer_util.h"
 
 namespace webrunner {
 
diff --git a/webrunner/common/test/test_common.h b/fuchsia/common/test/test_common.h
similarity index 90%
rename from webrunner/common/test/test_common.h
rename to fuchsia/common/test/test_common.h
index 852c1a6..594695d 100644
--- a/webrunner/common/test/test_common.h
+++ b/fuchsia/common/test/test_common.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_COMMON_TEST_TEST_COMMON_H_
-#define WEBRUNNER_COMMON_TEST_TEST_COMMON_H_
+#ifndef FUCHSIA_COMMON_TEST_TEST_COMMON_H_
+#define FUCHSIA_COMMON_TEST_TEST_COMMON_H_
 
 #include <string>
 #include <utility>
 
 #include "content/public/browser/web_contents_observer.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 #include "testing/gmock/include/gmock/gmock.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
 
 namespace webrunner {
 
@@ -53,4 +53,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_COMMON_TEST_TEST_COMMON_H_
+#endif  // FUCHSIA_COMMON_TEST_TEST_COMMON_H_
diff --git a/webrunner/common/test/webrunner_browser_test.cc b/fuchsia/common/test/webrunner_browser_test.cc
similarity index 87%
rename from webrunner/common/test/webrunner_browser_test.cc
rename to fuchsia/common/test/webrunner_browser_test.cc
index 5cd5278..308c525 100644
--- a/webrunner/common/test/webrunner_browser_test.cc
+++ b/fuchsia/common/test/webrunner_browser_test.cc
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/common/test/webrunner_browser_test.h"
+#include "fuchsia/common/test/webrunner_browser_test.h"
 
 #include "base/fuchsia/fuchsia_logging.h"
+#include "fuchsia/browser/webrunner_browser_context.h"
+#include "fuchsia/browser/webrunner_browser_main_parts.h"
+#include "fuchsia/browser/webrunner_content_browser_client.h"
+#include "fuchsia/service/webrunner_main_delegate.h"
 #include "net/test/embedded_test_server/default_handlers.h"
-#include "webrunner/browser/webrunner_browser_context.h"
-#include "webrunner/browser/webrunner_browser_main_parts.h"
-#include "webrunner/browser/webrunner_content_browser_client.h"
-#include "webrunner/service/webrunner_main_delegate.h"
 
 namespace webrunner {
 namespace {
diff --git a/webrunner/common/test/webrunner_browser_test.h b/fuchsia/common/test/webrunner_browser_test.h
similarity index 87%
rename from webrunner/common/test/webrunner_browser_test.h
rename to fuchsia/common/test/webrunner_browser_test.h
index bde3cf2..6a7c22b 100644
--- a/webrunner/common/test/webrunner_browser_test.h
+++ b/fuchsia/common/test/webrunner_browser_test.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_COMMON_TEST_WEBRUNNER_BROWSER_TEST_H_
-#define WEBRUNNER_COMMON_TEST_WEBRUNNER_BROWSER_TEST_H_
+#ifndef FUCHSIA_COMMON_TEST_WEBRUNNER_BROWSER_TEST_H_
+#define FUCHSIA_COMMON_TEST_WEBRUNNER_BROWSER_TEST_H_
 
 #include <lib/fidl/cpp/binding_set.h>
 #include <memory>
@@ -12,8 +12,8 @@
 #include "base/macros.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_base.h"
-#include "webrunner/browser/context_impl.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/browser/context_impl.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 
 namespace webrunner {
 
@@ -64,4 +64,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_COMMON_TEST_WEBRUNNER_BROWSER_TEST_H_
+#endif  // FUCHSIA_COMMON_TEST_FUCHSIA_BROWSER_TEST_H_
diff --git a/webrunner/common/test/webrunner_test_launcher.cc b/fuchsia/common/test/webrunner_test_launcher.cc
similarity index 91%
rename from webrunner/common/test/webrunner_test_launcher.cc
rename to fuchsia/common/test/webrunner_test_launcher.cc
index 8b3837b..ec8ba31f 100644
--- a/webrunner/common/test/webrunner_test_launcher.cc
+++ b/fuchsia/common/test/webrunner_test_launcher.cc
@@ -9,11 +9,11 @@
 #include "base/test/test_suite.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/test_launcher.h"
+#include "fuchsia/common/test/webrunner_browser_test.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/service/common.h"
+#include "fuchsia/service/webrunner_main_delegate.h"
 #include "ui/ozone/public/ozone_switches.h"
-#include "webrunner/common/test/webrunner_browser_test.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
-#include "webrunner/service/common.h"
-#include "webrunner/service/webrunner_main_delegate.h"
 
 namespace webrunner {
 namespace {
diff --git a/webrunner/common/webrunner_content_client.cc b/fuchsia/common/webrunner_content_client.cc
similarity index 95%
rename from webrunner/common/webrunner_content_client.cc
rename to fuchsia/common/webrunner_content_client.cc
index acf5237..019d7e0 100644
--- a/webrunner/common/webrunner_content_client.cc
+++ b/fuchsia/common/webrunner_content_client.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/common/webrunner_content_client.h"
+#include "fuchsia/common/webrunner_content_client.h"
 
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
diff --git a/webrunner/common/webrunner_content_client.h b/fuchsia/common/webrunner_content_client.h
similarity index 84%
rename from webrunner/common/webrunner_content_client.h
rename to fuchsia/common/webrunner_content_client.h
index 15bd599..26e36a8 100644
--- a/webrunner/common/webrunner_content_client.h
+++ b/fuchsia/common/webrunner_content_client.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_COMMON_WEBRUNNER_CONTENT_CLIENT_H_
-#define WEBRUNNER_COMMON_WEBRUNNER_CONTENT_CLIENT_H_
+#ifndef FUCHSIA_COMMON_WEBRUNNER_CONTENT_CLIENT_H_
+#define FUCHSIA_COMMON_WEBRUNNER_CONTENT_CLIENT_H_
 
 #include "base/macros.h"
 #include "content/public/common/content_client.h"
@@ -30,4 +30,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_COMMON_WEBRUNNER_CONTENT_CLIENT_H_
+#endif  // FUCHSIA_COMMON_WEBRUNNER_CONTENT_CLIENT_H_
diff --git a/webrunner/fidl/cast/application_config.fidl b/fuchsia/fidl/cast/application_config.fidl
similarity index 100%
rename from webrunner/fidl/cast/application_config.fidl
rename to fuchsia/fidl/cast/application_config.fidl
diff --git a/webrunner/fidl/cast/cast_channel.fidl b/fuchsia/fidl/cast/cast_channel.fidl
similarity index 100%
rename from webrunner/fidl/cast/cast_channel.fidl
rename to fuchsia/fidl/cast/cast_channel.fidl
diff --git a/webrunner/fidl/web/context.fidl b/fuchsia/fidl/web/context.fidl
similarity index 100%
rename from webrunner/fidl/web/context.fidl
rename to fuchsia/fidl/web/context.fidl
diff --git a/webrunner/fidl/web/context_provider.fidl b/fuchsia/fidl/web/context_provider.fidl
similarity index 100%
rename from webrunner/fidl/web/context_provider.fidl
rename to fuchsia/fidl/web/context_provider.fidl
diff --git a/webrunner/fidl/web/frame.fidl b/fuchsia/fidl/web/frame.fidl
similarity index 100%
rename from webrunner/fidl/web/frame.fidl
rename to fuchsia/fidl/web/frame.fidl
diff --git a/webrunner/fidl/web/navigation_controller.fidl b/fuchsia/fidl/web/navigation_controller.fidl
similarity index 94%
rename from webrunner/fidl/web/navigation_controller.fidl
rename to fuchsia/fidl/web/navigation_controller.fidl
index 5c18c2b..588b2d2 100644
--- a/webrunner/fidl/web/navigation_controller.fidl
+++ b/fuchsia/fidl/web/navigation_controller.fidl
@@ -32,6 +32,9 @@
   // The URL that linked to the resource being requested.
   string referrer;
 
+  // Should be set to true if the action was initiated by the user.
+  bool was_activated = false;
+
   // Custom HTTP headers.
   vector<vector<uint8>> headers;
 };
diff --git a/webrunner/fidl/web/navigation_event_observer.fidl b/fuchsia/fidl/web/navigation_event_observer.fidl
similarity index 100%
rename from webrunner/fidl/web/navigation_event_observer.fidl
rename to fuchsia/fidl/web/navigation_event_observer.fidl
diff --git a/webrunner/net_http/BUILD.gn b/fuchsia/net_http/BUILD.gn
similarity index 100%
rename from webrunner/net_http/BUILD.gn
rename to fuchsia/net_http/BUILD.gn
diff --git a/webrunner/net_http/http_service_impl.cc b/fuchsia/net_http/http_service_impl.cc
similarity index 88%
rename from webrunner/net_http/http_service_impl.cc
rename to fuchsia/net_http/http_service_impl.cc
index 25145da..a55aff57 100644
--- a/webrunner/net_http/http_service_impl.cc
+++ b/fuchsia/net_http/http_service_impl.cc
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/net_http/http_service_impl.h"
+#include "fuchsia/net_http/http_service_impl.h"
 
+#include "fuchsia/net_http/url_loader_impl.h"
 #include "net/url_request/url_request_context_builder.h"
-#include "webrunner/net_http/url_loader_impl.h"
 
 namespace net_http {
 
diff --git a/webrunner/net_http/http_service_impl.h b/fuchsia/net_http/http_service_impl.h
similarity index 83%
rename from webrunner/net_http/http_service_impl.h
rename to fuchsia/net_http/http_service_impl.h
index 1742c7d..1b04bb2 100644
--- a/webrunner/net_http/http_service_impl.h
+++ b/fuchsia/net_http/http_service_impl.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_NET_HTTP_HTTP_SERVICE_IMPL_H_
-#define WEBRUNNER_NET_HTTP_HTTP_SERVICE_IMPL_H_
+#ifndef FUCHSIA_NET_HTTP_HTTP_SERVICE_IMPL_H_
+#define FUCHSIA_NET_HTTP_HTTP_SERVICE_IMPL_H_
 
 #include <fuchsia/net/oldhttp/cpp/fidl.h>
 #include <lib/fidl/cpp/interface_request.h>
@@ -29,4 +29,4 @@
 
 }  // namespace net_http
 
-#endif  // WEBRUNNER_NET_HTTP_HTTP_SERVICE_IMPL_H_
+#endif  // FUCHSIA_NET_HTTP_HTTP_SERVICE_IMPL_H_
diff --git a/webrunner/net_http/http_service_main.cc b/fuchsia/net_http/http_service_main.cc
similarity index 96%
rename from webrunner/net_http/http_service_main.cc
rename to fuchsia/net_http/http_service_main.cc
index 76c9403..3396d86f 100644
--- a/webrunner/net_http/http_service_main.cc
+++ b/fuchsia/net_http/http_service_main.cc
@@ -10,7 +10,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/task/task_scheduler/task_scheduler.h"
-#include "webrunner/net_http/http_service_impl.h"
+#include "fuchsia/net_http/http_service_impl.h"
 
 int main(int argc, char** argv) {
   // Instantiate various global structures.
diff --git a/webrunner/net_http/http_service_unittest.cc b/fuchsia/net_http/http_service_unittest.cc
similarity index 98%
rename from webrunner/net_http/http_service_unittest.cc
rename to fuchsia/net_http/http_service_unittest.cc
index 7feef36..d09ccdf 100644
--- a/webrunner/net_http/http_service_unittest.cc
+++ b/fuchsia/net_http/http_service_unittest.cc
@@ -10,12 +10,12 @@
 #include "base/fuchsia/service_directory.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
+#include "fuchsia/net_http/http_service_impl.h"
+#include "fuchsia/net_http/url_loader_impl.h"
 #include "net/base/net_errors.h"
 #include "net/test/embedded_test_server/default_handlers.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "webrunner/net_http/http_service_impl.h"
-#include "webrunner/net_http/url_loader_impl.h"
 
 namespace net_http {
 
@@ -24,7 +24,7 @@
 namespace {
 
 const base::FilePath::CharType kTestFilePath[] =
-    FILE_PATH_LITERAL("webrunner/net_http/testdata");
+    FILE_PATH_LITERAL("fuchsia/net_http/testdata");
 
 // Capacity, in bytes, for buffers used to read data off the URLResponse.
 const size_t kBufferCapacity = 1024;
diff --git a/webrunner/net_http/sandbox_policy b/fuchsia/net_http/sandbox_policy
similarity index 100%
rename from webrunner/net_http/sandbox_policy
rename to fuchsia/net_http/sandbox_policy
diff --git a/webrunner/net_http/testdata/redirect-test.html b/fuchsia/net_http/testdata/redirect-test.html
similarity index 100%
rename from webrunner/net_http/testdata/redirect-test.html
rename to fuchsia/net_http/testdata/redirect-test.html
diff --git a/webrunner/net_http/testdata/redirect-test.html.mock-http-headers b/fuchsia/net_http/testdata/redirect-test.html.mock-http-headers
similarity index 100%
rename from webrunner/net_http/testdata/redirect-test.html.mock-http-headers
rename to fuchsia/net_http/testdata/redirect-test.html.mock-http-headers
diff --git a/webrunner/net_http/testdata/simple.html b/fuchsia/net_http/testdata/simple.html
similarity index 100%
rename from webrunner/net_http/testdata/simple.html
rename to fuchsia/net_http/testdata/simple.html
diff --git a/webrunner/net_http/testdata/simple.html.mock-http-headers b/fuchsia/net_http/testdata/simple.html.mock-http-headers
similarity index 100%
rename from webrunner/net_http/testdata/simple.html.mock-http-headers
rename to fuchsia/net_http/testdata/simple.html.mock-http-headers
diff --git a/webrunner/net_http/testdata/with-duplicate-headers.html b/fuchsia/net_http/testdata/with-duplicate-headers.html
similarity index 100%
rename from webrunner/net_http/testdata/with-duplicate-headers.html
rename to fuchsia/net_http/testdata/with-duplicate-headers.html
diff --git a/webrunner/net_http/testdata/with-duplicate-headers.html.mock-http-headers b/fuchsia/net_http/testdata/with-duplicate-headers.html.mock-http-headers
similarity index 100%
rename from webrunner/net_http/testdata/with-duplicate-headers.html.mock-http-headers
rename to fuchsia/net_http/testdata/with-duplicate-headers.html.mock-http-headers
diff --git a/webrunner/net_http/testdata/with-headers.html b/fuchsia/net_http/testdata/with-headers.html
similarity index 100%
rename from webrunner/net_http/testdata/with-headers.html
rename to fuchsia/net_http/testdata/with-headers.html
diff --git a/webrunner/net_http/testdata/with-headers.html.mock-http-headers b/fuchsia/net_http/testdata/with-headers.html.mock-http-headers
similarity index 100%
rename from webrunner/net_http/testdata/with-headers.html.mock-http-headers
rename to fuchsia/net_http/testdata/with-headers.html.mock-http-headers
diff --git a/webrunner/net_http/url_loader_impl.cc b/fuchsia/net_http/url_loader_impl.cc
similarity index 99%
rename from webrunner/net_http/url_loader_impl.cc
rename to fuchsia/net_http/url_loader_impl.cc
index f959561..c4d4468 100644
--- a/webrunner/net_http/url_loader_impl.cc
+++ b/fuchsia/net_http/url_loader_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/net_http/url_loader_impl.h"
+#include "fuchsia/net_http/url_loader_impl.h"
 
 #include "base/fuchsia/fuchsia_logging.h"
 #include "base/message_loop/message_loop_current.h"
diff --git a/webrunner/net_http/url_loader_impl.h b/fuchsia/net_http/url_loader_impl.h
similarity index 96%
rename from webrunner/net_http/url_loader_impl.h
rename to fuchsia/net_http/url_loader_impl.h
index d412f750..7405bac 100644
--- a/webrunner/net_http/url_loader_impl.h
+++ b/fuchsia/net_http/url_loader_impl.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_NET_HTTP_URL_LOADER_IMPL_H_
-#define WEBRUNNER_NET_HTTP_URL_LOADER_IMPL_H_
+#ifndef FUCHSIA_NET_HTTP_URL_LOADER_IMPL_H_
+#define FUCHSIA_NET_HTTP_URL_LOADER_IMPL_H_
 
 #include <fuchsia/net/oldhttp/cpp/fidl.h>
 #include <fuchsia/sys/cpp/fidl.h>
@@ -122,4 +122,4 @@
 
 }  // namespace net_http
 
-#endif  // WEBRUNNER_NET_HTTP_URL_LOADER_IMPL_H_
\ No newline at end of file
+#endif  // FUCHSIA_NET_HTTP_URL_LOADER_IMPL_H_
\ No newline at end of file
diff --git a/webrunner/renderer/DEPS b/fuchsia/renderer/DEPS
similarity index 100%
rename from webrunner/renderer/DEPS
rename to fuchsia/renderer/DEPS
diff --git a/webrunner/renderer/on_load_script_injector.cc b/fuchsia/renderer/on_load_script_injector.cc
similarity index 97%
rename from webrunner/renderer/on_load_script_injector.cc
rename to fuchsia/renderer/on_load_script_injector.cc
index 1a5abe9..904fdae85 100644
--- a/webrunner/renderer/on_load_script_injector.cc
+++ b/fuchsia/renderer/on_load_script_injector.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/renderer/on_load_script_injector.h"
+#include "fuchsia/renderer/on_load_script_injector.h"
 
 #include <lib/zx/vmo.h>
 #include <utility>
diff --git a/webrunner/renderer/on_load_script_injector.h b/fuchsia/renderer/on_load_script_injector.h
similarity index 87%
rename from webrunner/renderer/on_load_script_injector.h
rename to fuchsia/renderer/on_load_script_injector.h
index 7208a37..f814a2b 100644
--- a/webrunner/renderer/on_load_script_injector.h
+++ b/fuchsia/renderer/on_load_script_injector.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_RENDERER_ON_LOAD_SCRIPT_INJECTOR_H_
-#define WEBRUNNER_RENDERER_ON_LOAD_SCRIPT_INJECTOR_H_
+#ifndef FUCHSIA_RENDERER_ON_LOAD_SCRIPT_INJECTOR_H_
+#define FUCHSIA_RENDERER_ON_LOAD_SCRIPT_INJECTOR_H_
 
 #include <lib/zx/vmo.h>
 #include <vector>
@@ -12,8 +12,8 @@
 #include "base/memory/shared_memory_handle.h"
 #include "base/memory/weak_ptr.h"
 #include "content/public/renderer/render_frame_observer.h"
+#include "fuchsia/common/on_load_script_injector.mojom.h"
 #include "mojo/public/cpp/bindings/associated_binding_set.h"
-#include "webrunner/common/on_load_script_injector.mojom.h"
 
 namespace webrunner {
 
@@ -47,4 +47,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_RENDERER_ON_LOAD_SCRIPT_INJECTOR_H_
+#endif  // FUCHSIA_RENDERER_ON_LOAD_SCRIPT_INJECTOR_H_
diff --git a/webrunner/renderer/webrunner_content_renderer_client.cc b/fuchsia/renderer/webrunner_content_renderer_client.cc
similarity index 87%
rename from webrunner/renderer/webrunner_content_renderer_client.cc
rename to fuchsia/renderer/webrunner_content_renderer_client.cc
index c335fd9..cd05d13 100644
--- a/webrunner/renderer/webrunner_content_renderer_client.cc
+++ b/fuchsia/renderer/webrunner_content_renderer_client.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/renderer/webrunner_content_renderer_client.h"
+#include "fuchsia/renderer/webrunner_content_renderer_client.h"
 
 #include "base/macros.h"
 #include "content/public/renderer/render_frame.h"
+#include "fuchsia/renderer/on_load_script_injector.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
-#include "webrunner/renderer/on_load_script_injector.h"
 
 namespace webrunner {
 
diff --git a/webrunner/renderer/webrunner_content_renderer_client.h b/fuchsia/renderer/webrunner_content_renderer_client.h
similarity index 77%
rename from webrunner/renderer/webrunner_content_renderer_client.h
rename to fuchsia/renderer/webrunner_content_renderer_client.h
index fd6bd15..fdadb5e 100644
--- a/webrunner/renderer/webrunner_content_renderer_client.h
+++ b/fuchsia/renderer/webrunner_content_renderer_client.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_RENDERER_WEBRUNNER_CONTENT_RENDERER_CLIENT_H_
-#define WEBRUNNER_RENDERER_WEBRUNNER_CONTENT_RENDERER_CLIENT_H_
+#ifndef FUCHSIA_RENDERER_WEBRUNNER_CONTENT_RENDERER_CLIENT_H_
+#define FUCHSIA_RENDERER_WEBRUNNER_CONTENT_RENDERER_CLIENT_H_
 
 #include "base/macros.h"
 #include "content/public/renderer/content_renderer_client.h"
@@ -24,4 +24,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_RENDERER_WEBRUNNER_CONTENT_RENDERER_CLIENT_H_
+#endif  // FUCHSIA_RENDERER_WEBRUNNER_CONTENT_RENDERER_CLIENT_H_
diff --git a/webrunner/service/DEPS b/fuchsia/service/DEPS
similarity index 100%
rename from webrunner/service/DEPS
rename to fuchsia/service/DEPS
diff --git a/webrunner/service/common.cc b/fuchsia/service/common.cc
similarity index 87%
rename from webrunner/service/common.cc
rename to fuchsia/service/common.cc
index ccec139..cdccace 100644
--- a/webrunner/service/common.cc
+++ b/fuchsia/service/common.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/service/common.h"
+#include "fuchsia/service/common.h"
 
 namespace webrunner {
 
diff --git a/webrunner/service/common.h b/fuchsia/service/common.h
similarity index 74%
rename from webrunner/service/common.h
rename to fuchsia/service/common.h
index 0d3963d..e48ec86 100644
--- a/webrunner/service/common.h
+++ b/fuchsia/service/common.h
@@ -2,18 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_SERVICE_COMMON_H_
-#define WEBRUNNER_SERVICE_COMMON_H_
+#ifndef FUCHSIA_SERVICE_COMMON_H_
+#define FUCHSIA_SERVICE_COMMON_H_
 
 #include <zircon/processargs.h>
 
-#include "webrunner/common/webrunner_export.h"
+#include "fuchsia/common/fuchsia_export.h"
 
 namespace webrunner {
 
 // Switch passed to content process when running in incognito mode, i.e. when
 // there is no kWebContextDataPath.
-WEBRUNNER_EXPORT extern const char kIncognitoSwitch[];
+FUCHSIA_EXPORT extern const char kIncognitoSwitch[];
 
 // This file contains constants and functions shared between Context and
 // ContextProvider processes.
@@ -24,4 +24,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_SERVICE_COMMON_H_
+#endif  // FUCHSIA_SERVICE_COMMON_H_
diff --git a/webrunner/service/context_provider_impl.cc b/fuchsia/service/context_provider_impl.cc
similarity index 97%
rename from webrunner/service/context_provider_impl.cc
rename to fuchsia/service/context_provider_impl.cc
index 1031d952..4c64a1b 100644
--- a/webrunner/service/context_provider_impl.cc
+++ b/fuchsia/service/context_provider_impl.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/service/context_provider_impl.h"
+#include "fuchsia/service/context_provider_impl.h"
 
 #include <fuchsia/sys/cpp/fidl.h>
 #include <lib/async/default.h>
@@ -25,8 +25,8 @@
 #include "base/logging.h"
 #include "base/path_service.h"
 #include "base/process/launch.h"
+#include "fuchsia/service/common.h"
 #include "services/service_manager/sandbox/fuchsia/sandbox_policy_fuchsia.h"
-#include "webrunner/service/common.h"
 
 namespace webrunner {
 namespace {
diff --git a/webrunner/service/context_provider_impl.h b/fuchsia/service/context_provider_impl.h
similarity index 89%
rename from webrunner/service/context_provider_impl.h
rename to fuchsia/service/context_provider_impl.h
index 6efb9ce..84239c8 100644
--- a/webrunner/service/context_provider_impl.h
+++ b/fuchsia/service/context_provider_impl.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_SERVICE_CONTEXT_PROVIDER_IMPL_H_
-#define WEBRUNNER_SERVICE_CONTEXT_PROVIDER_IMPL_H_
+#ifndef FUCHSIA_SERVICE_CONTEXT_PROVIDER_IMPL_H_
+#define FUCHSIA_SERVICE_CONTEXT_PROVIDER_IMPL_H_
 
 #include <lib/fidl/cpp/binding_set.h>
 #include <memory>
@@ -11,7 +11,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "chromium/web/cpp/fidl.h"
-#include "webrunner/common/webrunner_export.h"
+#include "fuchsia/common/fuchsia_export.h"
 
 namespace base {
 class CommandLine;
@@ -21,7 +21,7 @@
 
 namespace webrunner {
 
-class WEBRUNNER_EXPORT ContextProviderImpl
+class FUCHSIA_EXPORT ContextProviderImpl
     : public chromium::web::ContextProvider {
  public:
   ContextProviderImpl();
@@ -68,4 +68,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_SERVICE_CONTEXT_PROVIDER_IMPL_H_
+#endif  // FUCHSIA_SERVICE_CONTEXT_PROVIDER_IMPL_H_
diff --git a/webrunner/service/context_provider_impl_unittest.cc b/fuchsia/service/context_provider_impl_unittest.cc
similarity index 97%
rename from webrunner/service/context_provider_impl_unittest.cc
rename to fuchsia/service/context_provider_impl_unittest.cc
index 9142843..ac744c1 100644
--- a/webrunner/service/context_provider_impl_unittest.cc
+++ b/fuchsia/service/context_provider_impl_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/service/context_provider_impl.h"
+#include "fuchsia/service/context_provider_impl.h"
 
 #include <lib/fdio/util.h>
 #include <lib/fidl/cpp/binding.h>
@@ -28,11 +28,11 @@
 #include "base/message_loop/message_loop.h"
 #include "base/path_service.h"
 #include "base/test/multiprocess_test.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl_test_base.h"
+#include "fuchsia/service/common.h"
+#include "fuchsia/test/fake_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/multiprocess_func_list.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl_test_base.h"
-#include "webrunner/service/common.h"
-#include "webrunner/test/fake_context.h"
 
 namespace webrunner {
 namespace {
diff --git a/webrunner/service/context_provider_main.cc b/fuchsia/service/context_provider_main.cc
similarity index 88%
rename from webrunner/service/context_provider_main.cc
rename to fuchsia/service/context_provider_main.cc
index fe0ddc15..dfa1749 100644
--- a/webrunner/service/context_provider_main.cc
+++ b/fuchsia/service/context_provider_main.cc
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/service/context_provider_main.h"
+#include "fuchsia/service/context_provider_main.h"
 
 #include "base/fuchsia/scoped_service_binding.h"
 #include "base/fuchsia/service_directory.h"
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
-#include "webrunner/service/context_provider_impl.h"
+#include "fuchsia/service/context_provider_impl.h"
 
 namespace webrunner {
 
diff --git a/webrunner/service/context_provider_main.h b/fuchsia/service/context_provider_main.h
similarity index 61%
rename from webrunner/service/context_provider_main.h
rename to fuchsia/service/context_provider_main.h
index b15358b7..4ae7b8c 100644
--- a/webrunner/service/context_provider_main.h
+++ b/fuchsia/service/context_provider_main.h
@@ -2,18 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_SERVICE_CONTEXT_PROVIDER_MAIN_H_
-#define WEBRUNNER_SERVICE_CONTEXT_PROVIDER_MAIN_H_
+#ifndef FUCHSIA_SERVICE_CONTEXT_PROVIDER_MAIN_H_
+#define FUCHSIA_SERVICE_CONTEXT_PROVIDER_MAIN_H_
 
-#include "webrunner/common/webrunner_export.h"
+#include "fuchsia/common/fuchsia_export.h"
 
 namespace webrunner {
 
 // Main function for the process that implements web::ContextProvider interface.
 // Called by WebRunnerMainDelegate when the process is started without --type
 // argument.
-WEBRUNNER_EXPORT int ContextProviderMain();
+FUCHSIA_EXPORT int ContextProviderMain();
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_SERVICE_CONTEXT_PROVIDER_MAIN_H_
\ No newline at end of file
+#endif  // FUCHSIA_SERVICE_CONTEXT_PROVIDER_MAIN_H_
\ No newline at end of file
diff --git a/webrunner/service/sandbox_policy b/fuchsia/service/sandbox_policy
similarity index 100%
rename from webrunner/service/sandbox_policy
rename to fuchsia/service/sandbox_policy
diff --git a/webrunner/service/web_content_service_main.cc b/fuchsia/service/web_content_service_main.cc
similarity index 90%
rename from webrunner/service/web_content_service_main.cc
rename to fuchsia/service/web_content_service_main.cc
index 0fa8e2d..6fb4d72 100644
--- a/webrunner/service/web_content_service_main.cc
+++ b/fuchsia/service/web_content_service_main.cc
@@ -6,10 +6,10 @@
 
 #include "base/command_line.h"
 #include "content/public/app/content_main.h"
+#include "fuchsia/service/common.h"
+#include "fuchsia/service/context_provider_main.h"
+#include "fuchsia/service/webrunner_main_delegate.h"
 #include "services/service_manager/embedder/switches.h"
-#include "webrunner/service/common.h"
-#include "webrunner/service/context_provider_main.h"
-#include "webrunner/service/webrunner_main_delegate.h"
 
 int main(int argc, const char** argv) {
   base::CommandLine::Init(argc, argv);
diff --git a/webrunner/service/webrunner_main_delegate.cc b/fuchsia/service/webrunner_main_delegate.cc
similarity index 89%
rename from webrunner/service/webrunner_main_delegate.cc
rename to fuchsia/service/webrunner_main_delegate.cc
index ae772ed..3617114 100644
--- a/webrunner/service/webrunner_main_delegate.cc
+++ b/fuchsia/service/webrunner_main_delegate.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/service/webrunner_main_delegate.h"
+#include "fuchsia/service/webrunner_main_delegate.h"
 
 #include <utility>
 
@@ -10,12 +10,12 @@
 #include "base/command_line.h"
 #include "base/path_service.h"
 #include "content/public/common/content_switches.h"
+#include "fuchsia/browser/webrunner_browser_main.h"
+#include "fuchsia/browser/webrunner_content_browser_client.h"
+#include "fuchsia/common/webrunner_content_client.h"
+#include "fuchsia/renderer/webrunner_content_renderer_client.h"
+#include "fuchsia/service/common.h"
 #include "ui/base/resource/resource_bundle.h"
-#include "webrunner/browser/webrunner_browser_main.h"
-#include "webrunner/browser/webrunner_content_browser_client.h"
-#include "webrunner/common/webrunner_content_client.h"
-#include "webrunner/renderer/webrunner_content_renderer_client.h"
-#include "webrunner/service/common.h"
 
 namespace webrunner {
 namespace {
diff --git a/webrunner/service/webrunner_main_delegate.h b/fuchsia/service/webrunner_main_delegate.h
similarity index 85%
rename from webrunner/service/webrunner_main_delegate.h
rename to fuchsia/service/webrunner_main_delegate.h
index 4e4ee5b..c430930c 100644
--- a/webrunner/service/webrunner_main_delegate.h
+++ b/fuchsia/service/webrunner_main_delegate.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_SERVICE_WEBRUNNER_MAIN_DELEGATE_H_
-#define WEBRUNNER_SERVICE_WEBRUNNER_MAIN_DELEGATE_H_
+#ifndef FUCHSIA_SERVICE_WEBRUNNER_MAIN_DELEGATE_H_
+#define FUCHSIA_SERVICE_WEBRUNNER_MAIN_DELEGATE_H_
 
 #include <lib/zx/channel.h>
 #include <memory>
@@ -11,7 +11,7 @@
 
 #include "base/macros.h"
 #include "content/public/app/content_main_delegate.h"
-#include "webrunner/common/webrunner_export.h"
+#include "fuchsia/common/fuchsia_export.h"
 
 namespace content {
 class ContentClient;
@@ -22,7 +22,7 @@
 class WebRunnerContentBrowserClient;
 class WebRunnerContentRendererClient;
 
-class WEBRUNNER_EXPORT WebRunnerMainDelegate
+class FUCHSIA_EXPORT WebRunnerMainDelegate
     : public content::ContentMainDelegate {
  public:
   explicit WebRunnerMainDelegate(zx::channel context_channel);
@@ -55,4 +55,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_SERVICE_WEBRUNNER_MAIN_DELEGATE_H_
+#endif  // FUCHSIA_SERVICE_WEBRUNNER_MAIN_DELEGATE_H_
diff --git a/webrunner/test/fake_context.cc b/fuchsia/test/fake_context.cc
similarity index 97%
rename from webrunner/test/fake_context.cc
rename to fuchsia/test/fake_context.cc
index b4890eb8..d2eafb6c 100644
--- a/webrunner/test/fake_context.cc
+++ b/fuchsia/test/fake_context.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "webrunner/test/fake_context.h"
+#include "fuchsia/test/fake_context.h"
 
 #include "base/logging.h"
 
diff --git a/webrunner/test/fake_context.h b/fuchsia/test/fake_context.h
similarity index 91%
rename from webrunner/test/fake_context.h
rename to fuchsia/test/fake_context.h
index b4236d4..d0505e9 100644
--- a/webrunner/test/fake_context.h
+++ b/fuchsia/test/fake_context.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_TEST_FAKE_CONTEXT_H_
-#define WEBRUNNER_TEST_FAKE_CONTEXT_H_
+#ifndef FUCHSIA_TEST_FAKE_CONTEXT_H_
+#define FUCHSIA_TEST_FAKE_CONTEXT_H_
 
 #include <lib/fidl/cpp/binding.h>
 #include <lib/fidl/cpp/binding_set.h>
@@ -12,8 +12,8 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl.h"
-#include "webrunner/fidl/chromium/web/cpp/fidl_test_base.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl_test_base.h"
 
 namespace webrunner {
 
@@ -87,4 +87,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_TEST_FAKE_CONTEXT_H_
+#endif  // FUCHSIA_TEST_FAKE_CONTEXT_H_
diff --git a/webrunner/test/promise.h b/fuchsia/test/promise.h
similarity index 94%
rename from webrunner/test/promise.h
rename to fuchsia/test/promise.h
index 557c1107..c087f587 100644
--- a/webrunner/test/promise.h
+++ b/fuchsia/test/promise.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef WEBRUNNER_TEST_PROMISE_H_
-#define WEBRUNNER_TEST_PROMISE_H_
+#ifndef FUCHSIA_TEST_PROMISE_H_
+#define FUCHSIA_TEST_PROMISE_H_
 
 #include "base/bind_helpers.h"
 #include "base/optional.h"
@@ -66,4 +66,4 @@
 
 }  // namespace webrunner
 
-#endif  // WEBRUNNER_TEST_PROMISE_H_
\ No newline at end of file
+#endif  // FUCHSIA_TEST_PROMISE_H_
\ No newline at end of file
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc b/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
index 9bb4b7d2..469e95e 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_ahardwarebuffer.cc
@@ -96,6 +96,7 @@
   base::ScopedFD gl_write_sync_fd_;
   base::ScopedFD vk_read_sync_fd_;
 
+  sk_sp<SkPromiseImageTexture> cached_promise_texture_;
   DISALLOW_COPY_AND_ASSIGN(SharedImageBackingAHB);
 };
 
@@ -161,16 +162,15 @@
 class SharedImageRepresentationSkiaGLAHB
     : public SharedImageRepresentationSkia {
  public:
-  SharedImageRepresentationSkiaGLAHB(SharedImageManager* manager,
-                                     SharedImageBacking* backing,
-                                     MemoryTypeTracker* tracker,
-                                     GLenum target,
-                                     GLuint service_id)
-      : SharedImageRepresentationSkia(manager, backing, tracker) {
-    GrBackendTexture backend_texture;
-    GetGrBackendTexture(gl::GLContext::GetCurrent()->GetVersionInfo(), target,
-                        size(), service_id, format(), &backend_texture);
-    promise_texture_ = SkPromiseImageTexture::Make(backend_texture);
+  SharedImageRepresentationSkiaGLAHB(
+      SharedImageManager* manager,
+      SharedImageBacking* backing,
+      sk_sp<SkPromiseImageTexture> cached_promise_image_texture,
+      MemoryTypeTracker* tracker,
+      GLenum target,
+      GLuint service_id)
+      : SharedImageRepresentationSkia(manager, backing, tracker),
+        promise_texture_(cached_promise_image_texture) {
 #if DCHECK_IS_ON()
     context_ = gl::GLContext::GetCurrent();
 #endif
@@ -538,9 +538,17 @@
   if (!GenGLTexture())
     return nullptr;
 
+  if (!cached_promise_texture_) {
+    GrBackendTexture backend_texture;
+    GetGrBackendTexture(gl::GLContext::GetCurrent()->GetVersionInfo(),
+                        texture_->target(), size(), texture_->service_id(),
+                        format(), &backend_texture);
+    cached_promise_texture_ = SkPromiseImageTexture::Make(backend_texture);
+  }
   DCHECK(texture_);
   return std::make_unique<SharedImageRepresentationSkiaGLAHB>(
-      manager, this, tracker, texture_->target(), texture_->service_id());
+      manager, this, cached_promise_texture_, tracker, texture_->target(),
+      texture_->service_id());
 }
 
 bool SharedImageBackingAHB::GenGLTexture() {
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
index 98f46e2..63e3150 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
@@ -229,16 +229,21 @@
 
 class SharedImageRepresentationSkiaImpl : public SharedImageRepresentationSkia {
  public:
-  SharedImageRepresentationSkiaImpl(SharedImageManager* manager,
-                                    SharedImageBacking* backing,
-                                    MemoryTypeTracker* tracker,
-                                    GLenum target,
-                                    GLuint service_id)
-      : SharedImageRepresentationSkia(manager, backing, tracker) {
-    GrBackendTexture backend_texture;
-    GetGrBackendTexture(gl::GLContext::GetCurrent()->GetVersionInfo(), target,
-                        size(), service_id, format(), &backend_texture);
-    promise_texture_ = SkPromiseImageTexture::Make(backend_texture);
+  SharedImageRepresentationSkiaImpl(
+      SharedImageManager* manager,
+      SharedImageBacking* backing,
+      sk_sp<SkPromiseImageTexture> cached_promise_texture,
+      MemoryTypeTracker* tracker,
+      GLenum target,
+      GLuint service_id)
+      : SharedImageRepresentationSkia(manager, backing, tracker),
+        promise_texture_(cached_promise_texture) {
+    if (!promise_texture_) {
+      GrBackendTexture backend_texture;
+      GetGrBackendTexture(gl::GLContext::GetCurrent()->GetVersionInfo(), target,
+                          size(), service_id, format(), &backend_texture);
+      promise_texture_ = SkPromiseImageTexture::Make(backend_texture);
+    }
 #if DCHECK_IS_ON()
     context_ = gl::GLContext::GetCurrent();
 #endif
@@ -284,6 +289,8 @@
     // TODO(ericrk): Handle begin/end correctness checks.
   }
 
+  sk_sp<SkPromiseImageTexture> promise_texture() { return promise_texture_; }
+
  private:
   void CheckContext() {
 #if DCHECK_IS_ON()
@@ -389,12 +396,16 @@
   std::unique_ptr<SharedImageRepresentationSkia> ProduceSkia(
       SharedImageManager* manager,
       MemoryTypeTracker* tracker) override {
-    return std::make_unique<SharedImageRepresentationSkiaImpl>(
-        manager, this, tracker, texture_->target(), texture_->service_id());
+    auto result = std::make_unique<SharedImageRepresentationSkiaImpl>(
+        manager, this, cached_promise_texture_, tracker, texture_->target(),
+        texture_->service_id());
+    cached_promise_texture_ = result->promise_texture();
+    return result;
   }
 
  private:
   gles2::Texture* texture_ = nullptr;
+  sk_sp<SkPromiseImageTexture> cached_promise_texture_;
 };
 
 // Implementation of SharedImageBacking that creates a GL Texture and stores it
@@ -485,13 +496,17 @@
   std::unique_ptr<SharedImageRepresentationSkia> ProduceSkia(
       SharedImageManager* manager,
       MemoryTypeTracker* tracker) override {
-    return std::make_unique<SharedImageRepresentationSkiaImpl>(
-        manager, this, tracker, texture_passthrough_->target(),
-        texture_passthrough_->service_id());
+    auto result = std::make_unique<SharedImageRepresentationSkiaImpl>(
+        manager, this, cached_promise_texture_, tracker,
+        texture_passthrough_->target(), texture_passthrough_->service_id());
+    cached_promise_texture_ = result->promise_texture();
+    return result;
   }
 
  private:
   scoped_refptr<gles2::TexturePassthrough> texture_passthrough_;
+  sk_sp<SkPromiseImageTexture> cached_promise_texture_;
+
   bool is_cleared_ = false;
 };
 
diff --git a/gpu/command_buffer/service/wrapped_sk_image.cc b/gpu/command_buffer/service/wrapped_sk_image.cc
index e76fa8b24..cca87d4 100644
--- a/gpu/command_buffer/service/wrapped_sk_image.cc
+++ b/gpu/command_buffer/service/wrapped_sk_image.cc
@@ -82,11 +82,7 @@
         final_msaa_count, color_type, /*colorSpace=*/nullptr, &surface_props);
   }
 
-  bool GetGrBackendTexture(GrBackendTexture* gr_texture) const {
-    context_state_->need_context_state_reset = true;
-    *gr_texture = image_->getBackendTexture(/*flushPendingGrContextIO=*/true);
-    return gr_texture->isValid();
-  }
+  sk_sp<SkPromiseImageTexture> promise_texture() { return promise_texture_; }
 
  protected:
   std::unique_ptr<SharedImageRepresentationSkia> ProduceSkia(
@@ -147,6 +143,7 @@
         image_->getBackendTexture(/*flushPendingGrContextIO=*/false);
     if (!gr_texture.isValid())
       return false;
+    promise_texture_ = SkPromiseImageTexture::Make(gr_texture);
 
     switch (gr_texture.backend()) {
       case GrBackendApi::kOpenGL: {
@@ -171,6 +168,8 @@
   RasterDecoderContextState* const context_state_;
 
   sk_sp<SkImage> image_;
+  sk_sp<SkPromiseImageTexture> promise_texture_;
+
   bool cleared_ = false;
 
   uint64_t tracing_id_ = 0;
@@ -207,12 +206,7 @@
   }
 
   sk_sp<SkPromiseImageTexture> BeginReadAccess(SkSurface* sk_surface) override {
-    if (!promise_texture_) {
-      GrBackendTexture backend_texture;
-      wrapped_sk_image()->GetGrBackendTexture(&backend_texture);
-      promise_texture_ = SkPromiseImageTexture::Make(backend_texture);
-    }
-    return promise_texture_;
+    return wrapped_sk_image()->promise_texture();
   }
 
   void EndReadAccess() override {
@@ -225,7 +219,6 @@
   }
 
   SkSurface* write_surface_ = nullptr;
-  sk_sp<SkPromiseImageTexture> promise_texture_;
 };
 
 }  // namespace
diff --git a/ios/chrome/browser/experimental_flags.mm b/ios/chrome/browser/experimental_flags.mm
index c6dd2eb..e072e71 100644
--- a/ios/chrome/browser/experimental_flags.mm
+++ b/ios/chrome/browser/experimental_flags.mm
@@ -48,7 +48,7 @@
 namespace experimental_flags {
 
 const base::Feature kExternalFilesLoadedInWebState{
-    "ExternalFilesLoadedInWebState", base::FEATURE_DISABLED_BY_DEFAULT};
+    "ExternalFilesLoadedInWebState", base::FEATURE_ENABLED_BY_DEFAULT};
 
 bool AlwaysDisplayFirstRun() {
   return
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view_controller.mm
index 2faf61b..0055ce60 100644
--- a/ios/chrome/browser/ui/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view_controller.mm
@@ -2376,6 +2376,9 @@
       UIViewController* viewController =
           _ntpCoordinatorsForWebStates[tab.webState].viewController;
       viewController.view.frame = [self ntpFrameForWebState:tab.webState];
+      // TODO(crbug.com/873729): For a newly created WebState, the session will
+      // not be restored until LoadIfNecessary call. Remove when fixed.
+      tab.webState->GetNavigationManager()->LoadIfNecessary();
       self.browserContainerViewController.contentViewController =
           viewController;
     } else {
diff --git a/ios/chrome/browser/ui/side_swipe/BUILD.gn b/ios/chrome/browser/ui/side_swipe/BUILD.gn
index 333368ed..6dde15c 100644
--- a/ios/chrome/browser/ui/side_swipe/BUILD.gn
+++ b/ios/chrome/browser/ui/side_swipe/BUILD.gn
@@ -36,6 +36,7 @@
     "//ios/chrome/browser/ui/tabs/requirements",
     "//ios/chrome/browser/ui/toolbar/public",
     "//ios/chrome/browser/web",
+    "//ios/chrome/browser/web_state_list",
     "//ios/chrome/common",
     "//ios/web/public",
     "//ui/base",
diff --git a/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm b/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm
index d7e2b842..dc5d335010 100644
--- a/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm
+++ b/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm
@@ -28,6 +28,7 @@
 #include "ios/chrome/browser/ui/util/ui_util.h"
 #import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
 #import "ios/chrome/browser/web/web_navigation_util.h"
+#import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/web/public/navigation_item.h"
 #import "ios/web/public/web_client.h"
 #import "ios/web/public/web_state/web_state_observer_bridge.h"
@@ -407,10 +408,14 @@
 }
 
 - (BOOL)canNavigate:(BOOL)goBack {
-  if (goBack && [[model_ currentTab] canGoBack]) {
+  WebStateList* webStateList = model_.webStateList;
+  if (!webStateList || !webStateList->GetActiveWebState())
+    return NO;
+  web::WebState* webState = webStateList->GetActiveWebState();
+  if (goBack && webState->GetNavigationManager()->CanGoBack()) {
     return YES;
   }
-  if (!goBack && [[model_ currentTab] canGoForward]) {
+  if (!goBack && webState->GetNavigationManager()->CanGoForward()) {
     return YES;
   }
   return NO;
@@ -455,11 +460,14 @@
     animatedFullscreenDisabler_ = nullptr;
   }
 
-  __weak Tab* weakCurrentTab = [model_ currentTab];
   [pageSideSwipeView_ handleHorizontalPan:gesture
       onOverThresholdCompletion:^{
+        WebStateList* webStateList = model_.webStateList;
+        web::WebState* webState = nullptr;
+        if (webStateList) {
+          webState = webStateList->GetActiveWebState();
+        }
         BOOL wantsBack = IsSwipingBack(gesture.direction);
-        web::WebState* webState = [weakCurrentTab webState];
         if (webState) {
           if (wantsBack) {
             web_navigation_util::GoBack(webState);
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_cell.mm b/ios/chrome/browser/ui/tab_grid/grid/grid_cell.mm
index ef39d77f..070ffdf 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_cell.mm
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_cell.mm
@@ -61,6 +61,22 @@
 @end
 
 @implementation GridCell
+// Public properties.
+@synthesize delegate = _delegate;
+@synthesize theme = _theme;
+@synthesize itemIdentifier = _itemIdentifier;
+@synthesize icon = _icon;
+@synthesize snapshot = _snapshot;
+@synthesize title = _title;
+@synthesize titleHidden = _titleHidden;
+// Private properties.
+@synthesize topBarHeight = _topBarHeight;
+@synthesize topBar = _topBar;
+@synthesize snapshotView = _snapshotView;
+@synthesize titleLabel = _titleLabel;
+@synthesize closeIconView = _closeIconView;
+@synthesize closeTapTargetButton = _closeTapTargetButton;
+@synthesize border = _border;
 
 // |-dequeueReusableCellWithReuseIdentifier:forIndexPath:| calls this method to
 // initialize a cell.
@@ -91,11 +107,15 @@
     _snapshotView = snapshotView;
     _closeTapTargetButton = closeTapTargetButton;
 
+    _topBarHeight =
+        [topBar.heightAnchor constraintEqualToConstant:kGridCellHeaderHeight];
+
     NSArray* constraints = @[
       [topBar.topAnchor constraintEqualToAnchor:contentView.topAnchor],
       [topBar.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor],
       [topBar.trailingAnchor
           constraintEqualToAnchor:contentView.trailingAnchor],
+      _topBarHeight,
       [snapshotView.topAnchor constraintEqualToAnchor:topBar.bottomAnchor],
       [snapshotView.leadingAnchor
           constraintEqualToAnchor:contentView.leadingAnchor],
@@ -254,8 +274,6 @@
   _closeIconView = closeIconView;
 
   _accessibilityConstraints = @[
-    [topBar.heightAnchor
-        constraintEqualToConstant:kGridCellHeaderAccessibilityHeight],
     [titleLabel.leadingAnchor
         constraintEqualToAnchor:topBar.leadingAnchor
                        constant:kGridCellHeaderLeadingInset],
@@ -264,7 +282,6 @@
   ];
 
   _nonAccessibilityConstraints = @[
-    [topBar.heightAnchor constraintEqualToConstant:kGridCellHeaderHeight],
     [iconView.leadingAnchor
         constraintEqualToAnchor:topBar.leadingAnchor
                        constant:kGridCellHeaderLeadingInset],
@@ -279,11 +296,10 @@
   NSArray* constraints = @[
     [titleLabel.centerYAnchor constraintEqualToAnchor:topBar.centerYAnchor],
     [titleLabel.trailingAnchor
-        constraintEqualToAnchor:closeIconView.leadingAnchor
-                       constant:-kGridCellTitleLabelContentInset],
-    [closeIconView.topAnchor
-        constraintEqualToAnchor:topBar.topAnchor
-                       constant:kGridCellCloseButtonContentInset],
+        constraintLessThanOrEqualToAnchor:closeIconView.leadingAnchor
+                                 constant:-kGridCellTitleLabelContentInset],
+    [closeIconView.topAnchor constraintEqualToAnchor:topBar.topAnchor],
+    [closeIconView.bottomAnchor constraintEqualToAnchor:topBar.bottomAnchor],
     [closeIconView.trailingAnchor
         constraintEqualToAnchor:topBar.trailingAnchor
                        constant:-kGridCellCloseButtonContentInset],
@@ -308,11 +324,9 @@
 - (void)updateTopBar {
   if (UIContentSizeCategoryIsAccessibilityCategory(
           self.traitCollection.preferredContentSizeCategory)) {
-    self.titleLabel.numberOfLines = 2;
     [NSLayoutConstraint deactivateConstraints:_nonAccessibilityConstraints];
     [NSLayoutConstraint activateConstraints:_accessibilityConstraints];
   } else {
-    self.titleLabel.numberOfLines = 1;
     [NSLayoutConstraint deactivateConstraints:_accessibilityConstraints];
     [NSLayoutConstraint activateConstraints:_nonAccessibilityConstraints];
   }
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_constants.h b/ios/chrome/browser/ui/tab_grid/grid/grid_constants.h
index 9fe18a6bb..27bdb3d 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_constants.h
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_constants.h
@@ -75,12 +75,10 @@
 extern const CGSize kGridCellSizeSmall;
 extern const CGSize kGridCellSizeMedium;
 extern const CGSize kGridCellSizeLarge;
-extern const CGSize kGridCellSizeAccessibility;
 extern const CGFloat kGridCellCornerRadius;
 extern const CGFloat kGridCellIconCornerRadius;
 // The cell header contains the icon, title, and close button.
 extern const CGFloat kGridCellHeaderHeight;
-extern const CGFloat kGridCellHeaderAccessibilityHeight;
 extern const CGFloat kGridCellHeaderLeadingInset;
 extern const CGFloat kGridCellCloseTapTargetWidthHeight;
 extern const CGFloat kGridCellCloseButtonContentInset;
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_constants.mm b/ios/chrome/browser/ui/tab_grid/grid/grid_constants.mm
index 04fae2837..5c6310f7 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_constants.mm
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_constants.mm
@@ -69,12 +69,10 @@
 const CGSize kGridCellSizeSmall = CGSize{144.0f, 168.0f};
 const CGSize kGridCellSizeMedium = CGSize{168.0f, 202.0f};
 const CGSize kGridCellSizeLarge = CGSize{228.0f, 256.0f};
-const CGSize kGridCellSizeAccessibility = CGSize{288.0f, 336.0f};
 const CGFloat kGridCellCornerRadius = 13.0f;
 const CGFloat kGridCellIconCornerRadius = 3.0f;
 // The cell header contains the icon, title, and close button.
 const CGFloat kGridCellHeaderHeight = 32.0f;
-const CGFloat kGridCellHeaderAccessibilityHeight = 108.0f;
 const CGFloat kGridCellHeaderLeadingInset = 9.0f;
 const CGFloat kGridCellCloseTapTargetWidthHeight = 44.0f;
 const CGFloat kGridCellCloseButtonContentInset = 8.5f;
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_layout.mm b/ios/chrome/browser/ui/tab_grid/grid/grid_layout.mm
index 6ffd1a7..16895e7 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_layout.mm
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_layout.mm
@@ -39,13 +39,8 @@
   UIUserInterfaceSizeClass verticalSizeClass =
       self.collectionView.traitCollection.verticalSizeClass;
   CGFloat width = CGRectGetWidth(self.collectionView.bounds);
-  if (UIContentSizeCategoryIsAccessibilityCategory(
-          UIApplication.sharedApplication.preferredContentSizeCategory)) {
-    self.itemSize = kGridCellSizeAccessibility;
-    self.sectionInset = kGridLayoutInsetsRegularCompact;
-    self.minimumLineSpacing = kGridLayoutLineSpacingRegularCompact;
-  } else if (horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
-             verticalSizeClass == UIUserInterfaceSizeClassCompact) {
+  if (horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
+      verticalSizeClass == UIUserInterfaceSizeClassCompact) {
     self.itemSize = kGridCellSizeSmall;
     if (width < kGridLayoutCompactCompactLimitedWidth) {
       self.sectionInset = kGridLayoutInsetsCompactCompactLimitedWidth;
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
index 6a98c63b..44893f6 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
@@ -100,8 +100,6 @@
   return self;
 }
 
-#pragma mark - UIViewController
-
 - (void)loadView {
   self.defaultLayout = [[GridLayout alloc] init];
   self.reorderingLayout = [[GridReorderingLayout alloc] init];
@@ -167,13 +165,6 @@
   [super viewWillDisappear:animated];
 }
 
-#pragma mark - UITraitEnvironment
-
-- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
-  [super traitCollectionDidChange:previousTraitCollection];
-  [self.collectionView.collectionViewLayout invalidateLayout];
-}
-
 #pragma mark - Public
 
 - (UIScrollView*)gridView {
diff --git a/ios/chrome/browser/ui/webui/inspect/inspect_ui_egtest.mm b/ios/chrome/browser/ui/webui/inspect/inspect_ui_egtest.mm
index 47a9efbb..5cc78f3 100644
--- a/ios/chrome/browser/ui/webui/inspect/inspect_ui_egtest.mm
+++ b/ios/chrome/browser/ui/webui/inspect/inspect_ui_egtest.mm
@@ -2,11 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <Foundation/Foundation.h>
 #import <XCTest/XCTest.h>
 
 #import "base/strings/sys_string_conversions.h"
 #include "ios/chrome/browser/chrome_url_constants.h"
+#import "ios/chrome/test/app/chrome_test_util.h"
+#import "ios/chrome/test/app/web_view_interaction_test_util.h"
+#import "ios/chrome/test/earl_grey/chrome_actions.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
+#import "ios/chrome/test/earl_grey/chrome_matchers.h"
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
 #include "ios/web/public/test/element_selector.h"
 
@@ -14,40 +19,302 @@
 #error "This file requires ARC support."
 #endif
 
-namespace {
-// Id of the "Start Logging" button.
-const char kStartLoggingButtonId[] = "start-logging";
-// Id of the "Stop Logging" button.
-const char kStopLoggingButtonId[] = "stop-logging";
-}  // namespace
-
+using chrome_test_util::GetCurrentWebState;
+using chrome_test_util::TapWebViewElementWithIdInIframe;
 using web::test::ElementSelector;
 
+namespace {
+// Directory containing the |kLogoPagePath| and |kLogoPageImageSourcePath|
+// resources.
+const char kServerFilesDir[] = "ios/testing/data/http_server_files/";
+
+// Id of the "Start Logging" button.
+NSString* const kStartLoggingButtonId = @"start-logging";
+// Id of the "Stop Logging" button.
+NSString* const kStopLoggingButtonId = @"stop-logging";
+
+// Test page continaing buttons to test console logging.
+const char kConsolePage[] = "/console_with_iframe.html";
+// Id of the console page button to log a debug message.
+NSString* const kDebugMessageButtonId = @"debug";
+// Id of the console page button to log an error.
+NSString* const kErrorMessageButtonId = @"error";
+// Id of the console page button to log an info message.
+NSString* const kInfoMessageButtonId = @"info";
+// Id of the console page button to log a message.
+NSString* const kLogMessageButtonId = @"log";
+// Id of the console page button to log a warning.
+NSString* const kWarningMessageButtonId = @"warn";
+
+// Label for debug console messages.
+const char kDebugMessageLabel[] = "DEBUG";
+// Label for error console messages.
+const char kErrorMessageLabel[] = "ERROR";
+// Label for info console messages.
+const char kInfoMessageLabel[] = "INFO";
+// Label for log console messages.
+const char kLogMessageLabel[] = "LOG";
+// Label for warning console messages.
+const char kWarningMessageLabel[] = "WARN";
+
+// Text of the message emitted from the |kDebugMessageButtonId| button on
+// |kConsolePage|.
+const char kDebugMessageText[] = "This is a debug message.";
+// Text of the message emitted from the |kErrorMessageButtonId| button on
+// |kConsolePage|.
+const char kErrorMessageText[] = "This is an error message.";
+// Text of the message emitted from the |kInfoMessageButtonId| button on
+// |kConsolePage|.
+const char kInfoMessageText[] = "This is an informative message.";
+// Text of the message emitted from the |kLogMessageButtonId| button on
+// |kConsolePage|.
+const char kLogMessageText[] = "This log is very round.";
+// Text of the message emitted from the |kWarningMessageButtonId| button on
+// |kConsolePage|.
+const char kWarningMessageText[] = "This is a warning message.";
+
+// Text of the message emitted from the |kDebugMessageButtonId| button within
+// the iframe on |kConsolePage|.
+const char kIFrameDebugMessageText[] = "This is an iframe debug message.";
+// Text of the message emitted from the |kErrorMessageButtonId| button within
+// the iframe on |kConsolePage|.
+const char kIFrameErrorMessageText[] = "This is an iframe error message.";
+// Text of the message emitted from the |kInfoMessageButtonId| button within the
+// iframe on |kConsolePage|.
+const char kIFrameInfoMessageText[] = "This is an iframe informative message.";
+// Text of the message emitted from the |kLogMessageButtonId| button within the
+// iframe on |kConsolePage|.
+const char kIFrameLogMessageText[] = "This iframe log is very round.";
+// Text of the message emitted from the |kWarningMessageButtonId| button within
+// the iframe on |kConsolePage|.
+const char kIFrameWarningMessageText[] = "This is an iframe warning message.";
+
+web::test::ElementSelector StartLoggingButton() {
+  return ElementSelector::ElementSelectorId(
+      base::SysNSStringToUTF8(kStartLoggingButtonId));
+}
+
+}  // namespace
+
 // Test case for chrome://inspect WebUI page.
 @interface InspectUITestCase : ChromeTestCase
 @end
 
 @implementation InspectUITestCase
 
+- (void)setUp {
+  [super setUp];
+  self.testServer->ServeFilesFromSourceDirectory(
+      base::FilePath(kServerFilesDir));
+  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
+}
+
 // Tests that chrome://inspect allows the user to enable and disable logging.
 - (void)testStartStopLogging {
   [ChromeEarlGrey loadURL:GURL(kChromeUIInspectURL)];
 
-  ElementSelector startLoggingButton =
-      ElementSelector::ElementSelectorId(kStartLoggingButtonId);
-  [ChromeEarlGrey waitForWebViewContainingElement:startLoggingButton];
+  [ChromeEarlGrey waitForWebViewContainingElement:StartLoggingButton()];
 
-  [ChromeEarlGrey
-      tapWebViewElementWithID:base::SysUTF8ToNSString(kStartLoggingButtonId)];
+  [ChromeEarlGrey tapWebViewElementWithID:kStartLoggingButtonId];
 
-  ElementSelector stopLoggingButton =
-      ElementSelector::ElementSelectorId(kStopLoggingButtonId);
+  ElementSelector stopLoggingButton = ElementSelector::ElementSelectorId(
+      base::SysNSStringToUTF8(kStopLoggingButtonId));
   [ChromeEarlGrey waitForWebViewContainingElement:stopLoggingButton];
 
-  [ChromeEarlGrey
-      tapWebViewElementWithID:base::SysUTF8ToNSString(kStopLoggingButtonId)];
+  [ChromeEarlGrey tapWebViewElementWithID:kStopLoggingButtonId];
 
-  [ChromeEarlGrey waitForWebViewContainingElement:startLoggingButton];
+  [ChromeEarlGrey waitForWebViewContainingElement:StartLoggingButton()];
+}
+
+// Tests that log messages from a page's main frame are displayed.
+- (void)testMainFrameLogging {
+  [ChromeEarlGrey loadURL:GURL(kChromeUIInspectURL)];
+
+  // Start logging.
+  [ChromeEarlGrey waitForWebViewContainingElement:StartLoggingButton()];
+  [ChromeEarlGrey tapWebViewElementWithID:kStartLoggingButtonId];
+
+  // Open console test page.
+  [ChromeEarlGrey openNewTab];
+  const GURL consoleTestsURL = self.testServer->GetURL(kConsolePage);
+  [ChromeEarlGrey loadURL:consoleTestsURL];
+  std::string debugButtonID = base::SysNSStringToUTF8(kDebugMessageButtonId);
+  [ChromeEarlGrey
+      waitForWebViewContainingElement:ElementSelector::ElementSelectorId(
+                                          debugButtonID)];
+
+  // Log messages.
+  [ChromeEarlGrey tapWebViewElementWithID:kDebugMessageButtonId];
+  [ChromeEarlGrey tapWebViewElementWithID:kErrorMessageButtonId];
+  [ChromeEarlGrey tapWebViewElementWithID:kInfoMessageButtonId];
+  [ChromeEarlGrey tapWebViewElementWithID:kLogMessageButtonId];
+  [ChromeEarlGrey tapWebViewElementWithID:kWarningMessageButtonId];
+
+  [ChromeEarlGrey closeCurrentTab];
+  // Validate messages and labels are displayed.
+  [ChromeEarlGrey waitForWebViewContainingText:kDebugMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kDebugMessageText];
+  [ChromeEarlGrey waitForWebViewContainingText:kErrorMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kErrorMessageText];
+  [ChromeEarlGrey waitForWebViewContainingText:kInfoMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kInfoMessageText];
+  [ChromeEarlGrey waitForWebViewContainingText:kLogMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kLogMessageText];
+  [ChromeEarlGrey waitForWebViewContainingText:kWarningMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kWarningMessageText];
+}
+
+// Tests that log messages from an iframe are displayed.
+- (void)testIframeLogging {
+  [ChromeEarlGrey loadURL:GURL(kChromeUIInspectURL)];
+
+  // Start logging.
+  [ChromeEarlGrey waitForWebViewContainingElement:StartLoggingButton()];
+  [ChromeEarlGrey tapWebViewElementWithID:kStartLoggingButtonId];
+
+  // Open console test page.
+  [ChromeEarlGrey openNewTab];
+  const GURL consoleTestsURL = self.testServer->GetURL(kConsolePage);
+  [ChromeEarlGrey loadURL:consoleTestsURL];
+
+  std::string debugButtonID = base::SysNSStringToUTF8(kDebugMessageButtonId);
+  [ChromeEarlGrey
+      waitForWebViewContainingElement:ElementSelector::ElementSelectorId(
+                                          debugButtonID)];
+
+  // Log messages.
+  GREYAssertTrue(TapWebViewElementWithIdInIframe(debugButtonID),
+                 @"Failed to tap debug button.");
+
+  std::string errorButtonID = base::SysNSStringToUTF8(kErrorMessageButtonId);
+  GREYAssertTrue(TapWebViewElementWithIdInIframe(errorButtonID),
+                 @"Failed to tap error button.");
+
+  std::string infoButtonID = base::SysNSStringToUTF8(kInfoMessageButtonId);
+  GREYAssertTrue(TapWebViewElementWithIdInIframe(infoButtonID),
+                 @"Failed to tap info button.");
+
+  std::string logButtonID = base::SysNSStringToUTF8(kLogMessageButtonId);
+  GREYAssertTrue(TapWebViewElementWithIdInIframe(logButtonID),
+                 @"Failed to tap log button.");
+
+  std::string warnButtonID = base::SysNSStringToUTF8(kWarningMessageButtonId);
+  GREYAssertTrue(TapWebViewElementWithIdInIframe(warnButtonID),
+                 @"Failed to tap warn button.");
+
+  [ChromeEarlGrey closeCurrentTab];
+  // Validate messages and labels are displayed.
+  [ChromeEarlGrey waitForWebViewContainingText:kDebugMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kIFrameDebugMessageText];
+  [ChromeEarlGrey waitForWebViewContainingText:kErrorMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kIFrameErrorMessageText];
+  [ChromeEarlGrey waitForWebViewContainingText:kInfoMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kIFrameInfoMessageText];
+  [ChromeEarlGrey waitForWebViewContainingText:kLogMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kIFrameLogMessageText];
+  [ChromeEarlGrey waitForWebViewContainingText:kWarningMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kIFrameWarningMessageText];
+}
+
+// Tests that log messages are correctly displayed from multiple tabs.
+- (void)testLoggingFromMultipleTabs {
+  [ChromeEarlGrey loadURL:GURL(kChromeUIInspectURL)];
+
+  // Start logging.
+  [ChromeEarlGrey waitForWebViewContainingElement:StartLoggingButton()];
+  [ChromeEarlGrey tapWebViewElementWithID:kStartLoggingButtonId];
+
+  // Open console test page.
+  [ChromeEarlGrey openNewTab];
+  const GURL consoleTestsURL = self.testServer->GetURL(kConsolePage);
+  [ChromeEarlGrey loadURL:consoleTestsURL];
+  std::string logButtonID = base::SysNSStringToUTF8(kLogMessageButtonId);
+  [ChromeEarlGrey
+      waitForWebViewContainingElement:ElementSelector::ElementSelectorId(
+                                          logButtonID)];
+
+  // Log a message and verify it is displayed.
+  [ChromeEarlGrey tapWebViewElementWithID:kDebugMessageButtonId];
+  [ChromeEarlGrey closeCurrentTab];
+  [ChromeEarlGrey waitForWebViewContainingText:kDebugMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kDebugMessageText];
+
+  // Open console test page again.
+  [ChromeEarlGrey openNewTab];
+  [ChromeEarlGrey loadURL:consoleTestsURL];
+  [ChromeEarlGrey
+      waitForWebViewContainingElement:ElementSelector::ElementSelectorId(
+                                          logButtonID)];
+
+  // Log another message and verify it is displayed.
+  [ChromeEarlGrey tapWebViewElementWithID:kLogMessageButtonId];
+  [ChromeEarlGrey closeCurrentTab];
+  [ChromeEarlGrey waitForWebViewContainingText:kLogMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kLogMessageText];
+
+  // Ensure the log from the first tab still exists.
+  [ChromeEarlGrey waitForWebViewContainingText:kDebugMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kDebugMessageText];
+}
+
+// Tests that messages are cleared after stopping logging.
+- (void)testMessagesClearedOnStopLogging {
+  [ChromeEarlGrey loadURL:GURL(kChromeUIInspectURL)];
+
+  // Start logging.
+  [ChromeEarlGrey waitForWebViewContainingElement:StartLoggingButton()];
+  [ChromeEarlGrey tapWebViewElementWithID:kStartLoggingButtonId];
+
+  // Open console test page.
+  [ChromeEarlGrey openNewTab];
+  const GURL consoleTestsURL = self.testServer->GetURL(kConsolePage);
+  [ChromeEarlGrey loadURL:consoleTestsURL];
+  std::string logButtonID = base::SysNSStringToUTF8(kLogMessageButtonId);
+  [ChromeEarlGrey
+      waitForWebViewContainingElement:ElementSelector::ElementSelectorId(
+                                          logButtonID)];
+
+  // Log a message and verify it is displayed.
+  [ChromeEarlGrey tapWebViewElementWithID:kDebugMessageButtonId];
+  [ChromeEarlGrey closeCurrentTab];
+  [ChromeEarlGrey waitForWebViewContainingText:kDebugMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kDebugMessageText];
+
+  // Stop logging.
+  [ChromeEarlGrey tapWebViewElementWithID:kStopLoggingButtonId];
+  // Ensure message was cleared.
+  [ChromeEarlGrey waitForWebViewNotContainingText:kDebugMessageLabel];
+  [ChromeEarlGrey waitForWebViewNotContainingText:kDebugMessageText];
+}
+
+// Tests that messages are cleared after a page reload.
+- (void)testMessagesClearedOnReload {
+  [ChromeEarlGrey loadURL:GURL(kChromeUIInspectURL)];
+
+  // Start logging.
+  [ChromeEarlGrey waitForWebViewContainingElement:StartLoggingButton()];
+  [ChromeEarlGrey tapWebViewElementWithID:kStartLoggingButtonId];
+
+  // Open console test page.
+  [ChromeEarlGrey openNewTab];
+  const GURL consoleTestsURL = self.testServer->GetURL(kConsolePage);
+  [ChromeEarlGrey loadURL:consoleTestsURL];
+  std::string logButtonID = base::SysNSStringToUTF8(kLogMessageButtonId);
+  [ChromeEarlGrey
+      waitForWebViewContainingElement:ElementSelector::ElementSelectorId(
+                                          logButtonID)];
+
+  // Log a message and verify it is displayed.
+  [ChromeEarlGrey tapWebViewElementWithID:kDebugMessageButtonId];
+  [ChromeEarlGrey closeCurrentTab];
+  [ChromeEarlGrey waitForWebViewContainingText:kDebugMessageLabel];
+  [ChromeEarlGrey waitForWebViewContainingText:kDebugMessageText];
+
+  // Reload page.
+  [ChromeEarlGrey reload];
+  // Ensure message was cleared.
+  [ChromeEarlGrey waitForWebViewNotContainingText:kDebugMessageLabel];
+  [ChromeEarlGrey waitForWebViewNotContainingText:kDebugMessageText];
 }
 
 @end
diff --git a/ios/chrome/test/app/web_view_interaction_test_util.h b/ios/chrome/test/app/web_view_interaction_test_util.h
index cc122969..608822a 100644
--- a/ios/chrome/test/app/web_view_interaction_test_util.h
+++ b/ios/chrome/test/app/web_view_interaction_test_util.h
@@ -18,6 +18,12 @@
 // was successful.
 bool TapWebViewElementWithId(const std::string& element_id) WARN_UNUSED_RESULT;
 
+// Attempts to tap the element with |element_id| within window.frames[0] of the
+// current WebState using a JavaScript click() event. This only works on
+// same-origin iframes. Returns a bool indicating if the tap was successful.
+bool TapWebViewElementWithIdInIframe(const std::string& element_id)
+    WARN_UNUSED_RESULT;
+
 // Attempts to tap the element with |element_id| in the current WebState
 // using a JavaScript click() event. |error| can be nil.
 bool TapWebViewElementWithId(const std::string& element_id,
diff --git a/ios/chrome/test/app/web_view_interaction_test_util.mm b/ios/chrome/test/app/web_view_interaction_test_util.mm
index 8f872c5..1f5713f8 100644
--- a/ios/chrome/test/app/web_view_interaction_test_util.mm
+++ b/ios/chrome/test/app/web_view_interaction_test_util.mm
@@ -17,6 +17,11 @@
   return web::test::TapWebViewElementWithId(GetCurrentWebState(), element_id);
 }
 
+bool TapWebViewElementWithIdInIframe(const std::string& element_id) {
+  return web::test::TapWebViewElementWithIdInIframe(GetCurrentWebState(),
+                                                    element_id);
+}
+
 bool TapWebViewElementWithId(const std::string& element_id,
                              NSError* __autoreleasing* error) {
   return web::test::TapWebViewElementWithId(GetCurrentWebState(), element_id,
diff --git a/ios/testing/BUILD.gn b/ios/testing/BUILD.gn
index 0cf33c8..f936550 100644
--- a/ios/testing/BUILD.gn
+++ b/ios/testing/BUILD.gn
@@ -70,6 +70,8 @@
     "data/http_server_files/browsing_prevent_default_test_page.html",
     "data/http_server_files/chromium_logo.png",
     "data/http_server_files/chromium_logo_page.html",
+    "data/http_server_files/console.html",
+    "data/http_server_files/console_with_iframe.html",
     "data/http_server_files/destination.html",
     "data/http_server_files/fullscreen.html",
     "data/http_server_files/generic.pkpass",
diff --git a/ios/testing/data/http_server_files/console.html b/ios/testing/data/http_server_files/console.html
new file mode 100644
index 0000000..347baf5
--- /dev/null
+++ b/ios/testing/data/http_server_files/console.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<!-- Copyright 2019 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+
+This file contains buttons which send log messages to the JavaScript console. It
+is embedded as an iframe on console.html.
+-->
+
+<html>
+<body>
+
+<button id="debug" onclick="console.debug('This is an iframe debug message.')">Debug</button>
+<button id="error" onclick="console.error('This is an iframe error message.')">Error</button>
+<button id="info" onclick="console.info('This is an iframe informative message.')">Info</button>
+<button id="log" onclick="console.log('This iframe log is very round.')">Log</button>
+<button id="warn" onclick="console.warn('This is an iframe warning message.')">Warn</button>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/ios/testing/data/http_server_files/console_with_iframe.html b/ios/testing/data/http_server_files/console_with_iframe.html
new file mode 100644
index 0000000..b205519
--- /dev/null
+++ b/ios/testing/data/http_server_files/console_with_iframe.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<!-- Copyright 2019 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+
+This file contains buttons which send log messages to the JavaScript console.
+-->
+
+<html>
+<body>
+
+<button id="debug" onclick="console.debug('This is a debug message.')">Debug</button>
+<button id="error" onclick="console.error('This is an error message.')">Error</button>
+<button id="info" onclick="console.info('This is an informative message.')">Info</button>
+<button id="log" onclick="console.log('This log is very round.')">Log</button>
+<button id="warn" onclick="console.warn('This is a warning message.')">Warn</button>
+
+<iframe src="console.html"></iframe>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/ios/third_party/earl_grey2/BUILD.gn b/ios/third_party/earl_grey2/BUILD.gn
index 8633180..0d8f4051 100644
--- a/ios/third_party/earl_grey2/BUILD.gn
+++ b/ios/third_party/earl_grey2/BUILD.gn
@@ -177,7 +177,7 @@
 ios_framework_bundle("app_framework") {
   testonly = true
 
-  output_name = "EarlGreyAppFramework"
+  output_name = "AppFramework"
   info_plist = "Info.plist"
 
   sources = [
diff --git a/media/blink/video_frame_compositor.cc b/media/blink/video_frame_compositor.cc
index 97958d8..32ccd6f 100644
--- a/media/blink/video_frame_compositor.cc
+++ b/media/blink/video_frame_compositor.cc
@@ -75,7 +75,6 @@
     base::TimeTicks local_surface_id_allocation_time,
     VideoRotation rotation,
     bool force_submit,
-    bool is_opaque,
     blink::WebFrameSinkDestroyedCallback frame_sink_destroyed_callback) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
@@ -85,7 +84,6 @@
 
   submitter_->SetRotation(rotation);
   submitter_->SetForceSubmit(force_submit);
-  submitter_->SetIsOpaque(is_opaque);
   submitter_->EnableSubmission(id, local_surface_id_allocation_time,
                                std::move(frame_sink_destroyed_callback));
   client_ = submitter_.get();
@@ -294,11 +292,6 @@
   submitter_->SetForceSubmit(force_submit);
 }
 
-void VideoFrameCompositor::UpdateIsOpaque(bool is_opaque) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  submitter_->SetIsOpaque(is_opaque);
-}
-
 void VideoFrameCompositor::BackgroundRender() {
   DCHECK(task_runner_->BelongsToCurrentThread());
   const base::TimeTicks now = tick_clock_->NowTicks();
diff --git a/media/blink/video_frame_compositor.h b/media/blink/video_frame_compositor.h
index 2da9bc6..a13801e 100644
--- a/media/blink/video_frame_compositor.h
+++ b/media/blink/video_frame_compositor.h
@@ -83,7 +83,6 @@
       base::TimeTicks local_surface_id_allocation_time,
       VideoRotation rotation,
       bool force_submit,
-      bool is_opaque,
       blink::WebFrameSinkDestroyedCallback frame_sink_destroyed_callback);
 
   // cc::VideoFrameProvider implementation. These methods must be called on the
@@ -136,9 +135,6 @@
   // Notifies the |submitter_| that the frames must be submitted.
   void SetForceSubmit(bool force_submit);
 
-  // Updates the opacity information for frames given to |submitter_|.
-  void UpdateIsOpaque(bool is_opaque);
-
   void set_tick_clock_for_testing(const base::TickClock* tick_clock) {
     tick_clock_ = tick_clock;
   }
diff --git a/media/blink/video_frame_compositor_unittest.cc b/media/blink/video_frame_compositor_unittest.cc
index 823a038..3044409 100644
--- a/media/blink/video_frame_compositor_unittest.cc
+++ b/media/blink/video_frame_compositor_unittest.cc
@@ -37,7 +37,6 @@
   MOCK_CONST_METHOD0(IsDrivingFrameUpdates, bool(void));
   MOCK_METHOD1(Initialize, void(cc::VideoFrameProvider*));
   MOCK_METHOD1(SetRotation, void(media::VideoRotation));
-  MOCK_METHOD1(SetIsOpaque, void(bool));
   MOCK_METHOD1(SetIsSurfaceVisible, void(bool));
   MOCK_METHOD1(SetIsPageVisible, void(bool));
   MOCK_METHOD1(SetForceSubmit, void(bool));
@@ -79,10 +78,9 @@
                   SetRotation(Eq(media::VideoRotation::VIDEO_ROTATION_90)));
       EXPECT_CALL(*submitter_, SetForceSubmit(false));
       EXPECT_CALL(*submitter_, EnableSubmission(Eq(viz::SurfaceId()), _, _));
-      EXPECT_CALL(*submitter_, SetIsOpaque(true));
       compositor_->EnableSubmission(viz::SurfaceId(), base::TimeTicks(),
                                     media::VideoRotation::VIDEO_ROTATION_90,
-                                    false, true, base::BindRepeating([] {}));
+                                    false, base::BindRepeating([] {}));
     }
 
     compositor_->set_tick_clock_for_testing(&tick_clock_);
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index 0b87ac2b5..0dd569b 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -1875,7 +1875,7 @@
           base::Unretained(compositor_.get()), bridge_->GetSurfaceId(),
           bridge_->GetLocalSurfaceIdAllocationTime(),
           pipeline_metadata_.video_decoder_config.video_rotation(),
-          IsInPictureInPicture(), opaque_,
+          IsInPictureInPicture(),
           BindToCurrentLoop(base::BindRepeating(
               &WebMediaPlayerImpl::OnFrameSinkDestroyed, AsWeakPtr()))));
   bridge_->SetContentsOpaque(opaque_);
@@ -2151,16 +2151,10 @@
   DCHECK_NE(ready_state_, WebMediaPlayer::kReadyStateHaveNothing);
 
   opaque_ = opaque;
-  if (!surface_layer_for_video_enabled_) {
-    if (video_layer_)
-      video_layer_->SetContentsOpaque(opaque_);
-  } else if (bridge_->GetCcLayer()) {
+  if (!surface_layer_for_video_enabled_ && video_layer_)
+    video_layer_->SetContentsOpaque(opaque_);
+  else if (bridge_->GetCcLayer())
     bridge_->SetContentsOpaque(opaque_);
-    vfc_task_runner_->PostTask(
-        FROM_HERE,
-        base::BindOnce(&VideoFrameCompositor::UpdateIsOpaque,
-                       base::Unretained(compositor_.get()), opaque_));
-  }
 }
 
 void WebMediaPlayerImpl::OnAudioConfigChange(const AudioDecoderConfig& config) {
diff --git a/media/blink/webmediaplayer_impl_unittest.cc b/media/blink/webmediaplayer_impl_unittest.cc
index 5ca0364..ec47046 100644
--- a/media/blink/webmediaplayer_impl_unittest.cc
+++ b/media/blink/webmediaplayer_impl_unittest.cc
@@ -340,12 +340,11 @@
   void SetOnNewProcessedFrameCallback(OnNewProcessedFrameCB cb) override {}
   MOCK_METHOD1(SetIsPageVisible, void(bool));
   MOCK_METHOD0(GetCurrentFrameAndUpdateIfStale, scoped_refptr<VideoFrame>());
-  MOCK_METHOD6(EnableSubmission,
+  MOCK_METHOD5(EnableSubmission,
                void(const viz::SurfaceId&,
                     base::TimeTicks,
                     media::VideoRotation,
                     bool,
-                    bool,
                     blink::WebFrameSinkDestroyedCallback));
 };
 
@@ -907,7 +906,7 @@
         .WillOnce(ReturnRef(surface_id_));
     EXPECT_CALL(*surface_layer_bridge_ptr_, GetLocalSurfaceIdAllocationTime())
         .WillOnce(Return(base::TimeTicks()));
-    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, false, _));
+    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, _));
     EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
   }
 
@@ -939,7 +938,7 @@
         .WillOnce(ReturnRef(surface_id_));
     EXPECT_CALL(*surface_layer_bridge_ptr_, GetLocalSurfaceIdAllocationTime())
         .WillOnce(Return(base::TimeTicks()));
-    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, false, _));
+    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, _));
     EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
   }
 
@@ -1369,7 +1368,7 @@
   if (base::FeatureList::IsEnabled(media::kUseSurfaceLayerForVideo)) {
     EXPECT_CALL(*surface_layer_bridge_ptr_, CreateSurfaceLayer()).Times(0);
     EXPECT_CALL(*surface_layer_bridge_ptr_, GetSurfaceId()).Times(0);
-    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, _, _)).Times(0);
+    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, _)).Times(0);
   }
 
   // Nothing should happen.  In particular, no assertions should fail.
@@ -1422,7 +1421,7 @@
         .WillOnce(ReturnRef(surface_id_));
     EXPECT_CALL(*surface_layer_bridge_ptr_, GetLocalSurfaceIdAllocationTime())
         .WillOnce(Return(base::TimeTicks()));
-    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, false, _));
+    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, _));
     EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
   } else {
     EXPECT_CALL(client_, SetCcLayer(NotNull()));
@@ -1451,7 +1450,7 @@
         .WillOnce(ReturnRef(surface_id_));
     EXPECT_CALL(*surface_layer_bridge_ptr_, GetLocalSurfaceIdAllocationTime())
         .WillOnce(Return(base::TimeTicks()));
-    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, false, _));
+    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, _));
     EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
   } else {
     EXPECT_CALL(client_, SetCcLayer(NotNull()));
@@ -1481,7 +1480,7 @@
         .WillOnce(ReturnRef(surface_id_));
     EXPECT_CALL(*surface_layer_bridge_ptr_, GetLocalSurfaceIdAllocationTime())
         .WillOnce(Return(base::TimeTicks()));
-    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, false, _));
+    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, _));
     EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
   } else {
     EXPECT_CALL(client_, SetCcLayer(NotNull()));
@@ -1559,7 +1558,7 @@
         .WillOnce(ReturnRef(surface_id_));
     EXPECT_CALL(*surface_layer_bridge_ptr_, GetLocalSurfaceIdAllocationTime())
         .WillOnce(Return(base::TimeTicks()));
-    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, false, _));
+    EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, _));
     EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
   } else {
     EXPECT_CALL(client_, SetCcLayer(NotNull()));
@@ -1600,7 +1599,7 @@
   EXPECT_CALL(*surface_layer_bridge_ptr_, GetLocalSurfaceIdAllocationTime())
       .WillOnce(Return(base::TimeTicks()));
   EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
-  EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, false, _));
+  EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, _));
 
   // We only call the callback to create the bridge in OnMetadata, so we need
   // to call it.
@@ -1645,7 +1644,7 @@
       .WillRepeatedly(ReturnRef(surface_id_));
   EXPECT_CALL(*surface_layer_bridge_ptr_, GetLocalSurfaceIdAllocationTime())
       .WillRepeatedly(Return(base::TimeTicks()));
-  EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, false, _));
+  EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, _));
   EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
 
   PipelineMetadata metadata;
@@ -1690,7 +1689,7 @@
       .WillRepeatedly(ReturnRef(surface_id_));
   EXPECT_CALL(*surface_layer_bridge_ptr_, GetLocalSurfaceIdAllocationTime())
       .WillRepeatedly(Return(base::TimeTicks()));
-  EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, false, _));
+  EXPECT_CALL(*compositor_, EnableSubmission(_, _, _, _, _));
   EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
 
   PipelineMetadata metadata;
diff --git a/media/gpu/fake_jpeg_decode_accelerator.cc b/media/gpu/fake_jpeg_decode_accelerator.cc
index 6f6b3537..b12b52f4 100644
--- a/media/gpu/fake_jpeg_decode_accelerator.cc
+++ b/media/gpu/fake_jpeg_decode_accelerator.cc
@@ -45,8 +45,6 @@
       new WritableUnalignedMapping(bitstream_buffer.handle(),
                                    bitstream_buffer.size(),
                                    bitstream_buffer.offset()));
-  // The handle is no longer needed.
-  bitstream_buffer.handle().Close();
   if (!src_shm->IsValid()) {
     DLOG(ERROR) << "Unable to map shared memory in FakeJpegDecodeAccelerator";
     NotifyError(bitstream_buffer.id(), JpegDecodeAccelerator::UNREADABLE_INPUT);
diff --git a/media/gpu/h264_decoder.cc b/media/gpu/h264_decoder.cc
index dc2c76cf..0fa01797 100644
--- a/media/gpu/h264_decoder.cc
+++ b/media/gpu/h264_decoder.cc
@@ -26,12 +26,6 @@
   return H264Decoder::H264Accelerator::Status::kNotSupported;
 }
 
-H264Decoder::H264Accelerator::Status
-H264Decoder::H264Accelerator::ParseSliceHeader(const H264NALU& slice_nalu,
-                                               H264SliceHeader* slice_header) {
-  return H264Decoder::H264Accelerator::Status::kNotSupported;
-}
-
 H264Decoder::H264Decoder(std::unique_ptr<H264Accelerator> accelerator,
                          const VideoColorSpace& container_color_space)
     : state_(kNeedStreamMetadata),
@@ -1319,32 +1313,12 @@
         // the call that failed previously. If it succeeds (it may not if no
         // additional key has been provided, for example), then the remaining
         // steps will be executed.
-
         if (!curr_slice_hdr_) {
           curr_slice_hdr_.reset(new H264SliceHeader());
-          // If the accelerator handles the slice header, let it handle it.
-          // If not, use the parser.
-          H264Accelerator::Status result = accelerator_->ParseSliceHeader(
-              *curr_nalu_, curr_slice_hdr_.get());
-          switch (result) {
-            case H264Accelerator::Status::kOk:
-              break;
-            case H264Accelerator::Status::kTryAgain:
-              DVLOG(1) << "ParseSliceHeader() needs to try again";
-              // reset |curr_slice_hdr_| so ParseSliceHeader() is tried again.
-              curr_slice_hdr_.reset();
-              return H264Decoder::kTryAgain;
-            case H264Accelerator::Status::kNotSupported:
-              // Let the parser try to handle it.
-              par_res =
-                  parser_.ParseSliceHeader(*curr_nalu_, curr_slice_hdr_.get());
-              if (par_res == H264Parser::kOk)
-                break;
-              FALLTHROUGH;
-            case H264Accelerator::Status::kFail:
-              SET_ERROR_AND_RETURN();
-          }
-
+          par_res =
+              parser_.ParseSliceHeader(*curr_nalu_, curr_slice_hdr_.get());
+          if (par_res != H264Parser::kOk)
+            SET_ERROR_AND_RETURN();
           state_ = kTryPreprocessCurrentSlice;
         }
 
diff --git a/media/gpu/h264_decoder.h b/media/gpu/h264_decoder.h
index e77ede18..66bb089 100644
--- a/media/gpu/h264_decoder.h
+++ b/media/gpu/h264_decoder.h
@@ -54,8 +54,8 @@
       // operation later, once the data has been provided.
       kTryAgain,
 
-      // Operation is not supported. Used by SetStream() and ParseSliceHeader()
-      // to indicate that the Accelerator can not handle this operation.
+      // Operation is not supported. Used by SetStream() to indicate that the
+      // Accelerator can not handle this operation.
       kNotSupported,
     };
 
@@ -142,14 +142,6 @@
     virtual Status SetStream(base::span<const uint8_t> stream,
                              const DecryptConfig* decrypt_config);
 
-    // Parse a slice header, returning it in |*slice_header|. |slice_nalu| must
-    // be a slice NALU. On success, this populates |*slice_header|. If the
-    // Accelerator doesn't handle this slice header, then it should return
-    // kNotSupported. This method has a default implementation that returns
-    // kNotSupported.
-    virtual Status ParseSliceHeader(const H264NALU& slice_nalu,
-                                    H264SliceHeader* slice_header);
-
    private:
     DISALLOW_COPY_AND_ASSIGN(H264Accelerator);
   };
diff --git a/media/gpu/h264_decoder_unittest.cc b/media/gpu/h264_decoder_unittest.cc
index bd0ca1e..f48003f 100644
--- a/media/gpu/h264_decoder_unittest.cc
+++ b/media/gpu/h264_decoder_unittest.cc
@@ -129,9 +129,6 @@
   MOCK_METHOD2(SetStream,
                Status(base::span<const uint8_t> stream,
                       const DecryptConfig* decrypt_config));
-  MOCK_METHOD2(ParseSliceHeader,
-               Status(const H264NALU& slice_nalu,
-                      H264SliceHeader* slice_header));
 
   void Reset() override {}
 };
@@ -182,9 +179,6 @@
   ON_CALL(*accelerator_, SetStream(_, _))
       .WillByDefault(
           Return(H264Decoder::H264Accelerator::Status::kNotSupported));
-  ON_CALL(*accelerator_, ParseSliceHeader(_, _))
-      .WillByDefault(
-          Return(H264Decoder::H264Accelerator::Status::kNotSupported));
 }
 
 void H264DecoderTest::SetInputFrameFiles(
@@ -592,7 +586,6 @@
   {
     InSequence sequence;
 
-    EXPECT_CALL(*accelerator_, ParseSliceHeader(_, _));
     EXPECT_CALL(*accelerator_, CreateH264Picture());
     EXPECT_CALL(*accelerator_, SubmitFrameMetadata(_, _, _, _, _, _, _));
     EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _));
@@ -607,36 +600,5 @@
   ASSERT_TRUE(decoder_->Flush());
 }
 
-TEST_F(H264DecoderTest, ParseSliceHeaderRetry) {
-  SetInputFrameFiles({kBaselineFrame0});
-  ASSERT_EQ(AcceleratedVideoDecoder::kAllocateNewSurfaces, Decode());
-  EXPECT_EQ(gfx::Size(320, 192), decoder_->GetPicSize());
-  EXPECT_LE(9u, decoder_->GetRequiredNumOfPictures());
-
-  EXPECT_CALL(*accelerator_, ParseSliceHeader(_, _))
-      .WillOnce(Return(H264Decoder::H264Accelerator::Status::kTryAgain));
-  ASSERT_EQ(AcceleratedVideoDecoder::kTryAgain, Decode());
-
-  H264SliceHeader slice_header = {};
-  {
-    InSequence sequence;
-
-    EXPECT_CALL(*accelerator_, ParseSliceHeader(_, _))
-        .WillOnce(ComputeSliceHeader(&slice_header));
-    EXPECT_CALL(*accelerator_, CreateH264Picture());
-    EXPECT_CALL(*accelerator_, SubmitFrameMetadata(_, _, _, _, _, _, _));
-    EXPECT_CALL(*accelerator_, SubmitSlice(_, SliceHeaderMatches(&slice_header),
-                                           _, _, _, _, _, _));
-  }
-  ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode());
-
-  {
-    InSequence sequence;
-    EXPECT_CALL(*accelerator_, SubmitDecode(WithPoc(0)));
-    EXPECT_CALL(*accelerator_, OutputPicture(WithPoc(0)));
-  }
-  ASSERT_TRUE(decoder_->Flush());
-}
-
 }  // namespace
 }  // namespace media
diff --git a/media/gpu/jpeg_encode_accelerator_unittest.cc b/media/gpu/jpeg_encode_accelerator_unittest.cc
index 85d864d2..920e5c2e 100644
--- a/media/gpu/jpeg_encode_accelerator_unittest.cc
+++ b/media/gpu/jpeg_encode_accelerator_unittest.cc
@@ -19,6 +19,7 @@
 #include "base/strings/string_split.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/scoped_task_environment.h"
+#include "base/test/test_timeouts.h"
 #include "base/threading/thread.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
@@ -131,6 +132,11 @@
 };
 
 void JpegEncodeAcceleratorTestEnvironment::SetUp() {
+  // Since base::test::ScopedTaskEnvironment will call
+  // TestTimeouts::action_max_timeout(), TestTimeouts::Initialize() needs to be
+  // called in advance.
+  TestTimeouts::Initialize();
+
   if (!log_path_.empty()) {
     log_file_.reset(new base::File(
         log_path_, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE));
diff --git a/media/gpu/video_encode_accelerator_unittest.cc b/media/gpu/video_encode_accelerator_unittest.cc
index 22b12ee7..5ef5169 100644
--- a/media/gpu/video_encode_accelerator_unittest.cc
+++ b/media/gpu/video_encode_accelerator_unittest.cc
@@ -2636,12 +2636,17 @@
  public:
   VEATestSuite(int argc, char** argv) : base::TestSuite(argc, argv) {}
 
-  int Run() {
+ private:
+  void Initialize() override {
+    base::TestSuite::Initialize();
+
 #if defined(OS_CHROMEOS)
-    base::test::ScopedTaskEnvironment scoped_task_environment(
-        base::test::ScopedTaskEnvironment::MainThreadType::UI);
+    scoped_task_environment_ =
+        std::make_unique<base::test::ScopedTaskEnvironment>(
+            base::test::ScopedTaskEnvironment::MainThreadType::UI);
 #else
-    base::test::ScopedTaskEnvironment scoped_task_environment;
+    scoped_task_environment_ =
+        std::make_unique<base::test::ScopedTaskEnvironment>();
 #endif
     media::g_env =
         reinterpret_cast<media::VideoEncodeAcceleratorTestEnvironment*>(
@@ -2657,8 +2662,15 @@
 #elif defined(OS_WIN)
     media::MediaFoundationVideoEncodeAccelerator::PreSandboxInitialization();
 #endif
-    return base::TestSuite::Run();
   }
+
+  void Shutdown() override {
+    scoped_task_environment_.reset();
+    base::TestSuite::Shutdown();
+  }
+
+ private:
+  std::unique_ptr<base::test::ScopedTaskEnvironment> scoped_task_environment_;
 };
 
 }  // namespace
diff --git a/media/renderers/paint_canvas_video_renderer.cc b/media/renderers/paint_canvas_video_renderer.cc
index 93de42e1..8bdeb82 100644
--- a/media/renderers/paint_canvas_video_renderer.cc
+++ b/media/renderers/paint_canvas_video_renderer.cc
@@ -342,6 +342,12 @@
   bool QueryYUVA8(SkYUVASizeInfo* sizeInfo,
                   SkYUVAIndex indices[SkYUVAIndex::kIndexCount],
                   SkYUVColorSpace* color_space) const override {
+    // Temporarily disabling this path to avoid creating YUV ImageData in
+    // GpuImageDecodeCache.
+    // TODO(crbug.com/921636): Restore the code below once YUV rendering support
+    // is added for VideoImageGenerator.
+    return false;
+#if 0
     if (!media::IsYuvPlanar(frame_->format()) ||
         // TODO(rileya): Skia currently doesn't support YUVA conversion. Remove
         // this case once it does. As-is we will fall back on the pure-software
@@ -376,6 +382,7 @@
     indices[SkYUVAIndex::kA_Index] = {-1, SkColorChannel::kR};
 
     return true;
+#endif
   }
 
   bool GetYUVA8Planes(const SkYUVASizeInfo& sizeInfo,
diff --git a/net/dns/BUILD.gn b/net/dns/BUILD.gn
index a7f5e99..741a5b56 100644
--- a/net/dns/BUILD.gn
+++ b/net/dns/BUILD.gn
@@ -54,6 +54,8 @@
       "host_cache.cc",
       "host_resolver.cc",
       "host_resolver_impl.cc",
+      "host_resolver_mdns_listener_impl.cc",
+      "host_resolver_mdns_listener_impl.h",
       "host_resolver_mdns_task.cc",
       "host_resolver_mdns_task.h",
       "host_resolver_proc.cc",
diff --git a/net/dns/host_cache.cc b/net/dns/host_cache.cc
index 1c5fc00..6fe37c7 100644
--- a/net/dns/host_cache.cc
+++ b/net/dns/host_cache.cc
@@ -13,6 +13,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/time/default_tick_clock.h"
 #include "base/trace_event/trace_event.h"
+#include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
 #include "net/base/trace_constants.h"
 #include "net/dns/host_resolver.h"
@@ -192,6 +193,40 @@
                              base::Unretained(this));
 }
 
+HostCache::Entry HostCache::Entry::CopyWithDefaultPort(uint16_t port) const {
+  Entry copy(*this);
+
+  if (addresses() &&
+      std::any_of(addresses().value().begin(), addresses().value().end(),
+                  [](const IPEndPoint& e) { return e.port() == 0; })) {
+    AddressList addresses_with_port;
+    addresses_with_port.set_canonical_name(
+        addresses().value().canonical_name());
+    for (const IPEndPoint& endpoint : addresses().value()) {
+      if (endpoint.port() == 0)
+        addresses_with_port.push_back(IPEndPoint(endpoint.address(), port));
+      else
+        addresses_with_port.push_back(endpoint);
+    }
+    copy.set_addresses(addresses_with_port);
+  }
+
+  if (hostnames() &&
+      std::any_of(hostnames().value().begin(), hostnames().value().end(),
+                  [](const HostPortPair& h) { return h.port() == 0; })) {
+    std::vector<HostPortPair> hostnames_with_port;
+    for (const HostPortPair& hostname : hostnames().value()) {
+      if (hostname.port() == 0)
+        hostnames_with_port.push_back(HostPortPair(hostname.host(), port));
+      else
+        hostnames_with_port.push_back(hostname);
+    }
+    copy.set_hostnames(std::move(hostnames_with_port));
+  }
+
+  return copy;
+}
+
 HostCache::Entry& HostCache::Entry::operator=(const Entry& entry) = default;
 
 HostCache::Entry& HostCache::Entry::operator=(Entry&& entry) = default;
diff --git a/net/dns/host_cache.h b/net/dns/host_cache.h
index f7400b2..926dcf09 100644
--- a/net/dns/host_cache.h
+++ b/net/dns/host_cache.h
@@ -167,6 +167,10 @@
     // |this| is.
     NetLogParametersCallback CreateNetLogCallback() const;
 
+    // Creates a copy of |this| with the port of all address and hostname values
+    // set to |port| if the current port is 0. Preserves any non-zero ports.
+    HostCache::Entry CopyWithDefaultPort(uint16_t port) const;
+
    private:
     friend class HostCache;
 
diff --git a/net/dns/host_resolver.cc b/net/dns/host_resolver.cc
index 9b8daa3c..60e28e4 100644
--- a/net/dns/host_resolver.cc
+++ b/net/dns/host_resolver.cc
@@ -114,6 +114,15 @@
 
 HostResolver::~HostResolver() = default;
 
+std::unique_ptr<HostResolver::MdnsListener> HostResolver::CreateMdnsListener(
+    const HostPortPair& host,
+    DnsQueryType query_type) {
+  // Should be overridden in any HostResolver implementation where this method
+  // may be called.
+  NOTREACHED();
+  return nullptr;
+}
+
 void HostResolver::SetDnsClientEnabled(bool enabled) {}
 
 HostCache* HostResolver::GetHostCache() {
diff --git a/net/dns/host_resolver.h b/net/dns/host_resolver.h
index a985f68..b08ebef 100644
--- a/net/dns/host_resolver.h
+++ b/net/dns/host_resolver.h
@@ -242,6 +242,45 @@
     bool is_speculative = false;
   };
 
+  // Handler for an ongoing MDNS listening operation. Created by
+  // HostResolver::CreateMdnsListener().
+  class MdnsListener {
+   public:
+    // Delegate type for result update notifications from MdnsListener. All
+    // methods have a |result_type| field to allow a single delegate to be
+    // passed to multiple MdnsListeners and be used to listen for updates for
+    // multiple types for the same host.
+    class Delegate {
+     public:
+      enum class UpdateType { ADDED, CHANGED, REMOVED };
+
+      virtual ~Delegate() {}
+
+      virtual void OnAddressResult(UpdateType update_type,
+                                   DnsQueryType result_type,
+                                   IPEndPoint address) = 0;
+      virtual void OnTextResult(UpdateType update_type,
+                                DnsQueryType result_type,
+                                std::vector<std::string> text_records) = 0;
+      virtual void OnHostnameResult(UpdateType update_type,
+                                    DnsQueryType result_type,
+                                    HostPortPair host) = 0;
+
+      // For results which may be valid MDNS but are not handled/parsed by
+      // HostResolver, e.g. pointers to the root domain.
+      virtual void OnUnhandledResult(UpdateType update_type,
+                                     DnsQueryType result_type) = 0;
+    };
+
+    // Destruction cancels the listening operation.
+    virtual ~MdnsListener() {}
+
+    // Begins the listening operation, invoking |delegate| whenever results are
+    // updated. |delegate| will no longer be called once the listening operation
+    // is cancelled (via destruction of |this|).
+    virtual int Start(Delegate* delegate) = 0;
+  };
+
   // Set Options.max_concurrent_resolves to this to select a default level
   // of concurrency.
   static const size_t kDefaultParallelism = 0;
@@ -320,6 +359,11 @@
                                     HostCache::EntryStaleness* stale_info,
                                     const NetLogWithSource& source_net_log) = 0;
 
+  // Create a listener to watch for updates to an MDNS result.
+  virtual std::unique_ptr<MdnsListener> CreateMdnsListener(
+      const HostPortPair& host,
+      DnsQueryType query_type);
+
   // Enable or disable the built-in asynchronous DnsClient.
   virtual void SetDnsClientEnabled(bool enabled);
 
diff --git a/net/dns/host_resolver_impl.cc b/net/dns/host_resolver_impl.cc
index fc570e71..9503444 100644
--- a/net/dns/host_resolver_impl.cc
+++ b/net/dns/host_resolver_impl.cc
@@ -23,7 +23,6 @@
 #include <memory>
 #include <unordered_set>
 #include <utility>
-#include <vector>
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
@@ -33,6 +32,7 @@
 #include "base/containers/linked_list.h"
 #include "base/debug/debugger.h"
 #include "base/debug/stack_trace.h"
+#include "base/logging.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/field_trial.h"
@@ -67,6 +67,7 @@
 #include "net/dns/dns_response.h"
 #include "net/dns/dns_transaction.h"
 #include "net/dns/dns_util.h"
+#include "net/dns/host_resolver_mdns_listener_impl.h"
 #include "net/dns/host_resolver_mdns_task.h"
 #include "net/dns/host_resolver_proc.h"
 #include "net/dns/mdns_client.h"
@@ -197,42 +198,6 @@
 
 //-----------------------------------------------------------------------------
 
-// Creates a copy of |results| with the port of all address and hostname values
-// set to |port| if the current port is 0. Preserves any non-zero ports.
-HostCache::Entry SetPortOnResults(HostCache::Entry results, uint16_t port) {
-  if (results.addresses() &&
-      std::any_of(results.addresses().value().begin(),
-                  results.addresses().value().end(),
-                  [](const IPEndPoint& e) { return e.port() == 0; })) {
-    AddressList addresses_with_port;
-    addresses_with_port.set_canonical_name(
-        results.addresses().value().canonical_name());
-    for (const IPEndPoint& endpoint : results.addresses().value()) {
-      if (endpoint.port() == 0)
-        addresses_with_port.push_back(IPEndPoint(endpoint.address(), port));
-      else
-        addresses_with_port.push_back(endpoint);
-    }
-    results.set_addresses(addresses_with_port);
-  }
-
-  if (results.hostnames() &&
-      std::any_of(results.hostnames().value().begin(),
-                  results.hostnames().value().end(),
-                  [](const HostPortPair& h) { return h.port() == 0; })) {
-    std::vector<HostPortPair> hostnames_with_port;
-    for (const HostPortPair& hostname : results.hostnames().value()) {
-      if (hostname.port() == 0)
-        hostnames_with_port.push_back(HostPortPair(hostname.host(), port));
-      else
-        hostnames_with_port.push_back(hostname);
-    }
-    results.set_hostnames(std::move(hostnames_with_port));
-  }
-
-  return results;
-}
-
 // Returns true if |addresses| contains only IPv4 loopback addresses.
 bool IsAllIPv4Loopback(const AddressList& addresses) {
   for (unsigned i = 0; i < addresses.size(); ++i) {
@@ -2125,7 +2090,8 @@
             tick_clock_->NowTicks() - req->request_time());
       }
       if (results.error() == OK && !req->parameters().is_speculative) {
-        req->set_results(SetPortOnResults(results, req->request_host().port()));
+        req->set_results(
+            results.CopyWithDefaultPort(req->request_host().port()));
       }
       req->OnJobCompleted(this, results.error());
 
@@ -2408,6 +2374,22 @@
   return results.error();
 }
 
+std::unique_ptr<HostResolver::MdnsListener>
+HostResolverImpl::CreateMdnsListener(const HostPortPair& host,
+                                     DnsQueryType query_type) {
+  DCHECK_NE(DnsQueryType::UNSPECIFIED, query_type);
+
+  auto listener =
+      std::make_unique<HostResolverMdnsListenerImpl>(host, query_type);
+
+  MDnsClient* client = GetOrCreateMdnsClient();
+  std::unique_ptr<net::MDnsListener> inner_listener = client->CreateListener(
+      DnsQueryTypeToQtype(query_type), host.host(), listener.get());
+
+  listener->set_inner_listener(std::move(inner_listener));
+  return listener;
+}
+
 void HostResolverImpl::SetDnsClientEnabled(bool enabled) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 #if defined(ENABLE_BUILT_IN_DNS)
@@ -2557,7 +2539,7 @@
       nullptr /* stale_info */, request->source_net_log(), &key);
   if (results.error() == OK && !request->parameters().is_speculative) {
     request->set_results(
-        SetPortOnResults(results, request->request_host().port()));
+        results.CopyWithDefaultPort(request->request_host().port()));
   }
   if (results.error() != ERR_DNS_CACHE_MISS) {
     LogFinishRequest(request->source_net_log(), results.error());
diff --git a/net/dns/host_resolver_impl.h b/net/dns/host_resolver_impl.h
index 1d4579f9..e2d0c94 100644
--- a/net/dns/host_resolver_impl.h
+++ b/net/dns/host_resolver_impl.h
@@ -36,6 +36,7 @@
 
 class AddressList;
 class DnsClient;
+class HostPortPair;
 class IPAddress;
 class MDnsClient;
 class MDnsSocketFactory;
@@ -157,6 +158,9 @@
                             AddressList* addresses,
                             HostCache::EntryStaleness* stale_info,
                             const NetLogWithSource& source_net_log) override;
+  std::unique_ptr<MdnsListener> CreateMdnsListener(
+      const HostPortPair& host,
+      DnsQueryType query_type) override;
   void SetDnsClientEnabled(bool enabled) override;
 
   HostCache* GetHostCache() override;
diff --git a/net/dns/host_resolver_impl_unittest.cc b/net/dns/host_resolver_impl_unittest.cc
index 4abc892..fdde5e38 100644
--- a/net/dns/host_resolver_impl_unittest.cc
+++ b/net/dns/host_resolver_impl_unittest.cc
@@ -24,14 +24,18 @@
 #include "base/synchronization/condition_variable.h"
 #include "base/synchronization/lock.h"
 #include "base/test/bind_test_util.h"
+#include "base/test/simple_test_clock.h"
 #include "base/test/test_mock_time_task_runner.h"
 #include "base/test/test_timeouts.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
+#include "base/timer/mock_timer.h"
 #include "base/values.h"
 #include "net/base/address_list.h"
+#include "net/base/host_port_pair.h"
 #include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
 #include "net/base/mock_network_change_notifier.h"
 #include "net/base/net_errors.h"
 #include "net/dns/dns_client.h"
@@ -50,6 +54,10 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(ENABLE_MDNS)
+#include "net/dns/mdns_client_impl.h"
+#endif  // BUILDFLAG(ENABLE_MDNS)
+
 using net::test::IsError;
 using net::test::IsOk;
 using ::testing::_;
@@ -3040,6 +3048,46 @@
     0x01, 0x02, 0x03, 0x04,  // 1.2.3.4
 };
 
+const uint8_t kMdnsResponseA2[] = {
+    // Header
+    0x00, 0x00,  // ID is zeroed out
+    0x81, 0x80,  // Standard query response, RA, no error
+    0x00, 0x00,  // No questions (for simplicity)
+    0x00, 0x01,  // 1 RR (answers)
+    0x00, 0x00,  // 0 authority RRs
+    0x00, 0x00,  // 0 additional RRs
+
+    // "myhello.local."
+    0x07, 'm', 'y', 'h', 'e', 'l', 'l', 'o', 0x05, 'l', 'o', 'c', 'a', 'l',
+    0x00,
+
+    0x00, 0x01,              // TYPE is A.
+    0x00, 0x01,              // CLASS is IN.
+    0x00, 0x00, 0x00, 0x10,  // TTL is 16 (seconds)
+    0x00, 0x04,              // RDLENGTH is 4 bytes.
+    0x05, 0x06, 0x07, 0x08,  // 5.6.7.8
+};
+
+const uint8_t kMdnsResponseA2Goodbye[] = {
+    // Header
+    0x00, 0x00,  // ID is zeroed out
+    0x81, 0x80,  // Standard query response, RA, no error
+    0x00, 0x00,  // No questions (for simplicity)
+    0x00, 0x01,  // 1 RR (answers)
+    0x00, 0x00,  // 0 authority RRs
+    0x00, 0x00,  // 0 additional RRs
+
+    // "myhello.local."
+    0x07, 'm', 'y', 'h', 'e', 'l', 'l', 'o', 0x05, 'l', 'o', 'c', 'a', 'l',
+    0x00,
+
+    0x00, 0x01,              // TYPE is A.
+    0x00, 0x01,              // CLASS is IN.
+    0x00, 0x00, 0x00, 0x00,  // TTL is 0 (signaling "goodbye" removal of result)
+    0x00, 0x04,              // RDLENGTH is 4 bytes.
+    0x05, 0x06, 0x07, 0x08,  // 5.6.7.8
+};
+
 const uint8_t kMdnsResponseAAAA[] = {
     // Header
     0x00, 0x00,  // ID is zeroed out
@@ -3134,6 +3182,27 @@
     // "foo.com."
     0x03, 'f', 'o', 'o', 0x03, 'c', 'o', 'm', 0x00};
 
+const uint8_t kMdnsResponsePtrRoot[] = {
+    // Header
+    0x00, 0x00,  // ID is zeroed out
+    0x81, 0x80,  // Standard query response, RA, no error
+    0x00, 0x00,  // No questions (for simplicity)
+    0x00, 0x01,  // 1 RR (answers)
+    0x00, 0x00,  // 0 authority RRs
+    0x00, 0x00,  // 0 additional RRs
+
+    // "myhello.local."
+    0x07, 'm', 'y', 'h', 'e', 'l', 'l', 'o', 0x05, 'l', 'o', 'c', 'a', 'l',
+    0x00,
+
+    0x00, 0x0c,              // TYPE is PTR.
+    0x00, 0x01,              // CLASS is IN.
+    0x00, 0x00, 0x00, 0x13,  // TTL is 19 (seconds)
+    0x00, 0x01,              // RDLENGTH is 1 byte.
+
+    // "." (the root domain)
+    0x00};
+
 const uint8_t kMdnsResponseSrv[] = {
     // Header
     0x00, 0x00,  // ID is zeroed out
@@ -3474,6 +3543,299 @@
   EXPECT_THAT(response.result_error(), IsError(ERR_FAILED));
   EXPECT_FALSE(response.request()->GetAddressResults());
 }
+
+// Implementation of HostResolver::MdnsListenerDelegate that records all
+// received results in maps.
+class TestMdnsListenerDelegate : public HostResolver::MdnsListener::Delegate {
+ public:
+  using UpdateKey =
+      std::pair<HostResolver::MdnsListener::Delegate::UpdateType, DnsQueryType>;
+
+  void OnAddressResult(
+      HostResolver::MdnsListener::Delegate::UpdateType update_type,
+      DnsQueryType result_type,
+      IPEndPoint address) override {
+    address_results_.insert({{update_type, result_type}, std::move(address)});
+  }
+
+  void OnTextResult(
+      HostResolver::MdnsListener::Delegate::UpdateType update_type,
+      DnsQueryType result_type,
+      std::vector<std::string> text_records) override {
+    for (auto& text_record : text_records) {
+      text_results_.insert(
+          {{update_type, result_type}, std::move(text_record)});
+    }
+  }
+
+  void OnHostnameResult(
+      HostResolver::MdnsListener::Delegate::UpdateType update_type,
+      DnsQueryType result_type,
+      HostPortPair host) override {
+    hostname_results_.insert({{update_type, result_type}, std::move(host)});
+  }
+
+  void OnUnhandledResult(
+      HostResolver::MdnsListener::Delegate::UpdateType update_type,
+      DnsQueryType result_type) override {
+    unhandled_results_.insert({update_type, result_type});
+  }
+
+  const std::multimap<UpdateKey, IPEndPoint>& address_results() {
+    return address_results_;
+  }
+
+  const std::multimap<UpdateKey, std::string>& text_results() {
+    return text_results_;
+  }
+
+  const std::multimap<UpdateKey, HostPortPair>& hostname_results() {
+    return hostname_results_;
+  }
+
+  const std::multiset<UpdateKey>& unhandled_results() {
+    return unhandled_results_;
+  }
+
+  template <typename T>
+  static std::pair<UpdateKey, T> CreateExpectedResult(
+      HostResolver::MdnsListener::Delegate::UpdateType update_type,
+      DnsQueryType query_type,
+      T result) {
+    return std::make_pair(std::make_pair(update_type, query_type), result);
+  }
+
+ private:
+  std::multimap<UpdateKey, IPEndPoint> address_results_;
+  std::multimap<UpdateKey, std::string> text_results_;
+  std::multimap<UpdateKey, HostPortPair> hostname_results_;
+  std::multiset<UpdateKey> unhandled_results_;
+};
+
+TEST_F(HostResolverImplTest, MdnsListener) {
+  auto socket_factory = std::make_unique<MockMDnsSocketFactory>();
+  base::SimpleTestClock clock;
+  clock.SetNow(base::Time::Now());
+  auto cache_cleanup_timer = std::make_unique<base::MockOneShotTimer>();
+  auto* cache_cleanup_timer_ptr = cache_cleanup_timer.get();
+  auto mdns_client =
+      std::make_unique<MDnsClientImpl>(&clock, std::move(cache_cleanup_timer));
+  mdns_client->StartListening(socket_factory.get());
+  resolver_->SetMdnsClientForTesting(std::move(mdns_client));
+
+  std::unique_ptr<HostResolver::MdnsListener> listener =
+      resolver_->CreateMdnsListener(HostPortPair("myhello.local", 80),
+                                    DnsQueryType::A);
+
+  TestMdnsListenerDelegate delegate;
+  ASSERT_THAT(listener->Start(&delegate), IsOk());
+  ASSERT_THAT(delegate.address_results(), testing::IsEmpty());
+
+  socket_factory->SimulateReceive(kMdnsResponseA, sizeof(kMdnsResponseA));
+  socket_factory->SimulateReceive(kMdnsResponseA2, sizeof(kMdnsResponseA2));
+  socket_factory->SimulateReceive(kMdnsResponseA2Goodbye,
+                                  sizeof(kMdnsResponseA2Goodbye));
+
+  // Per RFC6762 section 10.1, removals take effect 1 second after receiving the
+  // goodbye message.
+  clock.Advance(base::TimeDelta::FromSeconds(1));
+  cache_cleanup_timer_ptr->Fire();
+
+  // Expect 1 record adding "1.2.3.4", another changing to "5.6.7.8", and a
+  // final removing "5.6.7.8".
+  EXPECT_THAT(delegate.address_results(),
+              testing::ElementsAre(
+                  TestMdnsListenerDelegate::CreateExpectedResult(
+                      HostResolver::MdnsListener::Delegate::UpdateType::ADDED,
+                      DnsQueryType::A, CreateExpected("1.2.3.4", 80)),
+                  TestMdnsListenerDelegate::CreateExpectedResult(
+                      HostResolver::MdnsListener::Delegate::UpdateType::CHANGED,
+                      DnsQueryType::A, CreateExpected("5.6.7.8", 80)),
+                  TestMdnsListenerDelegate::CreateExpectedResult(
+                      HostResolver::MdnsListener::Delegate::UpdateType::REMOVED,
+                      DnsQueryType::A, CreateExpected("5.6.7.8", 80))));
+
+  EXPECT_THAT(delegate.text_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.hostname_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.unhandled_results(), testing::IsEmpty());
+}
+
+// Test that removal notifications are sent on natural expiration of MDNS
+// records.
+TEST_F(HostResolverImplTest, MdnsListener_Expiration) {
+  auto socket_factory = std::make_unique<MockMDnsSocketFactory>();
+  base::SimpleTestClock clock;
+  clock.SetNow(base::Time::Now());
+  auto cache_cleanup_timer = std::make_unique<base::MockOneShotTimer>();
+  auto* cache_cleanup_timer_ptr = cache_cleanup_timer.get();
+  auto mdns_client =
+      std::make_unique<MDnsClientImpl>(&clock, std::move(cache_cleanup_timer));
+  mdns_client->StartListening(socket_factory.get());
+  resolver_->SetMdnsClientForTesting(std::move(mdns_client));
+
+  std::unique_ptr<HostResolver::MdnsListener> listener =
+      resolver_->CreateMdnsListener(HostPortPair("myhello.local", 100),
+                                    DnsQueryType::A);
+
+  TestMdnsListenerDelegate delegate;
+  ASSERT_THAT(listener->Start(&delegate), IsOk());
+  ASSERT_THAT(delegate.address_results(), testing::IsEmpty());
+
+  socket_factory->SimulateReceive(kMdnsResponseA, sizeof(kMdnsResponseA));
+
+  EXPECT_THAT(
+      delegate.address_results(),
+      testing::ElementsAre(TestMdnsListenerDelegate::CreateExpectedResult(
+          HostResolver::MdnsListener::Delegate::UpdateType::ADDED,
+          DnsQueryType::A, CreateExpected("1.2.3.4", 100))));
+
+  clock.Advance(base::TimeDelta::FromSeconds(16));
+  cache_cleanup_timer_ptr->Fire();
+
+  EXPECT_THAT(delegate.address_results(),
+              testing::ElementsAre(
+                  TestMdnsListenerDelegate::CreateExpectedResult(
+                      HostResolver::MdnsListener::Delegate::UpdateType::ADDED,
+                      DnsQueryType::A, CreateExpected("1.2.3.4", 100)),
+                  TestMdnsListenerDelegate::CreateExpectedResult(
+                      HostResolver::MdnsListener::Delegate::UpdateType::REMOVED,
+                      DnsQueryType::A, CreateExpected("1.2.3.4", 100))));
+
+  EXPECT_THAT(delegate.text_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.hostname_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.unhandled_results(), testing::IsEmpty());
+}
+
+TEST_F(HostResolverImplTest, MdnsListener_Txt) {
+  auto socket_factory = std::make_unique<MockMDnsSocketFactory>();
+  MockMDnsSocketFactory* socket_factory_ptr = socket_factory.get();
+  resolver_->SetMdnsSocketFactoryForTesting(std::move(socket_factory));
+
+  std::unique_ptr<HostResolver::MdnsListener> listener =
+      resolver_->CreateMdnsListener(HostPortPair("myhello.local", 12),
+                                    DnsQueryType::TXT);
+
+  TestMdnsListenerDelegate delegate;
+  ASSERT_THAT(listener->Start(&delegate), IsOk());
+  ASSERT_THAT(delegate.text_results(), testing::IsEmpty());
+
+  socket_factory_ptr->SimulateReceive(kMdnsResponseTxt,
+                                      sizeof(kMdnsResponseTxt));
+
+  EXPECT_THAT(delegate.text_results(),
+              testing::ElementsAre(
+                  TestMdnsListenerDelegate::CreateExpectedResult(
+                      HostResolver::MdnsListener::Delegate::UpdateType::ADDED,
+                      DnsQueryType::TXT, "foo"),
+                  TestMdnsListenerDelegate::CreateExpectedResult(
+                      HostResolver::MdnsListener::Delegate::UpdateType::ADDED,
+                      DnsQueryType::TXT, "bar")));
+
+  EXPECT_THAT(delegate.address_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.hostname_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.unhandled_results(), testing::IsEmpty());
+}
+
+TEST_F(HostResolverImplTest, MdnsListener_Ptr) {
+  auto socket_factory = std::make_unique<MockMDnsSocketFactory>();
+  MockMDnsSocketFactory* socket_factory_ptr = socket_factory.get();
+  resolver_->SetMdnsSocketFactoryForTesting(std::move(socket_factory));
+
+  std::unique_ptr<HostResolver::MdnsListener> listener =
+      resolver_->CreateMdnsListener(HostPortPair("myhello.local", 13),
+                                    DnsQueryType::PTR);
+
+  TestMdnsListenerDelegate delegate;
+  ASSERT_THAT(listener->Start(&delegate), IsOk());
+  ASSERT_THAT(delegate.text_results(), testing::IsEmpty());
+
+  socket_factory_ptr->SimulateReceive(kMdnsResponsePtr,
+                                      sizeof(kMdnsResponsePtr));
+
+  EXPECT_THAT(
+      delegate.hostname_results(),
+      testing::ElementsAre(TestMdnsListenerDelegate::CreateExpectedResult(
+          HostResolver::MdnsListener::Delegate::UpdateType::ADDED,
+          DnsQueryType::PTR, HostPortPair("foo.com", 13))));
+
+  EXPECT_THAT(delegate.address_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.text_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.unhandled_results(), testing::IsEmpty());
+}
+
+TEST_F(HostResolverImplTest, MdnsListener_Srv) {
+  auto socket_factory = std::make_unique<MockMDnsSocketFactory>();
+  MockMDnsSocketFactory* socket_factory_ptr = socket_factory.get();
+  resolver_->SetMdnsSocketFactoryForTesting(std::move(socket_factory));
+
+  std::unique_ptr<HostResolver::MdnsListener> listener =
+      resolver_->CreateMdnsListener(HostPortPair("myhello.local", 14),
+                                    DnsQueryType::SRV);
+
+  TestMdnsListenerDelegate delegate;
+  ASSERT_THAT(listener->Start(&delegate), IsOk());
+  ASSERT_THAT(delegate.text_results(), testing::IsEmpty());
+
+  socket_factory_ptr->SimulateReceive(kMdnsResponseSrv,
+                                      sizeof(kMdnsResponseSrv));
+
+  EXPECT_THAT(
+      delegate.hostname_results(),
+      testing::ElementsAre(TestMdnsListenerDelegate::CreateExpectedResult(
+          HostResolver::MdnsListener::Delegate::UpdateType::ADDED,
+          DnsQueryType::SRV, HostPortPair("foo.com", 8265))));
+
+  EXPECT_THAT(delegate.address_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.text_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.unhandled_results(), testing::IsEmpty());
+}
+
+// Ensure query types we are not listening for do not affect MdnsListener.
+TEST_F(HostResolverImplTest, MdnsListener_NonListeningTypes) {
+  auto socket_factory = std::make_unique<MockMDnsSocketFactory>();
+  MockMDnsSocketFactory* socket_factory_ptr = socket_factory.get();
+  resolver_->SetMdnsSocketFactoryForTesting(std::move(socket_factory));
+
+  std::unique_ptr<HostResolver::MdnsListener> listener =
+      resolver_->CreateMdnsListener(HostPortPair("myhello.local", 41),
+                                    DnsQueryType::A);
+
+  TestMdnsListenerDelegate delegate;
+  ASSERT_THAT(listener->Start(&delegate), IsOk());
+
+  socket_factory_ptr->SimulateReceive(kMdnsResponseAAAA,
+                                      sizeof(kMdnsResponseAAAA));
+
+  EXPECT_THAT(delegate.address_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.text_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.hostname_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.unhandled_results(), testing::IsEmpty());
+}
+
+TEST_F(HostResolverImplTest, MdnsListener_RootDomain) {
+  auto socket_factory = std::make_unique<MockMDnsSocketFactory>();
+  MockMDnsSocketFactory* socket_factory_ptr = socket_factory.get();
+  resolver_->SetMdnsSocketFactoryForTesting(std::move(socket_factory));
+
+  std::unique_ptr<HostResolver::MdnsListener> listener =
+      resolver_->CreateMdnsListener(HostPortPair("myhello.local", 5),
+                                    DnsQueryType::PTR);
+
+  TestMdnsListenerDelegate delegate;
+  ASSERT_THAT(listener->Start(&delegate), IsOk());
+
+  socket_factory_ptr->SimulateReceive(kMdnsResponsePtrRoot,
+                                      sizeof(kMdnsResponsePtrRoot));
+
+  EXPECT_THAT(delegate.unhandled_results(),
+              testing::ElementsAre(std::make_pair(
+                  HostResolver::MdnsListener::Delegate::UpdateType::ADDED,
+                  DnsQueryType::PTR)));
+
+  EXPECT_THAT(delegate.address_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.text_results(), testing::IsEmpty());
+  EXPECT_THAT(delegate.hostname_results(), testing::IsEmpty());
+}
 #endif  // BUILDFLAG(ENABLE_MDNS)
 
 DnsConfig CreateValidDnsConfig() {
diff --git a/net/dns/host_resolver_mdns_listener_impl.cc b/net/dns/host_resolver_mdns_listener_impl.cc
new file mode 100644
index 0000000..607c5c8
--- /dev/null
+++ b/net/dns/host_resolver_mdns_listener_impl.cc
@@ -0,0 +1,101 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/dns/host_resolver_mdns_listener_impl.h"
+
+#include "base/logging.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "net/dns/host_cache.h"
+#include "net/dns/host_resolver_mdns_task.h"
+#include "net/dns/record_parsed.h"
+
+namespace net {
+
+namespace {
+
+HostResolver::MdnsListener::Delegate::UpdateType ConvertUpdateType(
+    net::MDnsListener::UpdateType type) {
+  switch (type) {
+    case net::MDnsListener::RECORD_ADDED:
+      return HostResolver::MdnsListener::Delegate::UpdateType::ADDED;
+    case net::MDnsListener::RECORD_CHANGED:
+      return HostResolver::MdnsListener::Delegate::UpdateType::CHANGED;
+    case net::MDnsListener::RECORD_REMOVED:
+      return HostResolver::MdnsListener::Delegate::UpdateType::REMOVED;
+  }
+}
+
+}  // namespace
+
+HostResolverMdnsListenerImpl::HostResolverMdnsListenerImpl(
+    const HostPortPair& query_host,
+    DnsQueryType query_type)
+    : query_host_(query_host), query_type_(query_type) {
+  DCHECK_NE(DnsQueryType::UNSPECIFIED, query_type_);
+}
+
+HostResolverMdnsListenerImpl::~HostResolverMdnsListenerImpl() {
+  // Destroy |inner_listener_| first to cancel listening and callbacks to |this|
+  // before anything else becomes invalid.
+  inner_listener_ = nullptr;
+}
+
+int HostResolverMdnsListenerImpl::Start(Delegate* delegate) {
+  DCHECK(delegate);
+  DCHECK(inner_listener_);
+
+  delegate_ = delegate;
+  return inner_listener_->Start() ? OK : ERR_FAILED;
+}
+
+void HostResolverMdnsListenerImpl::OnRecordUpdate(
+    net::MDnsListener::UpdateType update,
+    const RecordParsed* record) {
+  DCHECK(delegate_);
+
+  HostCache::Entry parsed_entry =
+      HostResolverMdnsTask::ParseResult(OK, query_type_, record,
+                                        query_host_.host())
+          .CopyWithDefaultPort(query_host_.port());
+  if (parsed_entry.error() != OK) {
+    delegate_->OnUnhandledResult(ConvertUpdateType(update), query_type_);
+    return;
+  }
+
+  switch (query_type_) {
+    case DnsQueryType::UNSPECIFIED:
+      NOTREACHED();
+      break;
+    case DnsQueryType::A:
+    case DnsQueryType::AAAA:
+      DCHECK(parsed_entry.addresses());
+      DCHECK_EQ(1u, parsed_entry.addresses().value().size());
+      delegate_->OnAddressResult(ConvertUpdateType(update), query_type_,
+                                 parsed_entry.addresses().value().front());
+      break;
+    case DnsQueryType::TXT:
+      DCHECK(parsed_entry.text_records());
+      delegate_->OnTextResult(ConvertUpdateType(update), query_type_,
+                              parsed_entry.text_records().value());
+      break;
+    case DnsQueryType::PTR:
+    case DnsQueryType::SRV:
+      DCHECK(parsed_entry.hostnames());
+      delegate_->OnHostnameResult(ConvertUpdateType(update), query_type_,
+                                  parsed_entry.hostnames().value().front());
+      break;
+  }
+}
+
+void HostResolverMdnsListenerImpl::OnNsecRecord(const std::string& name,
+                                                unsigned type) {
+  // Do nothing. HostResolver does not support listening for NSEC records.
+}
+
+void HostResolverMdnsListenerImpl::OnCachePurged() {
+  // Do nothing. HostResolver does not support listening for cache purges.
+}
+
+}  // namespace net
diff --git a/net/dns/host_resolver_mdns_listener_impl.h b/net/dns/host_resolver_mdns_listener_impl.h
new file mode 100644
index 0000000..66eaf85d
--- /dev/null
+++ b/net/dns/host_resolver_mdns_listener_impl.h
@@ -0,0 +1,55 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_DNS_HOST_RESOLVER_MDNS_LISTENER_IMPL_H_
+#define NET_DNS_HOST_RESOLVER_MDNS_LISTENER_IMPL_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "net/dns/host_resolver.h"
+#include "net/dns/mdns_client.h"
+#include "net/dns/public/dns_query_type.h"
+
+namespace net {
+
+class HostPortPair;
+class RecordParsed;
+
+// Intermediary between the HostResolver::CreateMdnsListener API and the
+// underlying listener functionality in MDnsClient.
+class HostResolverMdnsListenerImpl : public HostResolver::MdnsListener,
+                                     public net::MDnsListener::Delegate {
+ public:
+  using Delegate = HostResolver::MdnsListener::Delegate;
+
+  HostResolverMdnsListenerImpl(const HostPortPair& query_host,
+                               DnsQueryType query_type);
+  ~HostResolverMdnsListenerImpl() override;
+
+  void set_inner_listener(std::unique_ptr<net::MDnsListener> inner_listener) {
+    inner_listener_ = std::move(inner_listener);
+  }
+
+  // HostResolver::MdnsListener implementation
+  int Start(Delegate* delegate) override;
+
+  // net::MDnsListener::Delegate implementation
+  void OnRecordUpdate(net::MDnsListener::UpdateType update,
+                      const RecordParsed* record) override;
+  void OnNsecRecord(const std::string& name, unsigned type) override;
+  void OnCachePurged() override;
+
+ private:
+  HostPortPair query_host_;
+  DnsQueryType query_type_;
+
+  std::unique_ptr<net::MDnsListener> inner_listener_;
+  Delegate* delegate_;
+};
+
+}  // namespace net
+
+#endif  // NET_DNS_HOST_RESOLVER_MDNS_LISTENER_IMPL_H_
diff --git a/net/dns/host_resolver_mdns_task.cc b/net/dns/host_resolver_mdns_task.cc
index b1373fa..c280b256 100644
--- a/net/dns/host_resolver_mdns_task.cc
+++ b/net/dns/host_resolver_mdns_task.cc
@@ -17,6 +17,20 @@
 
 namespace net {
 
+namespace {
+HostCache::Entry ParseHostnameResult(const std::string& host, uint16_t port) {
+  // Filter out root domain. Depending on the type, it either means no-result
+  // or is simply not a result important to any expected Chrome usecases.
+  if (host.empty()) {
+    return HostCache::Entry(ERR_NAME_NOT_RESOLVED,
+                            HostCache::Entry::SOURCE_UNKNOWN);
+  }
+  return HostCache::Entry(OK,
+                          std::vector<HostPortPair>({HostPortPair(host, port)}),
+                          HostCache::Entry::SOURCE_UNKNOWN);
+}
+}  // namespace
+
 class HostResolverMdnsTask::Transaction {
  public:
   Transaction(DnsQueryType query_type, HostResolverMdnsTask* task)
@@ -75,6 +89,7 @@
     int error = ERR_UNEXPECTED;
     switch (result) {
       case MDnsTransaction::RESULT_RECORD:
+        DCHECK(parsed);
         error = OK;
         break;
       case MDnsTransaction::RESULT_NO_RESULTS:
@@ -86,48 +101,8 @@
         NOTREACHED();
     }
 
-    if (error == net::OK) {
-      // Expected to be validated by MDnsClient.
-      DCHECK_EQ(DnsQueryTypeToQtype(query_type_), parsed->type());
-      DCHECK(
-          base::EqualsCaseInsensitiveASCII(task_->hostname_, parsed->name()));
-
-      switch (query_type_) {
-        case DnsQueryType::UNSPECIFIED:
-          // Should create two separate transactions with specified type.
-          NOTREACHED();
-          break;
-        case DnsQueryType::A:
-          results_ = HostCache::Entry(
-              OK,
-              AddressList(
-                  IPEndPoint(parsed->rdata<ARecordRdata>()->address(), 0)),
-              HostCache::Entry::SOURCE_UNKNOWN);
-          break;
-        case DnsQueryType::AAAA:
-          results_ = HostCache::Entry(
-              OK,
-              AddressList(
-                  IPEndPoint(parsed->rdata<AAAARecordRdata>()->address(), 0)),
-              HostCache::Entry::SOURCE_UNKNOWN);
-          break;
-        case DnsQueryType::TXT:
-          results_ =
-              HostCache::Entry(OK, parsed->rdata<TxtRecordRdata>()->texts(),
-                               HostCache::Entry::SOURCE_UNKNOWN);
-          break;
-        case DnsQueryType::PTR:
-          ParseHostnameResult(parsed->rdata<PtrRecordRdata>()->ptrdomain(),
-                              0 /* port */);
-          break;
-        case DnsQueryType::SRV:
-          ParseHostnameResult(parsed->rdata<SrvRecordRdata>()->target(),
-                              parsed->rdata<SrvRecordRdata>()->port());
-          break;
-      }
-    } else {
-      results_ = HostCache::Entry(error, HostCache::Entry::SOURCE_UNKNOWN);
-    }
+    results_ = HostResolverMdnsTask::ParseResult(error, query_type_, parsed,
+                                                 task_->hostname_);
 
     // If we don't have a saved async_transaction, it means OnComplete was
     // invoked inline in MDnsTransaction::Start. Callbacks will need to be
@@ -135,19 +110,6 @@
     task_->CheckCompletion(!async_transaction_);
   }
 
-  void ParseHostnameResult(const std::string& host, uint16_t port) {
-    // Filter out root domain. Depending on the type, it either means no-result
-    // or is simply not a result important to any expected Chrome usecases.
-    if (host.empty()) {
-      results_ = HostCache::Entry(ERR_NAME_NOT_RESOLVED,
-                                  HostCache::Entry::SOURCE_UNKNOWN);
-    } else {
-      results_ = HostCache::Entry(
-          OK, std::vector<HostPortPair>({HostPortPair(host, port)}),
-          HostCache::Entry::SOURCE_UNKNOWN);
-    }
-  }
-
   const DnsQueryType query_type_;
 
   // ERR_IO_PENDING until transaction completes (or is cancelled).
@@ -215,6 +177,50 @@
   return combined_results;
 }
 
+// static
+HostCache::Entry HostResolverMdnsTask::ParseResult(
+    int error,
+    DnsQueryType query_type,
+    const RecordParsed* parsed,
+    const std::string& expected_hostname) {
+  if (error != OK) {
+    return HostCache::Entry(error, HostCache::Entry::SOURCE_UNKNOWN);
+  }
+  DCHECK(parsed);
+
+  // Expected to be validated by MDnsClient.
+  DCHECK_EQ(DnsQueryTypeToQtype(query_type), parsed->type());
+  DCHECK(base::EqualsCaseInsensitiveASCII(expected_hostname, parsed->name()));
+
+  switch (query_type) {
+    case DnsQueryType::UNSPECIFIED:
+      // Should create two separate transactions with specified type.
+      NOTREACHED();
+      return HostCache::Entry(ERR_FAILED, HostCache::Entry::SOURCE_UNKNOWN);
+    case DnsQueryType::A:
+      return HostCache::Entry(
+          OK,
+          AddressList(
+              IPEndPoint(parsed->rdata<net::ARecordRdata>()->address(), 0)),
+          HostCache::Entry::SOURCE_UNKNOWN);
+    case DnsQueryType::AAAA:
+      return HostCache::Entry(
+          OK,
+          AddressList(
+              IPEndPoint(parsed->rdata<net::AAAARecordRdata>()->address(), 0)),
+          HostCache::Entry::SOURCE_UNKNOWN);
+    case DnsQueryType::TXT:
+      return HostCache::Entry(OK, parsed->rdata<net::TxtRecordRdata>()->texts(),
+                              HostCache::Entry::SOURCE_UNKNOWN);
+    case DnsQueryType::PTR:
+      return ParseHostnameResult(parsed->rdata<PtrRecordRdata>()->ptrdomain(),
+                                 0 /* port */);
+    case DnsQueryType::SRV:
+      return ParseHostnameResult(parsed->rdata<SrvRecordRdata>()->target(),
+                                 parsed->rdata<SrvRecordRdata>()->port());
+  }
+}
+
 void HostResolverMdnsTask::CheckCompletion(bool post_needed) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
diff --git a/net/dns/host_resolver_mdns_task.h b/net/dns/host_resolver_mdns_task.h
index c4f9058..5a1c829 100644
--- a/net/dns/host_resolver_mdns_task.h
+++ b/net/dns/host_resolver_mdns_task.h
@@ -21,6 +21,8 @@
 
 namespace net {
 
+class RecordParsed;
+
 // Representation of a single HostResolverImpl::Job task to resolve the hostname
 // using multicast DNS transactions.  Destruction cancels the task and prevents
 // any callbacks from being invoked.
@@ -40,6 +42,11 @@
   // Results only available after invocation of the completion closure.
   HostCache::Entry GetResults() const;
 
+  static HostCache::Entry ParseResult(int error,
+                                      DnsQueryType query_type,
+                                      const RecordParsed* parsed,
+                                      const std::string& expected_hostname);
+
  private:
   class Transaction;
 
diff --git a/net/dns/mdns_client.h b/net/dns/mdns_client.h
index 53889373..89011cc 100644
--- a/net/dns/mdns_client.h
+++ b/net/dns/mdns_client.h
@@ -89,6 +89,10 @@
 // A listener listens for updates regarding a specific record or set of records.
 // Created by the MDnsClient (see |MDnsClient::CreateListener|) and used to keep
 // track of listeners.
+//
+// TODO(ericorth@chromium.org): Consider moving this inside MDnsClient to better
+// organize the namespace and avoid confusion with
+// net::HostResolver::MdnsListener.
 class NET_EXPORT MDnsListener {
  public:
   // Used in the MDnsListener delegate to signify what type of change has been
diff --git a/net/dns/mdns_client_impl.h b/net/dns/mdns_client_impl.h
index 7230855..5627193 100644
--- a/net/dns/mdns_client_impl.h
+++ b/net/dns/mdns_client_impl.h
@@ -190,6 +190,11 @@
   };
 
   MDnsClientImpl();
+
+  // Test constructor, takes a mock clock and mock timer.
+  MDnsClientImpl(base::Clock* clock,
+                 std::unique_ptr<base::OneShotTimer> cleanup_timer);
+
   ~MDnsClientImpl() override;
 
   // MDnsClient implementation:
@@ -211,12 +216,6 @@
   Core* core() { return core_.get(); }
 
  private:
-  FRIEND_TEST_ALL_PREFIXES(MDnsTest, CacheCleanupWithShortTTL);
-
-  // Test constructor, takes a mock clock and mock timer.
-  MDnsClientImpl(base::Clock* clock,
-                 std::unique_ptr<base::OneShotTimer> cleanup_timer);
-
   std::unique_ptr<Core> core_;
   base::Clock* clock_;
   std::unique_ptr<base::OneShotTimer> cleanup_timer_;
diff --git a/net/http/http_auth_handler_negotiate.cc b/net/http/http_auth_handler_negotiate.cc
index 96940f4..5eacf4c 100644
--- a/net/http/http_auth_handler_negotiate.cc
+++ b/net/http/http_auth_handler_negotiate.cc
@@ -13,9 +13,10 @@
 #include "base/strings/stringprintf.h"
 #include "base/values.h"
 #include "net/base/address_family.h"
+#include "net/base/address_list.h"
+#include "net/base/host_port_pair.h"
 #include "net/base/net_errors.h"
 #include "net/cert/x509_util.h"
-#include "net/dns/host_resolver.h"
 #include "net/http/http_auth_filter.h"
 #include "net/http/http_auth_preferences.h"
 #include "net/log/net_log_capture_mode.h"
@@ -137,56 +138,6 @@
 
 HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() = default;
 
-std::string HttpAuthHandlerNegotiate::CreateSPN(const AddressList& address_list,
-                                                const GURL& origin) {
-  // Kerberos Web Server SPNs are in the form HTTP/<host>:<port> through SSPI,
-  // and in the form HTTP@<host>:<port> through GSSAPI
-  //   http://msdn.microsoft.com/en-us/library/ms677601%28VS.85%29.aspx
-  //
-  // However, reality differs from the specification. A good description of
-  // the problems can be found here:
-  //   http://blog.michelbarneveld.nl/michel/archive/2009/11/14/the-reason-why-kb911149-and-kb908209-are-not-the-soluton.aspx
-  //
-  // Typically the <host> portion should be the canonical FQDN for the service.
-  // If this could not be resolved, the original hostname in the URL will be
-  // attempted instead. However, some intranets register SPNs using aliases
-  // for the same canonical DNS name to allow multiple web services to reside
-  // on the same host machine without requiring different ports. IE6 and IE7
-  // have hotpatches that allow the default behavior to be overridden.
-  //   http://support.microsoft.com/kb/911149
-  //   http://support.microsoft.com/kb/938305
-  //
-  // According to the spec, the <port> option should be included if it is a
-  // non-standard port (i.e. not 80 or 443 in the HTTP case). However,
-  // historically browsers have not included the port, even on non-standard
-  // ports. IE6 required a hotpatch and a registry setting to enable
-  // including non-standard ports, and IE7 and IE8 also require the same
-  // registry setting, but no hotpatch. Firefox does not appear to have an
-  // option to include non-standard ports as of 3.6.
-  //   http://support.microsoft.com/kb/908209
-  //
-  // Without any command-line flags, Chrome matches the behavior of Firefox
-  // and IE. Users can override the behavior so aliases are allowed and
-  // non-standard ports are included.
-  int port = origin.EffectiveIntPort();
-  std::string server = address_list.canonical_name();
-  if (server.empty())
-    server = origin.host();
-#if defined(OS_WIN)
-  static const char kSpnSeparator = '/';
-#elif defined(OS_POSIX)
-  static const char kSpnSeparator = '@';
-#endif
-  if (port != 80 && port != 443 &&
-      (http_auth_preferences_ &&
-       http_auth_preferences_->NegotiateEnablePort())) {
-    return base::StringPrintf("HTTP%c%s:%d", kSpnSeparator, server.c_str(),
-                              port);
-  } else {
-    return base::StringPrintf("HTTP%c%s", kSpnSeparator, server.c_str());
-  }
-}
-
 HttpAuth::AuthorizationResult HttpAuthHandlerNegotiate::HandleAnotherChallenge(
     HttpAuthChallengeTokenizer* challenge) {
   return auth_system_.ParseChallenge(challenge);
@@ -273,6 +224,53 @@
   return rv;
 }
 
+std::string HttpAuthHandlerNegotiate::CreateSPN(const std::string& server,
+                                                const GURL& origin) {
+  // Kerberos Web Server SPNs are in the form HTTP/<host>:<port> through SSPI,
+  // and in the form HTTP@<host>:<port> through GSSAPI
+  //   http://msdn.microsoft.com/en-us/library/ms677601%28VS.85%29.aspx
+  //
+  // However, reality differs from the specification. A good description of
+  // the problems can be found here:
+  //   http://blog.michelbarneveld.nl/michel/archive/2009/11/14/the-reason-why-kb911149-and-kb908209-are-not-the-soluton.aspx
+  //
+  // Typically the <host> portion should be the canonical FQDN for the service.
+  // If this could not be resolved, the original hostname in the URL will be
+  // attempted instead. However, some intranets register SPNs using aliases
+  // for the same canonical DNS name to allow multiple web services to reside
+  // on the same host machine without requiring different ports. IE6 and IE7
+  // have hotpatches that allow the default behavior to be overridden.
+  //   http://support.microsoft.com/kb/911149
+  //   http://support.microsoft.com/kb/938305
+  //
+  // According to the spec, the <port> option should be included if it is a
+  // non-standard port (i.e. not 80 or 443 in the HTTP case). However,
+  // historically browsers have not included the port, even on non-standard
+  // ports. IE6 required a hotpatch and a registry setting to enable
+  // including non-standard ports, and IE7 and IE8 also require the same
+  // registry setting, but no hotpatch. Firefox does not appear to have an
+  // option to include non-standard ports as of 3.6.
+  //   http://support.microsoft.com/kb/908209
+  //
+  // Without any command-line flags, Chrome matches the behavior of Firefox
+  // and IE. Users can override the behavior so aliases are allowed and
+  // non-standard ports are included.
+  int port = origin.EffectiveIntPort();
+#if defined(OS_WIN)
+  static const char kSpnSeparator = '/';
+#elif defined(OS_POSIX)
+  static const char kSpnSeparator = '@';
+#endif
+  if (port != 80 && port != 443 &&
+      (http_auth_preferences_ &&
+       http_auth_preferences_->NegotiateEnablePort())) {
+    return base::StringPrintf("HTTP%c%s:%d", kSpnSeparator, server.c_str(),
+                              port);
+  } else {
+    return base::StringPrintf("HTTP%c%s", kSpnSeparator, server.c_str());
+  }
+}
+
 void HttpAuthHandlerNegotiate::OnIOComplete(int result) {
   int rv = DoLoop(result);
   if (rv != ERR_IO_PENDING)
@@ -325,27 +323,36 @@
     return OK;
 
   // TODO(cbentzel): Add reverse DNS lookup for numeric addresses.
-  HostResolver::RequestInfo info(HostPortPair(origin_.host(), 0));
-  info.set_host_resolver_flags(HOST_RESOLVER_CANONNAME);
-  return resolver_->Resolve(info, DEFAULT_PRIORITY, &address_list_,
-                            base::Bind(&HttpAuthHandlerNegotiate::OnIOComplete,
-                                       base::Unretained(this)),
-                            &request_, net_log_);
+  HostResolver::ResolveHostParameters parameters;
+  parameters.include_canonical_name = true;
+  resolve_host_request_ = resolver_->CreateRequest(
+      HostPortPair(origin_.host(), 0), net_log_, parameters);
+  return resolve_host_request_->Start(base::BindOnce(
+      &HttpAuthHandlerNegotiate::OnIOComplete, base::Unretained(this)));
 }
 
 int HttpAuthHandlerNegotiate::DoResolveCanonicalNameComplete(int rv) {
   DCHECK_NE(ERR_IO_PENDING, rv);
-  if (rv != OK) {
-    // Even in the error case, try to use origin_.host instead of
-    // passing the failure on to the caller.
-    VLOG(1) << "Problem finding canonical name for SPN for host "
-            << origin_.host() << ": " << ErrorToString(rv);
-    rv = OK;
+  std::string server = origin_.host();
+  if (resolve_host_request_) {
+    if (rv == OK) {
+      DCHECK(resolve_host_request_->GetAddressResults());
+      const std::string& canonical_name =
+          resolve_host_request_->GetAddressResults().value().canonical_name();
+      if (!canonical_name.empty())
+        server = canonical_name;
+    } else {
+      // Even in the error case, try to use origin_.host instead of
+      // passing the failure on to the caller.
+      VLOG(1) << "Problem finding canonical name for SPN for host "
+              << origin_.host() << ": " << ErrorToString(rv);
+      rv = OK;
+    }
   }
 
   next_state_ = STATE_GENERATE_AUTH_TOKEN;
-  spn_ = CreateSPN(address_list_, origin_);
-  address_list_ = AddressList();
+  spn_ = CreateSPN(server, origin_);
+  resolve_host_request_ = nullptr;
   return rv;
 }
 
diff --git a/net/http/http_auth_handler_negotiate.h b/net/http/http_auth_handler_negotiate.h
index 0cd2e9d..de65976 100644
--- a/net/http/http_auth_handler_negotiate.h
+++ b/net/http/http_auth_handler_negotiate.h
@@ -5,11 +5,11 @@
 #ifndef NET_HTTP_HTTP_AUTH_HANDLER_NEGOTIATE_H_
 #define NET_HTTP_HTTP_AUTH_HANDLER_NEGOTIATE_H_
 
+#include <memory>
 #include <string>
 #include <utility>
 
 #include "build/build_config.h"
-#include "net/base/address_list.h"
 #include "net/base/completion_once_callback.h"
 #include "net/base/net_export.h"
 #include "net/dns/host_resolver.h"
@@ -107,10 +107,6 @@
 
   ~HttpAuthHandlerNegotiate() override;
 
-  // These are public for unit tests
-  std::string CreateSPN(const AddressList& address_list, const GURL& orign);
-  const std::string& spn() const { return spn_; }
-
   // HttpAuthHandler:
   HttpAuth::AuthorizationResult HandleAnotherChallenge(
       HttpAuthChallengeTokenizer* challenge) override;
@@ -118,6 +114,8 @@
   bool AllowsDefaultCredentials() override;
   bool AllowsExplicitCredentials() override;
 
+  const std::string& spn_for_testing() const { return spn_; }
+
  protected:
   bool Init(HttpAuthChallengeTokenizer* challenge,
             const SSLInfo& ssl_info) override;
@@ -136,6 +134,8 @@
     STATE_NONE,
   };
 
+  std::string CreateSPN(const std::string& server, const GURL& orign);
+
   void OnIOComplete(int result);
   void DoCallback(int result);
   int DoLoop(int result);
@@ -150,8 +150,7 @@
   HostResolver* const resolver_;
 
   // Members which are needed for DNS lookup + SPN.
-  AddressList address_list_;
-  std::unique_ptr<net::HostResolver::Request> request_;
+  std::unique_ptr<HostResolver::ResolveHostRequest> resolve_host_request_;
 
   // Things which should be consistent after first call to GenerateAuthToken.
   bool already_called_;
diff --git a/net/http/http_auth_handler_negotiate_unittest.cc b/net/http/http_auth_handler_negotiate_unittest.cc
index ad4f9b0..d4a932ff 100644
--- a/net/http/http_auth_handler_negotiate_unittest.cc
+++ b/net/http/http_auth_handler_negotiate_unittest.cc
@@ -51,7 +51,7 @@
   void SetUp() override {
     auth_library_ = new MockAuthLibrary();
     resolver_.reset(new MockHostResolver());
-    resolver_->rules_map()[HostResolverSource::SYSTEM]->AddIPLiteralRule(
+    resolver_->rules_map()[HostResolverSource::ANY]->AddIPLiteralRule(
         "alias", "10.0.0.2", "canonical.example.com");
 
     http_auth_preferences_.reset(new MockAllowHttpAuthPreferences());
@@ -259,9 +259,9 @@
   EXPECT_EQ(OK, callback.GetResult(auth_handler->GenerateAuthToken(
                     NULL, &request_info, callback.callback(), &token)));
 #if defined(OS_WIN)
-  EXPECT_EQ("HTTP/alias", auth_handler->spn());
+  EXPECT_EQ("HTTP/alias", auth_handler->spn_for_testing());
 #elif defined(OS_POSIX)
-  EXPECT_EQ("HTTP@alias", auth_handler->spn());
+  EXPECT_EQ("HTTP@alias", auth_handler->spn_for_testing());
 #endif
 }
 
@@ -277,9 +277,9 @@
   EXPECT_EQ(OK, callback.GetResult(auth_handler->GenerateAuthToken(
                     NULL, &request_info, callback.callback(), &token)));
 #if defined(OS_WIN)
-  EXPECT_EQ("HTTP/alias", auth_handler->spn());
+  EXPECT_EQ("HTTP/alias", auth_handler->spn_for_testing());
 #elif defined(OS_POSIX)
-  EXPECT_EQ("HTTP@alias", auth_handler->spn());
+  EXPECT_EQ("HTTP@alias", auth_handler->spn_for_testing());
 #endif
 }
 
@@ -295,9 +295,9 @@
   EXPECT_EQ(OK, callback.GetResult(auth_handler->GenerateAuthToken(
                     NULL, &request_info, callback.callback(), &token)));
 #if defined(OS_WIN)
-  EXPECT_EQ("HTTP/alias:500", auth_handler->spn());
+  EXPECT_EQ("HTTP/alias:500", auth_handler->spn_for_testing());
 #elif defined(OS_POSIX)
-  EXPECT_EQ("HTTP@alias:500", auth_handler->spn());
+  EXPECT_EQ("HTTP@alias:500", auth_handler->spn_for_testing());
 #endif
 }
 
@@ -313,9 +313,9 @@
   EXPECT_EQ(OK, callback.GetResult(auth_handler->GenerateAuthToken(
                     NULL, &request_info, callback.callback(), &token)));
 #if defined(OS_WIN)
-  EXPECT_EQ("HTTP/canonical.example.com", auth_handler->spn());
+  EXPECT_EQ("HTTP/canonical.example.com", auth_handler->spn_for_testing());
 #elif defined(OS_POSIX)
-  EXPECT_EQ("HTTP@canonical.example.com", auth_handler->spn());
+  EXPECT_EQ("HTTP@canonical.example.com", auth_handler->spn_for_testing());
 #endif
 }
 
@@ -332,9 +332,9 @@
       NULL, &request_info, callback.callback(), &token));
   EXPECT_THAT(callback.WaitForResult(), IsOk());
 #if defined(OS_WIN)
-  EXPECT_EQ("HTTP/canonical.example.com", auth_handler->spn());
+  EXPECT_EQ("HTTP/canonical.example.com", auth_handler->spn_for_testing());
 #elif defined(OS_POSIX)
-  EXPECT_EQ("HTTP@canonical.example.com", auth_handler->spn());
+  EXPECT_EQ("HTTP@canonical.example.com", auth_handler->spn_for_testing());
 #endif
 }
 
diff --git a/net/proxy_resolution/pac_file_decider.cc b/net/proxy_resolution/pac_file_decider.cc
index 9eb3640..8b5b37c7 100644
--- a/net/proxy_resolution/pac_file_decider.cc
+++ b/net/proxy_resolution/pac_file_decider.cc
@@ -16,7 +16,9 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "net/base/completion_repeating_callback.h"
+#include "net/base/host_port_pair.h"
 #include "net/base/net_errors.h"
+#include "net/base/request_priority.h"
 #include "net/log/net_log_capture_mode.h"
 #include "net/log/net_log_event_type.h"
 #include "net/log/net_log_source_type.h"
@@ -266,8 +268,20 @@
 
   quick_check_start_time_ = base::Time::Now();
   std::string host = current_pac_source().url.host();
-  HostResolver::RequestInfo reqinfo(HostPortPair(host, 80));
-  reqinfo.set_host_resolver_flags(HOST_RESOLVER_SYSTEM_ONLY);
+
+  HostResolver::ResolveHostParameters parameters;
+  // We use HIGHEST here because proxy decision blocks doing any other requests.
+  parameters.initial_priority = HIGHEST;
+  // Only resolve via the system resolver for maximum compatibility with DNS
+  // suffix search paths, because for security, we are relying on suffix search
+  // paths rather than WPAD-standard DNS devolution.
+  parameters.source = HostResolverSource::SYSTEM;
+
+  HostResolver* host_resolver =
+      pac_file_fetcher_->GetRequestContext()->host_resolver();
+  resolve_request_ = host_resolver->CreateRequest(HostPortPair(host, 80),
+                                                  net_log_, parameters);
+
   CompletionRepeatingCallback callback = base::BindRepeating(
       &PacFileDecider::OnIOCompletion, base::Unretained(this));
 
@@ -276,12 +290,7 @@
       FROM_HERE, base::TimeDelta::FromMilliseconds(kQuickCheckDelayMs),
       base::BindRepeating(callback, ERR_NAME_NOT_RESOLVED));
 
-  HostResolver* host_resolver =
-      pac_file_fetcher_->GetRequestContext()->host_resolver();
-
-  // We use HIGHEST here because proxy decision blocks doing any other requests.
-  return host_resolver->Resolve(reqinfo, HIGHEST, &wpad_addresses_, callback,
-                                &request_, net_log_);
+  return resolve_request_->Start(callback);
 }
 
 int PacFileDecider::DoQuickCheckComplete(int result) {
@@ -291,7 +300,7 @@
     UMA_HISTOGRAM_TIMES("Net.WpadQuickCheckSuccess", delta);
   else
     UMA_HISTOGRAM_TIMES("Net.WpadQuickCheckFailure", delta);
-  request_.reset();
+  resolve_request_.reset();
   quick_check_timer_.Stop();
   if (result != OK)
     return TryToFallbackPacSource(result);
@@ -473,7 +482,7 @@
 
   switch (next_state_) {
     case STATE_QUICK_CHECK_COMPLETE:
-      request_.reset();
+      resolve_request_.reset();
       break;
     case STATE_WAIT_COMPLETE:
       wait_timer_.Stop();
@@ -491,7 +500,7 @@
   if (dhcp_pac_file_fetcher_)
     dhcp_pac_file_fetcher_->Cancel();
 
-  DCHECK(!request_);
+  DCHECK(!resolve_request_);
 
   DidComplete();
 }
diff --git a/net/proxy_resolution/pac_file_decider.h b/net/proxy_resolution/pac_file_decider.h
index af8438d41..e1f038d 100644
--- a/net/proxy_resolution/pac_file_decider.h
+++ b/net/proxy_resolution/pac_file_decider.h
@@ -7,6 +7,7 @@
 
 #include <stddef.h>
 
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -15,7 +16,6 @@
 #include "base/strings/string16.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
-#include "net/base/address_list.h"
 #include "net/base/completion_once_callback.h"
 #include "net/base/net_export.h"
 #include "net/dns/host_resolver.h"
@@ -199,9 +199,9 @@
   ProxyConfigWithAnnotation effective_config_;
   scoped_refptr<PacFileData> script_data_;
 
-  AddressList wpad_addresses_;
+  std::unique_ptr<HostResolver::ResolveHostRequest> resolve_request_;
+
   base::OneShotTimer quick_check_timer_;
-  std::unique_ptr<HostResolver::Request> request_;
   base::Time quick_check_start_time_;
 
   DISALLOW_COPY_AND_ASSIGN(PacFileDecider);
diff --git a/net/socket/tcp_socket_unittest.cc b/net/socket/tcp_socket_unittest.cc
index 62ce898..359426d 100644
--- a/net/socket/tcp_socket_unittest.cc
+++ b/net/socket/tcp_socket_unittest.cc
@@ -22,6 +22,7 @@
 #include "net/base/sockaddr_storage.h"
 #include "net/base/test_completion_callback.h"
 #include "net/log/net_log_source.h"
+#include "net/socket/socket_descriptor.h"
 #include "net/socket/socket_performance_watcher.h"
 #include "net/socket/socket_test_util.h"
 #include "net/socket/tcp_client_socket.h"
@@ -596,13 +597,7 @@
   ASSERT_EQ(0, memcmp(&kMsg, read_buffer->data(), msg_size));
 }
 
-// Flaky on iOS device, see crbug.com/921852
-#if defined(OS_IOS) && !(TARGET_OS_SIMULATOR)
-#define MAYBE_IsConnected FLAKY_IsConnected
-#else
-#define MAYBE_IsConnected IsConnected
-#endif  // defined(OS_IOS) && !(TARGET_OS_SIMULATOR)
-TEST_F(TCPSocketTest, MAYBE_IsConnected) {
+TEST_F(TCPSocketTest, IsConnected) {
   ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
 
   TestCompletionCallback accept_callback;
@@ -642,8 +637,16 @@
               1);
   accepted_socket.reset();
 
-  // |connecting_socket| should have data to read, so should still be reported
-  // as connected, but not idle.
+  // Wait until |connecting_socket| is signalled as having data to read.
+  fd_set read_fds;
+  FD_ZERO(&read_fds);
+  SocketDescriptor connecting_fd =
+      connecting_socket.SocketDescriptorForTesting();
+  FD_SET(connecting_fd, &read_fds);
+  ASSERT_EQ(select(FD_SETSIZE, &read_fds, nullptr, nullptr, nullptr), 1);
+  ASSERT_TRUE(FD_ISSET(connecting_fd, &read_fds));
+
+  // It should now be reported as connected, but not as idle.
   EXPECT_TRUE(connecting_socket.IsConnected());
   EXPECT_FALSE(connecting_socket.IsConnectedAndIdle());
 
diff --git a/remoting/host/remoting_me2me_host.cc b/remoting/host/remoting_me2me_host.cc
index 105fc13..925065c 100644
--- a/remoting/host/remoting_me2me_host.cc
+++ b/remoting/host/remoting_me2me_host.cc
@@ -466,10 +466,7 @@
   // And remove the same line from me2me_desktop_environment.cc.
 
 
-  // TODO(jarhar): Replace this ifdef with a chrome policy.
-#ifdef CHROME_REMOTE_DESKTOP_FILE_TRANSFER_ENABLED
   desktop_environment_options_.set_enable_file_transfer(true);
-#endif
 
   StartOnUiThread();
 }
diff --git a/remoting/protocol/webrtc_audio_stream.cc b/remoting/protocol/webrtc_audio_stream.cc
index 46b996e..3028b01 100644
--- a/remoting/protocol/webrtc_audio_stream.cc
+++ b/remoting/protocol/webrtc_audio_stream.cc
@@ -24,11 +24,8 @@
 WebrtcAudioStream::WebrtcAudioStream() = default;
 
 WebrtcAudioStream::~WebrtcAudioStream() {
-  if (stream_) {
-    for (const auto& track : stream_->GetAudioTracks()) {
-      stream_->RemoveTrack(track.get());
-    }
-    peer_connection_->RemoveStream(stream_.get());
+  if (audio_sender_) {
+    peer_connection_->RemoveTrack(audio_sender_.get());
   }
 }
 
@@ -50,17 +47,11 @@
       peer_connection_factory->CreateAudioTrack(kAudioTrackLabel,
                                                 source_adapter_.get());
 
-  stream_ = peer_connection_factory->CreateLocalMediaStream(kAudioStreamLabel);
-
-  // AddTrack() may fail only if there is another track with the same name,
-  // which is impossible because it's a brand new stream.
-  bool result = stream_->AddTrack(audio_track.get());
-  DCHECK(result);
-
-  // AddStream() may fail if there is another stream with the same name or when
-  // the PeerConnection is closed, neither is expected.
-  result = peer_connection_->AddStream(stream_.get());
-  DCHECK(result);
+  // value() DCHECKs if AddTrack() fails, which only happens if a track was
+  // already added with the stream label.
+  audio_sender_ =
+      peer_connection_->AddTrack(audio_track.get(), {kAudioStreamLabel})
+          .value();
 }
 
 void WebrtcAudioStream::Pause(bool pause) {
diff --git a/remoting/protocol/webrtc_audio_stream.h b/remoting/protocol/webrtc_audio_stream.h
index 6c5981a..75ac45e 100644
--- a/remoting/protocol/webrtc_audio_stream.h
+++ b/remoting/protocol/webrtc_audio_stream.h
@@ -16,8 +16,8 @@
 }  // namespace webrtc
 
 namespace webrtc {
-class MediaStreamInterface;
 class PeerConnectionInterface;
+class RtpSenderInterface;
 }  // namespace webrtc
 
 namespace remoting {
@@ -43,7 +43,7 @@
   scoped_refptr<WebrtcAudioSourceAdapter> source_adapter_;
 
   scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
-  scoped_refptr<webrtc::MediaStreamInterface> stream_;
+  scoped_refptr<webrtc::RtpSenderInterface> audio_sender_;
 
   DISALLOW_COPY_AND_ASSIGN(WebrtcAudioStream);
 };
diff --git a/remoting/protocol/webrtc_transport.cc b/remoting/protocol/webrtc_transport.cc
index 23efe17..a682eb8f 100644
--- a/remoting/protocol/webrtc_transport.cc
+++ b/remoting/protocol/webrtc_transport.cc
@@ -264,11 +264,7 @@
 
     rtc_config.media_config.video.periodic_alr_bandwidth_probing = true;
 
-    // Currently, the host only supports the deprecated "Plan B" semantics, so
-    // specify it here.
-    // TODO(crbug.com/903012): Change to kUnifiedPlan and make any necessary
-    // changes for this to work.
-    rtc_config.sdp_semantics = webrtc::SdpSemantics::kPlanB;
+    rtc_config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
 
     peer_connection_ = peer_connection_factory_->CreatePeerConnection(
         rtc_config, std::move(port_allocator), nullptr, this);
diff --git a/remoting/protocol/webrtc_video_stream.cc b/remoting/protocol/webrtc_video_stream.cc
index b26fdb24..570c794 100644
--- a/remoting/protocol/webrtc_video_stream.cc
+++ b/remoting/protocol/webrtc_video_stream.cc
@@ -100,11 +100,8 @@
 
 WebrtcVideoStream::~WebrtcVideoStream() {
   DCHECK(thread_checker_.CalledOnValidThread());
-  if (stream_) {
-    for (const auto& track : stream_->GetVideoTracks()) {
-      stream_->RemoveTrack(track.get());
-    }
-    peer_connection_->RemoveStream(stream_.get());
+  if (video_sender_) {
+    peer_connection_->RemoveTrack(video_sender_.get());
   }
 }
 
@@ -138,17 +135,10 @@
   rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track =
       peer_connection_factory->CreateVideoTrack(kVideoLabel, src);
 
-  stream_ = peer_connection_factory->CreateLocalMediaStream(kStreamLabel);
-
-  // AddTrack() may fail only if there is another track with the same name,
-  // which is impossible because it's a brand new stream.
-  bool result = stream_->AddTrack(video_track.get());
-  DCHECK(result);
-
-  // AddStream() may fail if there is another stream with the same name or when
-  // the PeerConnection is closed, neither is expected.
-  result = peer_connection_->AddStream(stream_.get());
-  DCHECK(result);
+  // value() DCHECKs if AddTrack() fails, which only happens if a track was
+  // already added with the stream label.
+  video_sender_ =
+      peer_connection_->AddTrack(video_track.get(), {kStreamLabel}).value();
 
   scheduler_.reset(new WebrtcFrameSchedulerSimple(session_options_));
   scheduler_->Start(
diff --git a/remoting/protocol/webrtc_video_stream.h b/remoting/protocol/webrtc_video_stream.h
index 736b645..b19c8ae1 100644
--- a/remoting/protocol/webrtc_video_stream.h
+++ b/remoting/protocol/webrtc_video_stream.h
@@ -25,8 +25,8 @@
 #include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
 
 namespace webrtc {
-class MediaStreamInterface;
 class PeerConnectionInterface;
+class RtpSenderInterface;
 }  // namespace webrtc
 
 namespace remoting {
@@ -87,7 +87,7 @@
   scoped_refptr<InputEventTimestampsSource> event_timestamps_source_;
 
   scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
-  scoped_refptr<webrtc::MediaStreamInterface> stream_;
+  scoped_refptr<webrtc::RtpSenderInterface> video_sender_;
 
   HostVideoStatsDispatcher video_stats_dispatcher_;
 
diff --git a/services/identity/public/cpp/identity_manager.cc b/services/identity/public/cpp/identity_manager.cc
index 603b820..95b2734 100644
--- a/services/identity/public/cpp/identity_manager.cc
+++ b/services/identity/public/cpp/identity_manager.cc
@@ -132,6 +132,10 @@
   return HasAccountWithRefreshToken(GetPrimaryAccountId());
 }
 
+bool IdentityManager::AreRefreshTokensLoaded() const {
+  return token_service_->AreAllCredentialsLoaded();
+}
+
 base::Optional<AccountInfo>
 IdentityManager::FindAccountInfoForAccountWithRefreshTokenByAccountId(
     const std::string& account_id) const {
diff --git a/services/identity/public/cpp/identity_manager.h b/services/identity/public/cpp/identity_manager.h
index 96f9b413..cb2d38f 100644
--- a/services/identity/public/cpp/identity_manager.h
+++ b/services/identity/public/cpp/identity_manager.h
@@ -230,6 +230,9 @@
   // exists for the primary account.
   bool HasPrimaryAccountWithRefreshToken() const;
 
+  // Returns true if all refresh tokens have been loaded from disk.
+  bool AreRefreshTokensLoaded() const;
+
   // Looks up and returns information for account with given |account_id|. If
   // the account cannot be found, return an empty optional. This is equivalent
   // to searching on the vector returned by GetAccountsWithRefreshTokens() but
@@ -342,7 +345,6 @@
       const std::string& account_id);
   friend void RemoveRefreshTokenForAccount(IdentityManager* identity_manager,
                                            const std::string& account_id);
-  friend bool AreAllCredentialsLoaded(IdentityManager* identity_manager);
   friend void UpdateAccountInfoForAccount(IdentityManager* identity_manager,
                                           AccountInfo account_info);
   friend void UpdatePersistentErrorOfRefreshTokenForAccount(
diff --git a/services/identity/public/cpp/identity_manager_unittest.cc b/services/identity/public/cpp/identity_manager_unittest.cc
index 80a91d9..1f50efa 100644
--- a/services/identity/public/cpp/identity_manager_unittest.cc
+++ b/services/identity/public/cpp/identity_manager_unittest.cc
@@ -2083,4 +2083,21 @@
   EXPECT_EQ(foo_account_info.gaia, maybe_account_info.value().gaia);
 }
 
+// Checks that AreRefreshTokensLoaded() returns true after LoadCredentials.
+TEST_F(IdentityManagerTest, AreRefreshTokensLoaded) {
+  base::RunLoop run_loop;
+  identity_manager_observer()->set_on_refresh_tokens_loaded_callback(
+      run_loop.QuitClosure());
+
+  // Credentials are already loaded in SigninManager::Initialize()
+  // which runs even before the IdentityManager is created. That's why
+  // we fake the credentials loaded state and force another load in
+  // order to test AreRefreshTokensLoaded.
+  token_service()->set_all_credentials_loaded_for_testing(false);
+  EXPECT_FALSE(identity_manager()->AreRefreshTokensLoaded());
+  token_service()->LoadCredentials("");
+  run_loop.Run();
+  EXPECT_TRUE(identity_manager()->AreRefreshTokensLoaded());
+}
+
 }  // namespace identity
diff --git a/services/identity/public/cpp/identity_test_utils.cc b/services/identity/public/cpp/identity_test_utils.cc
index 529f678..b9048576 100644
--- a/services/identity/public/cpp/identity_test_utils.cc
+++ b/services/identity/public/cpp/identity_test_utils.cc
@@ -306,10 +306,6 @@
   run_loop.Run();
 }
 
-bool AreAllCredentialsLoaded(IdentityManager* identity_manager) {
-  return identity_manager->GetTokenService()->AreAllCredentialsLoaded();
-}
-
 void SetCookieAccounts(FakeGaiaCookieManagerService* cookie_manager,
                        IdentityManager* identity_manager,
                        const std::vector<CookieParams>& cookie_accounts) {
diff --git a/services/identity/public/cpp/identity_test_utils.h b/services/identity/public/cpp/identity_test_utils.h
index 29ef957..6e71aef5 100644
--- a/services/identity/public/cpp/identity_test_utils.h
+++ b/services/identity/public/cpp/identity_test_utils.h
@@ -119,9 +119,6 @@
 void RemoveRefreshTokenForAccount(IdentityManager* identity_manager,
                                   const std::string& account_id);
 
-// Returns true if all credentials have been loaded from disk.
-bool AreAllCredentialsLoaded(IdentityManager* identity_manager);
-
 // Puts the given accounts into the Gaia cookie, replacing any previous
 // accounts. Blocks until the accounts have been set.
 // NOTE: See disclaimer at top of file re: direct usage.
diff --git a/services/media_session/public/cpp/media_metadata.cc b/services/media_session/public/cpp/media_metadata.cc
index 79468f9..0d07d04e8 100644
--- a/services/media_session/public/cpp/media_metadata.cc
+++ b/services/media_session/public/cpp/media_metadata.cc
@@ -28,7 +28,8 @@
 
 bool MediaMetadata::operator==(const MediaMetadata& other) const {
   return title == other.title && artist == other.artist &&
-         album == other.album && artwork == other.artwork;
+         album == other.album && artwork == other.artwork &&
+         source_title == other.source_title;
 }
 
 bool MediaMetadata::operator!=(const MediaMetadata& other) const {
diff --git a/services/media_session/public/cpp/media_metadata.h b/services/media_session/public/cpp/media_metadata.h
index db2ff7c..6716521 100644
--- a/services/media_session/public/cpp/media_metadata.h
+++ b/services/media_session/public/cpp/media_metadata.h
@@ -75,6 +75,11 @@
 
   // Artwork associated to the MediaSession.
   std::vector<MediaImage> artwork;
+
+  // The |source_title| is a human readable title for the source of the media
+  // session. This could be the name of the app or the name of the site playing
+  // media.
+  base::string16 source_title;
 };
 
 }  // namespace media_session
diff --git a/services/media_session/public/cpp/media_session_mojom_traits.cc b/services/media_session/public/cpp/media_session_mojom_traits.cc
index 067c7a2..cfddaea1 100644
--- a/services/media_session/public/cpp/media_session_mojom_traits.cc
+++ b/services/media_session/public/cpp/media_session_mojom_traits.cc
@@ -32,13 +32,19 @@
          media_session::MediaMetadata* out) {
   if (!data.ReadTitle(&out->title))
     return false;
+
   if (!data.ReadArtist(&out->artist))
     return false;
+
   if (!data.ReadAlbum(&out->album))
     return false;
+
   if (!data.ReadArtwork(&out->artwork))
     return false;
 
+  if (!data.ReadSourceTitle(&out->source_title))
+    return false;
+
   return true;
 }
 
diff --git a/services/media_session/public/cpp/media_session_mojom_traits.h b/services/media_session/public/cpp/media_session_mojom_traits.h
index b49dfa7..e96b276 100644
--- a/services/media_session/public/cpp/media_session_mojom_traits.h
+++ b/services/media_session/public/cpp/media_session_mojom_traits.h
@@ -54,6 +54,11 @@
     return metadata.artwork;
   }
 
+  static const base::string16& source_title(
+      const media_session::MediaMetadata& metadata) {
+    return metadata.source_title;
+  }
+
   static bool Read(media_session::mojom::MediaMetadataDataView data,
                    media_session::MediaMetadata* out);
 };
diff --git a/services/media_session/public/cpp/test/mock_media_session.cc b/services/media_session/public/cpp/test/mock_media_session.cc
index 12ae3caf..9af4e118 100644
--- a/services/media_session/public/cpp/test/mock_media_session.cc
+++ b/services/media_session/public/cpp/test/mock_media_session.cc
@@ -11,6 +11,19 @@
 namespace media_session {
 namespace test {
 
+namespace {
+
+bool IsMetadataNonEmpty(const base::Optional<MediaMetadata>& metadata) {
+  if (!metadata.has_value())
+    return false;
+
+  return !metadata->title.empty() || !metadata->artist.empty() ||
+         !metadata->album.empty() || !metadata->source_title.empty() ||
+         !metadata->artwork.empty();
+}
+
+}  // namespace
+
 MockMediaSessionMojoObserver::MockMediaSessionMojoObserver(
     mojom::MediaSession& media_session)
     : binding_(this) {
@@ -46,7 +59,7 @@
   if (waiting_for_metadata_) {
     run_loop_->Quit();
     waiting_for_metadata_ = false;
-  } else if (waiting_for_non_empty_metadata_ && metadata.has_value()) {
+  } else if (waiting_for_non_empty_metadata_ && IsMetadataNonEmpty(metadata)) {
     run_loop_->Quit();
     waiting_for_non_empty_metadata_ = false;
   }
diff --git a/services/media_session/public/mojom/media_session.mojom b/services/media_session/public/mojom/media_session.mojom
index 7d9e4b2a..148bcfa 100644
--- a/services/media_session/public/mojom/media_session.mojom
+++ b/services/media_session/public/mojom/media_session.mojom
@@ -38,12 +38,17 @@
 };
 
 // MediaMetadata
-// Spec: https://wicg.github.io/mediasession/
 struct MediaMetadata {
+  // These are defined in the spec: https://wicg.github.io/mediasession/
   mojo_base.mojom.String16 title;
   mojo_base.mojom.String16 artist;
   mojo_base.mojom.String16 album;
   array<MediaImage> artwork;
+
+  // The |source_title| is a human readable title for the source of the media
+  // session. This could be the name of the app or the name of the site playing
+  // media.
+  mojo_base.mojom.String16 source_title;
 };
 
 // Contains state information about a MediaSession.
diff --git a/services/service_manager/public/cpp/manifest.h b/services/service_manager/public/cpp/manifest.h
index 59434b9..85fe136 100644
--- a/services/service_manager/public/cpp/manifest.h
+++ b/services/service_manager/public/cpp/manifest.h
@@ -111,7 +111,7 @@
     // The type of sandboxing required by instances of this service.
     //
     // TODO(https://crbug.com/915806): Make this field a SandboxType enum.
-    std::string sandbox_type{"none"};
+    std::string sandbox_type{"utility"};
   };
 
   // Represents a file required by instances of the service despite being
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 f4a098e..af057de 100644
--- a/testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter
+++ b/testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter
@@ -12,6 +12,11 @@
 # Crashing on Mash. https://crbug.com/919108
 -ChromeBrowserMainBrowserTest.VariationsServiceStartsRequestOnNetworkChange
 
+# https://crbug.com/921814
+# ProxyResolutionServiceProvider needs to be converted to NetworkService.
+-ProxyResolutionServiceProviderPacBrowserTest.ResolveProxy
+-ProxyResolutionServiceProviderManualProxyBrowserTest.ResolveProxy
+
 # 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/mojo.fyi.network_webview_instrumentation_test_apk.filter b/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter
index 509a543..47a7e25 100644
--- a/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter
+++ b/testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter
@@ -48,7 +48,6 @@
 -org.chromium.android_webview.test.AwContentsClientShouldInterceptRequestTest.testDoesNotChangeReportedUrl
 
 # https://crbug.com/893568
--org.chromium.android_webview.test.AwContentsTest.testCanInjectHeaders
 -org.chromium.android_webview.test.AwContentsTest.testDownload
 -org.chromium.android_webview.test.AwContentsTest.testEscapingOfErrorPage
 
@@ -58,17 +57,10 @@
 
 # https://crbug.com/893570
 -org.chromium.android_webview.test.AwSettingsTest.testAssetUrl
--org.chromium.android_webview.test.AwSettingsTest.testBlockNetworkLoadsWithAudio
--org.chromium.android_webview.test.AwSettingsTest.testBlockNetworkLoadsWithHttpResources
--org.chromium.android_webview.test.AwSettingsTest.testCacheModeWithBlockedNetworkLoads
 -org.chromium.android_webview.test.AwSettingsTest.testContentUrlAccessWithTwoViews
 -org.chromium.android_webview.test.AwSettingsTest.testContentUrlFromFileWithTwoViews
--org.chromium.android_webview.test.AwSettingsTest.testFileAccessFromFilesIframeWithTwoViews
--org.chromium.android_webview.test.AwSettingsTest.testFileAccessFromFilesImage
--org.chromium.android_webview.test.AwSettingsTest.testFileAccessFromFilesXhrWithTwoViews
 -org.chromium.android_webview.test.AwSettingsTest.testFileUrlAccessToggleDoesNotBlockAssetUrls
 -org.chromium.android_webview.test.AwSettingsTest.testFileUrlAccessToggleDoesNotBlockResourceUrls
--org.chromium.android_webview.test.AwSettingsTest.testFileUrlAccessWithTwoViews
 -org.chromium.android_webview.test.AwSettingsTest.testResourceUrl
 
 # https://crbug.com/893572
diff --git a/testing/buildbot/filters/webui_polymer2_browser_tests.filter b/testing/buildbot/filters/webui_polymer2_browser_tests.filter
index 1a0ed89..a4c74a67 100644
--- a/testing/buildbot/filters/webui_polymer2_browser_tests.filter
+++ b/testing/buildbot/filters/webui_polymer2_browser_tests.filter
@@ -40,6 +40,22 @@
 AudioPlayerBrowserTest.*
 AutofillBrowserTest.*
 BluetoothInternalsTest.*
+BookmarksActionsTest.*
+BookmarksAppTest.*
+BookmarksCommandManagerTest.*
+BookmarksDNDManagerTest.*
+BookmarksEditDialogTest.*
+BookmarksFocusTest.*
+BookmarksFolderNodeTest.*
+BookmarksItemTest.*
+BookmarksListTest.*
+BookmarksPolicyTest.*
+BookmarksReducersTest.*
+BookmarksRouterTest.*
+BookmarksStoreTest.*
+BookmarksToastManagerTest.*
+BookmarksToolbarTest.*
+BookmarksUtilTest.*
 CertificateViewerModalUITest.*
 CertificateViewerModalUITestAsync.*
 CertificateViewerUITest.*
@@ -210,22 +226,6 @@
 LoadTimeDataTest.*
 LocalAndDriveFileSystemExtensionApiTest.*
 LocalFileSystemExtensionApiTest.*
-MaterialBookmarksActionsTest.*
-MaterialBookmarksAppTest.*
-MaterialBookmarksCommandManagerTest.*
-MaterialBookmarksDNDManagerTest.*
-MaterialBookmarksEditDialogTest.*
-MaterialBookmarksFocusTest.*
-MaterialBookmarksFolderNodeTest.*
-MaterialBookmarksItemTest.*
-MaterialBookmarksListTest.*
-MaterialBookmarksPolicyTest.*
-MaterialBookmarksReducersTest.*
-MaterialBookmarksRouterTest.*
-MaterialBookmarksStoreTest.*
-MaterialBookmarksToastManagerTest.*
-MaterialBookmarksToolbarTest.*
-MaterialBookmarksUtilTest.*
 MaterialHistoryBrowserServiceTest.*
 MaterialHistoryDrawerTest.*
 MaterialHistoryFocusTest.*
diff --git a/testing/buildbot/filters/webui_polymer2_interactive_ui_tests.filter b/testing/buildbot/filters/webui_polymer2_interactive_ui_tests.filter
index b1325e6..61cf9be3 100644
--- a/testing/buildbot/filters/webui_polymer2_interactive_ui_tests.filter
+++ b/testing/buildbot/filters/webui_polymer2_interactive_ui_tests.filter
@@ -36,6 +36,7 @@
 #
 # There are overall very few WebUI interactive_ui_tests, so list them here
 # explicitly, instead of running all interactive_ui_tests.
+BookmarksFocusTest.All
 CrElementsActionMenuTest.All
 CrElementsCheckboxTest.All
 CrElementsInputTest.All
@@ -44,7 +45,6 @@
 CrSettingsAnimatedPagesTest.All
 CrSettingsFocusRowBehavior.FocusTest
 CrSettingsSyncPageTest.All
-MaterialBookmarksFocusTest.All
 MaterialHistoryFocusTest.All
 PrintPreviewDestinationDialogInteractiveTest.*
 PrintPreviewNumberSettingsSectionInteractiveTest.BlurResetsEmptyInput
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 596ced9..4d596c9 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -416,11 +416,11 @@
     "type": "console_test_launcher",
   },
   "castrunner_integration_tests": {
-    "label": "//webrunner:castrunner_integration_tests",
+    "label": "//fuchsia:castrunner_integration_tests",
     "type": "console_test_launcher",
   },
   "castrunner_unittests": {
-    "label": "//webrunner:castrunner_unittests",
+    "label": "//fuchsia:castrunner_unittests",
     "type": "console_test_launcher",
   },
   "cast_audio_backend_unittests": {
@@ -1109,7 +1109,7 @@
     "type": "fuzzer",
   },
   "http_service_tests": {
-    "label": "//webrunner/net_http:http_service_tests",
+    "label": "//fuchsia/net_http:http_service_tests",
     "type": "console_test_launcher",
   },
   "hunspell_fuzzer": {
@@ -2735,15 +2735,15 @@
     "type": "console_test_launcher",
   },
   "webrunner_browsertests": {
-    "label": "//webrunner:webrunner_browsertests",
+    "label": "//fuchsia:webrunner_browsertests",
     "type": "console_test_launcher",
   },
   "webrunner_smoketests": {
-    "label": "//webrunner:webrunner_smoketests",
+    "label": "//fuchsia:webrunner_smoketests",
     "type": "console_test_launcher",
   },
   "webrunner_unittests": {
-    "label": "//webrunner:webrunner_unittests",
+    "label": "//fuchsia:webrunner_unittests",
     "type": "console_test_launcher",
   },
   "webusb_descriptors_fuzzer": {
diff --git a/third_party/android_swipe_refresh/README.chromium b/third_party/android_swipe_refresh/README.chromium
index 3f23083..aa10d32 100644
--- a/third_party/android_swipe_refresh/README.chromium
+++ b/third_party/android_swipe_refresh/README.chromium
@@ -13,6 +13,8 @@
 
 CircleImageView
   * The package has been changed, and most ViewCompat dependencies removed.
+  * The class has been made visible outside of the package.
+  * Scalable outer circle can be drawn with a specified color.
 
 MaterialProgressDrawable
   * The package has been changed, and most ViewCompat dependencies removed.
diff --git a/third_party/android_swipe_refresh/java/src/org/chromium/third_party/android/swiperefresh/CircleImageView.java b/third_party/android_swipe_refresh/java/src/org/chromium/third_party/android/swiperefresh/CircleImageView.java
index c5611fe1..50db63cb 100644
--- a/third_party/android_swipe_refresh/java/src/org/chromium/third_party/android/swiperefresh/CircleImageView.java
+++ b/third_party/android_swipe_refresh/java/src/org/chromium/third_party/android/swiperefresh/CircleImageView.java
@@ -25,8 +25,10 @@
 import android.graphics.Shader;
 import android.graphics.drawable.ShapeDrawable;
 import android.graphics.drawable.shapes.OvalShape;
-import android.view.animation.Animation;
+import android.support.annotation.ColorInt;
+import android.support.annotation.ColorRes;
 import android.view.View;
+import android.view.animation.Animation;
 import android.widget.ImageView;
 
 /**
@@ -36,8 +38,7 @@
  *
  * @hide
  */
-class CircleImageView extends ImageView {
-
+public class CircleImageView extends ImageView {
     private static final int KEY_SHADOW_COLOR = 0x1E000000;
     private static final int FILL_SHADOW_COLOR = 0x3D000000;
     // PX
@@ -48,16 +49,27 @@
 
     private Animation.AnimationListener mListener;
     private int mShadowRadius;
+    private int mOuterRadius;
+    private @ColorInt int mOuterColor;
+    private float mDensity;
+    private int mViewDimension;
+
+    public CircleImageView(Context context, int color, final float radius) {
+        this(context, color, radius, 0.f, 0);
+    }
 
     @SuppressWarnings("deprecation")
-    public CircleImageView(Context context, int color, final float radius) {
+    public CircleImageView(Context context, @ColorRes int color, float radius, float maxRadius,
+            @ColorInt int outerColor) {
         super(context);
         final float density = getContext().getResources().getDisplayMetrics().density;
 
         mShadowRadius = (int) (density * SHADOW_RADIUS);
+        mViewDimension = (int) (maxRadius > 0.f ? (maxRadius + radius) * density : mShadowRadius);
+        mOuterColor = outerColor;
 
         ShapeDrawable circle;
-        if (elevationSupported()) {
+        if (elevationSupported() && maxRadius == 0.f) {
             circle = initializeElevated(density);
         } else {
             circle = initializeNonElevated(radius, density);
@@ -68,6 +80,7 @@
         } else {
             setBackground(circle);
         }
+        mDensity = density;
     }
 
     ShapeDrawable initializeElevated(float density) {
@@ -85,7 +98,8 @@
         setLayerType(View.LAYER_TYPE_SOFTWARE, circle.getPaint());
         circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,
                 KEY_SHADOW_COLOR);
-        final int padding = mShadowRadius;
+
+        final int padding = mViewDimension;
         // set padding so the inner image sits correctly within the shadow.
         setPadding(padding, padding, padding, padding);
         return circle;
@@ -98,9 +112,9 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        if (!elevationSupported()) {
-            setMeasuredDimension(getMeasuredWidth() + mShadowRadius*2, getMeasuredHeight()
-                    + mShadowRadius*2);
+        if (!elevationSupported() || mViewDimension > mShadowRadius) {
+            setMeasuredDimension(getMeasuredWidth() + mViewDimension * 2,
+                    getMeasuredHeight() + mViewDimension * 2);
         }
     }
 
@@ -140,10 +154,15 @@
         }
     }
 
+    public void setProgress(float progress) {
+        mOuterRadius = (int) (progress * mViewDimension);
+    }
+
     private class OvalShadow extends OvalShape {
         private RadialGradient mRadialGradient;
         private int mShadowRadius;
         private Paint mShadowPaint;
+        private Paint mOuterPaint;
         private int mCircleDiameter;
 
         public OvalShadow(int shadowRadius, int circleDiameter) {
@@ -164,6 +183,14 @@
             final int viewHeight = CircleImageView.this.getHeight();
             canvas.drawCircle(viewWidth / 2f, viewHeight / 2f, (mCircleDiameter / 2f + mShadowRadius),
                     mShadowPaint);
+            if (mOuterRadius > 0.f) {
+                if (mOuterPaint == null) {
+                    mOuterPaint = new Paint();
+                    mOuterPaint.setColor(getResources().getColor(mOuterColor));
+                    mOuterPaint.setAlpha(0x80);
+                }
+                canvas.drawCircle(viewWidth / 2f, viewHeight / 2f, mOuterRadius, mOuterPaint);
+            }
             canvas.drawCircle(viewWidth / 2f, viewHeight / 2f, (mCircleDiameter / 2f), paint);
         }
     }
diff --git a/third_party/blink/public/platform/web_feature.mojom b/third_party/blink/public/platform/web_feature.mojom
index 146d8ea..d8e0456 100644
--- a/third_party/blink/public/platform/web_feature.mojom
+++ b/third_party/blink/public/platform/web_feature.mojom
@@ -2170,6 +2170,7 @@
   kV8HTMLMediaElement_CaptureStream_Method = 2729,
   kQuirkyLineBoxBackgroundSize = 2730,
   kDirectlyCompositedImage = 2731,
+  kForbiddenSyncXhrInPageDismissal = 2732,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/platform/web_resource_timing_info.h b/third_party/blink/public/platform/web_resource_timing_info.h
index 835372cb..65e7f29 100644
--- a/third_party/blink/public/platform/web_resource_timing_info.h
+++ b/third_party/blink/public/platform/web_resource_timing_info.h
@@ -53,6 +53,7 @@
   uint64_t decoded_body_size;
 
   bool did_reuse_connection;
+  bool is_secure_context;
 
   // TODO(dcheng): The way this code works is fairly confusing: it might seem
   // unusual to store policy members like |allow_timing_details| inline, rather
diff --git a/third_party/blink/public/platform/web_runtime_features.h b/third_party/blink/public/platform/web_runtime_features.h
index 5f95096..cc9ad06 100644
--- a/third_party/blink/public/platform/web_runtime_features.h
+++ b/third_party/blink/public/platform/web_runtime_features.h
@@ -165,6 +165,8 @@
   BLINK_PLATFORM_EXPORT static void EnableSharedArrayBuffer(bool);
   BLINK_PLATFORM_EXPORT static void EnableSharedWorker(bool);
   BLINK_PLATFORM_EXPORT static void EnableTouchEventFeatureDetection(bool);
+  BLINK_PLATFORM_EXPORT static void EnableUserActivationSameOriginVisibility(
+      bool);
   BLINK_PLATFORM_EXPORT static void EnableUserActivationV2(bool);
   BLINK_PLATFORM_EXPORT static void EnableV8IdleTasks(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebAuth(bool);
diff --git a/third_party/blink/public/platform/web_video_frame_submitter.h b/third_party/blink/public/platform/web_video_frame_submitter.h
index 4a9b5a74..1678f1a 100644
--- a/third_party/blink/public/platform/web_video_frame_submitter.h
+++ b/third_party/blink/public/platform/web_video_frame_submitter.h
@@ -50,11 +50,11 @@
   virtual void Initialize(cc::VideoFrameProvider*) = 0;
 
   // Set the rotation state of the video to be used while appending frames.
+  //
+  // TODO(dalecurtis): This could be removed in favor of getting it from each
+  // VideoFrame, but today that information isn't set everywhere.
   virtual void SetRotation(media::VideoRotation) = 0;
 
-  // Set if the video is opaque or not.
-  virtual void SetIsOpaque(bool) = 0;
-
   // Prepares the compositor frame sink to accept frames by providing
   // a SurfaceId, with its associated allocation time. The callback is to be
   // used when on context loss to prevent the submitter from continuing to
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 2a1ca29..7bced7dd 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1953,7 +1953,6 @@
     "html/image_document_test.cc",
     "html/imports/html_import_sheets_test.cc",
     "html/lazy_load_frame_observer_test.cc",
-    "html/lazy_load_image_observer_test.cc",
     "html/link_element_loading_test.cc",
     "html/link_rel_attribute_test.cc",
     "html/list_item_ordinal_test.cc",
diff --git a/third_party/blink/renderer/core/clipboard/system_clipboard.cc b/third_party/blink/renderer/core/clipboard/system_clipboard.cc
index f696ad8..a889c15 100644
--- a/third_party/blink/renderer/core/clipboard/system_clipboard.cc
+++ b/third_party/blink/renderer/core/clipboard/system_clipboard.cc
@@ -169,7 +169,7 @@
   SkBitmap bitmap;
   if (sk_sp<SkImage> sk_image = paint_image.GetSkImage())
     sk_image->asLegacyBitmap(&bitmap);
-  WriteImageNoCommit(bitmap);
+  clipboard_->WriteImage(mojom::ClipboardBuffer::kStandard, bitmap);
 
   if (url.IsValid() && !url.IsEmpty()) {
 #if !defined(OS_MACOSX)
@@ -192,8 +192,7 @@
 }
 
 void SystemClipboard::WriteImage(const SkBitmap& bitmap) {
-  WriteImageNoCommit(bitmap);
-
+  clipboard_->WriteImage(mojom::ClipboardBuffer::kStandard, bitmap);
   clipboard_->CommitWrite(mojom::ClipboardBuffer::kStandard);
 }
 
@@ -256,13 +255,4 @@
   return true;
 }
 
-void SystemClipboard::WriteImageNoCommit(const SkBitmap& bitmap) {
-  if (bitmap.isNull())
-    return;
-  // TODO(crbug.com/918717): Remove CHECK if no crashes occur on it in canary.
-  CHECK(bitmap.getPixels());
-
-  clipboard_->WriteImage(mojom::ClipboardBuffer::kStandard, bitmap);
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/clipboard/system_clipboard.h b/third_party/blink/renderer/core/clipboard/system_clipboard.h
index e266a08..ca65a4414 100644
--- a/third_party/blink/renderer/core/clipboard/system_clipboard.h
+++ b/third_party/blink/renderer/core/clipboard/system_clipboard.h
@@ -61,7 +61,6 @@
  private:
   SystemClipboard();
   bool IsValidBufferType(mojom::ClipboardBuffer);
-  void WriteImageNoCommit(const SkBitmap&);
 
   mojom::blink::ClipboardHostPtr clipboard_;
   mojom::ClipboardBuffer buffer_ = mojom::ClipboardBuffer::kStandard;
diff --git a/third_party/blink/renderer/core/css/css_image_value.cc b/third_party/blink/renderer/core/css/css_image_value.cc
index 7627124ae..b8521faa 100644
--- a/third_party/blink/renderer/core/css/css_image_value.cc
+++ b/third_party/blink/renderer/core/css/css_image_value.cc
@@ -78,16 +78,13 @@
         document.GetFrame()->IsClientLoFiAllowed(params.GetResourceRequest())) {
       params.SetClientLoFiPlaceholder();
     }
-    bool is_lazily_loaded =
-        image_request_optimization == FetchParameters::kDeferImageLoad &&
+    cached_image_ = StyleFetchedImage::Create(
+        document, params,
         // Only http/https images are eligible to be lazily loaded.
-        params.Url().ProtocolIsInHTTPFamily();
-    if (is_lazily_loaded)
-      params.SetLazyImageDeferred();
-
-    cached_image_ =
-        StyleFetchedImage::Create(document, params, is_lazily_loaded);
+        params.Url().ProtocolIsInHTTPFamily() &&
+            image_request_optimization == FetchParameters::kDeferImageLoad);
   }
+
   return cached_image_.Get();
 }
 
diff --git a/third_party/blink/renderer/core/exported/web_remote_frame_impl.cc b/third_party/blink/renderer/core/exported/web_remote_frame_impl.cc
index fee07e3..6386fc5 100644
--- a/third_party/blink/renderer/core/exported/web_remote_frame_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_remote_frame_impl.cc
@@ -301,8 +301,7 @@
       ToHTMLFrameOwnerElement(frame_->Owner());
   DCHECK(owner_element);
   DOMWindowPerformance::performance(*parent_frame->GetFrame()->DomWindow())
-      ->AddResourceTiming(info, owner_element->localName(),
-                          owner_element->GetDocument().IsSecureContext());
+      ->AddResourceTiming(info, owner_element->localName());
 }
 
 void WebRemoteFrameImpl::DispatchLoadEventForFrameOwner() {
diff --git a/third_party/blink/renderer/core/exported/web_shared_worker_impl.cc b/third_party/blink/renderer/core/exported/web_shared_worker_impl.cc
index cb192069..94973cfa 100644
--- a/third_party/blink/renderer/core/exported/web_shared_worker_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_shared_worker_impl.cc
@@ -320,8 +320,11 @@
 
   auto global_scope_creation_params =
       std::make_unique<GlobalScopeCreationParams>(
-          script_response_url, script_type, document->UserAgent(),
-          std::move(web_worker_fetch_context),
+          script_response_url, script_type,
+          // TODO(nhiroki): Implement off-the-main-thread worker script fetch
+          // for shared workers (https://crbug.com/835717).
+          OffMainThreadWorkerScriptFetchOption::kDisabled,
+          document->UserAgent(), std::move(web_worker_fetch_context),
           content_security_policy ? content_security_policy->Headers()
                                   : Vector<CSPHeaderAndType>(),
           referrer_policy, outside_settings_object->GetSecurityOrigin(),
diff --git a/third_party/blink/renderer/core/frame/frame.cc b/third_party/blink/renderer/core/frame/frame.cc
index 3739269..ec10f62c 100644
--- a/third_party/blink/renderer/core/frame/frame.cc
+++ b/third_party/blink/renderer/core/frame/frame.cc
@@ -188,6 +188,22 @@
 void Frame::NotifyUserActivationInLocalTree() {
   for (Frame* node = this; node; node = node->Tree().Parent())
     node->user_activation_state_.Activate();
+
+  // See FrameTreeNode::NotifyUserActivation() for details about this block.
+  if (IsLocalFrame() && RuntimeEnabledFeatures::UserActivationV2Enabled() &&
+      RuntimeEnabledFeatures::UserActivationSameOriginVisibilityEnabled()) {
+    const SecurityOrigin* security_origin =
+        ToLocalFrame(this)->GetSecurityContext()->GetSecurityOrigin();
+
+    Frame& root = Tree().Top();
+    for (Frame* node = &root; node; node = node->Tree().TraverseNext(&root)) {
+      if (node->IsLocalFrame() &&
+          security_origin->CanAccess(
+              ToLocalFrame(node)->GetSecurityContext()->GetSecurityOrigin())) {
+        node->user_activation_state_.Activate();
+      }
+    }
+  }
 }
 
 bool Frame::ConsumeTransientUserActivationInLocalTree() {
diff --git a/third_party/blink/renderer/core/frame/reporting_context.cc b/third_party/blink/renderer/core/frame/reporting_context.cc
index 7692ab2..e749011 100644
--- a/third_party/blink/renderer/core/frame/reporting_context.cc
+++ b/third_party/blink/renderer/core/frame/reporting_context.cc
@@ -33,12 +33,16 @@
 
 void ReportingContext::QueueReport(Report* report) {
   CountReport(report);
-  report_buffer_.insert(report);
 
-  // Only the most recent 100 reports will remain buffered.
+  // Buffer the report.
+  if (!report_buffer_.Contains(report->type()))
+    report_buffer_.insert(report->type(), HeapListHashSet<Member<Report>>());
+  report_buffer_.find(report->type())->value.insert(report);
+
+  // Only the most recent 100 reports will remain buffered, per report type.
   // https://w3c.github.io/reporting/#notify-observers
-  if (report_buffer_.size() > 100)
-    report_buffer_.RemoveFirst();
+  if (report_buffer_.at(report->type()).size() > 100)
+    report_buffer_.find(report->type())->value.RemoveFirst();
 
   for (auto observer : observers_)
     observer->QueueReport(report);
@@ -69,8 +73,11 @@
     return;
 
   observer->ClearBuffered();
-  for (auto report : report_buffer_)
-    observer->QueueReport(report);
+  for (auto type : report_buffer_) {
+    for (Report* report : type.value) {
+      observer->QueueReport(report);
+    }
+  }
 }
 
 void ReportingContext::UnregisterObserver(ReportingObserver* observer) {
diff --git a/third_party/blink/renderer/core/frame/reporting_context.h b/third_party/blink/renderer/core/frame/reporting_context.h
index 9332439b..4b86d99 100644
--- a/third_party/blink/renderer/core/frame/reporting_context.h
+++ b/third_party/blink/renderer/core/frame/reporting_context.h
@@ -8,6 +8,7 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
 
@@ -46,7 +47,7 @@
 
  private:
   HeapListHashSet<Member<ReportingObserver>> observers_;
-  HeapListHashSet<Member<Report>> report_buffer_;
+  HeapHashMap<String, HeapListHashSet<Member<Report>>> report_buffer_;
   Member<ExecutionContext> execution_context_;
 };
 
diff --git a/third_party/blink/renderer/core/html/lazy_load_image_observer_test.cc b/third_party/blink/renderer/core/html/lazy_load_image_observer_test.cc
deleted file mode 100644
index 887dae0..0000000
--- a/third_party/blink/renderer/core/html/lazy_load_image_observer_test.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/core/html/lazy_load_image_observer.h"
-
-#include "third_party/blink/renderer/core/dom/node_computed_style.h"
-#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_element.h"
-#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
-#include "third_party/blink/renderer/core/style/computed_style.h"
-#include "third_party/blink/renderer/core/style/style_image.h"
-#include "third_party/blink/renderer/core/testing/sim/sim_compositor.h"
-#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
-#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
-#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
-#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
-
-namespace blink {
-
-namespace {
-
-class LazyLoadImagesSimTest : public SimTest {
- protected:
-  LazyLoadImagesSimTest() : scoped_lazy_image_loading_for_test_(true) {}
-  void SetLazyLoadEnabled(bool enabled) {
-    WebView().GetPage()->GetSettings().SetLazyLoadEnabled(enabled);
-  }
-
-  void LoadMainResource() {
-    SimRequest main_resource("https://example.com/", "text/html");
-    LoadURL("https://example.com/");
-
-    main_resource.Complete(String::Format(R"HTML(
-          <style>
-          #deferred_image {
-            height:200px;
-            background-image: url('img.jpg');
-          }
-          </style>
-          <div style='height:10000px;'></div>
-          <div id="deferred_image"></div>
-        )HTML"));
-  }
-
-  void ExpectCSSBackgroundImageDeferredState(bool deferred) {
-    const ComputedStyle* deferred_image_style =
-        GetDocument().getElementById("deferred_image")->GetComputedStyle();
-    EXPECT_TRUE(deferred_image_style->HasBackgroundImage());
-    bool is_background_image_found = false;
-    for (const FillLayer* background_layer =
-             &deferred_image_style->BackgroundLayers();
-         background_layer; background_layer = background_layer->Next()) {
-      if (StyleImage* deferred_image = background_layer->GetImage()) {
-        EXPECT_TRUE(deferred_image->IsImageResource());
-        EXPECT_EQ(deferred, deferred_image->IsLazyloadPossiblyDeferred());
-        EXPECT_NE(deferred, deferred_image->IsLoaded());
-        is_background_image_found = true;
-      }
-    }
-    EXPECT_TRUE(is_background_image_found);
-  }
-
- private:
-  ScopedLazyImageLoadingForTest scoped_lazy_image_loading_for_test_;
-};
-
-TEST_F(LazyLoadImagesSimTest, CSSBackgroundImageLoadedWithoutLazyLoad) {
-  SetLazyLoadEnabled(false);
-  SimRequest image_resource("https://example.com/img.jpg", "image/jpeg");
-  LoadMainResource();
-  image_resource.Complete("");
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-  ExpectCSSBackgroundImageDeferredState(false);
-}
-
-TEST_F(LazyLoadImagesSimTest, CSSBackgroundImageDeferredWithLazyLoad) {
-  SetLazyLoadEnabled(true);
-  LoadMainResource();
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-
-  ExpectCSSBackgroundImageDeferredState(true);
-
-  // Scroll down until the background image is visible.
-  GetDocument().View()->LayoutViewport()->SetScrollOffset(
-      ScrollOffset(0, 10000), kProgrammaticScroll);
-  SimRequest image_resource("https://example.com/img.jpg", "image/jpeg");
-  Compositor().BeginFrame();
-  test::RunPendingTasks();
-  image_resource.Complete("");
-  ExpectCSSBackgroundImageDeferredState(false);
-}
-
-}  // namespace
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/custom/layout_worklet_global_scope_proxy.cc b/third_party/blink/renderer/core/layout/custom/layout_worklet_global_scope_proxy.cc
index 89a5b15d..bd07119b 100644
--- a/third_party/blink/renderer/core/layout/custom/layout_worklet_global_scope_proxy.cc
+++ b/third_party/blink/renderer/core/layout/custom/layout_worklet_global_scope_proxy.cc
@@ -41,7 +41,8 @@
       worker_clients, frame->Client()->CreateWorkerContentSettingsClient());
 
   auto creation_params = std::make_unique<GlobalScopeCreationParams>(
-      document->Url(), mojom::ScriptType::kModule, document->UserAgent(),
+      document->Url(), mojom::ScriptType::kModule,
+      OffMainThreadWorkerScriptFetchOption::kEnabled, document->UserAgent(),
       frame->Client()->CreateWorkerFetchContext(),
       document->GetContentSecurityPolicy()->Headers(),
       document->GetReferrerPolicy(), document->GetSecurityOrigin(),
diff --git a/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc b/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc
index b163374..2830a2a 100644
--- a/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc
+++ b/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc
@@ -183,7 +183,8 @@
       ResourceFetcherInit(*properties, fetch_context));
   reporting_proxy_ = std::make_unique<MockWorkerReportingProxy>();
   auto creation_params = std::make_unique<GlobalScopeCreationParams>(
-      url_, mojom::ScriptType::kModule, "UserAgent",
+      url_, mojom::ScriptType::kModule,
+      OffMainThreadWorkerScriptFetchOption::kEnabled, "UserAgent",
       nullptr /* web_worker_fetch_context */, Vector<CSPHeaderAndType>(),
       network::mojom::ReferrerPolicy::kDefault, security_origin_.get(),
       true /* is_secure_context */, HttpsState::kModern,
diff --git a/third_party/blink/renderer/core/paint/box_paint_invalidator.cc b/third_party/blink/renderer/core/paint/box_paint_invalidator.cc
index 1b965c0..af885a2f 100644
--- a/third_party/blink/renderer/core/paint/box_paint_invalidator.cc
+++ b/third_party/blink/renderer/core/paint/box_paint_invalidator.cc
@@ -244,13 +244,8 @@
       new_background_rect.Size() != old_background_rect.Size();
   if (background_location_changed || background_size_changed) {
     for (auto* object :
-         layout_view.GetFrameView()->BackgroundAttachmentFixedObjects()) {
-      if (background_location_changed ||
-          ShouldFullyInvalidateFillLayersOnSizeChange(
-              object->StyleRef().BackgroundLayers(), old_background_rect.Size(),
-              new_background_rect.Size()))
-        object->SetBackgroundNeedsFullPaintInvalidation();
-    }
+         layout_view.GetFrameView()->BackgroundAttachmentFixedObjects())
+      object->SetBackgroundNeedsFullPaintInvalidation();
   }
 
   if (background_location_changed ||
diff --git a/third_party/blink/renderer/core/timing/performance.cc b/third_party/blink/renderer/core/timing/performance.cc
index 547eac2..2d6a700 100644
--- a/third_party/blink/renderer/core/timing/performance.cc
+++ b/third_party/blink/renderer/core/timing/performance.cc
@@ -480,8 +480,7 @@
     return;
   AddResourceTiming(
       GenerateResourceTiming(*security_origin, info, *context),
-      !initiator_type.IsNull() ? initiator_type : info.InitiatorType(),
-      context->IsSecureContext());
+      !initiator_type.IsNull() ? initiator_type : info.InitiatorType());
 }
 
 WebResourceTimingInfo Performance::GenerateResourceTiming(
@@ -537,6 +536,8 @@
   result.encoded_body_size = final_response.EncodedBodyLength();
   result.decoded_body_size = final_response.DecodedBodyLength();
   result.did_reuse_connection = final_response.ConnectionReused();
+  result.is_secure_context =
+      SecurityOrigin::IsSecure(final_response.ResponseUrl());
   result.allow_negative_values = info.NegativeAllowed();
 
   if (result.allow_timing_details) {
@@ -551,10 +552,9 @@
 }
 
 void Performance::AddResourceTiming(const WebResourceTimingInfo& info,
-                                    const AtomicString& initiator_type,
-                                    bool is_secure_context) {
-  PerformanceEntry* entry = PerformanceResourceTiming::Create(
-      info, time_origin_, initiator_type, is_secure_context);
+                                    const AtomicString& initiator_type) {
+  PerformanceEntry* entry =
+      PerformanceResourceTiming::Create(info, time_origin_, initiator_type);
   NotifyObserversOfEntry(*entry);
   // https://w3c.github.io/resource-timing/#dfn-add-a-performanceresourcetiming-entry
   if (CanAddResourceTimingEntry() &&
diff --git a/third_party/blink/renderer/core/timing/performance.h b/third_party/blink/renderer/core/timing/performance.h
index ab0e171..40f15693 100644
--- a/third_party/blink/renderer/core/timing/performance.h
+++ b/third_party/blink/renderer/core/timing/performance.h
@@ -149,8 +149,7 @@
       const ResourceTimingInfo&,
       ExecutionContext& context_for_use_counter);
   void AddResourceTiming(const WebResourceTimingInfo&,
-                         const AtomicString& initiator_type,
-                         bool is_secure_context);
+                         const AtomicString& initiator_type);
 
   void NotifyNavigationTimingToObservers();
 
diff --git a/third_party/blink/renderer/core/timing/performance_resource_timing.cc b/third_party/blink/renderer/core/timing/performance_resource_timing.cc
index 5bd7d15..bb8cb0d3 100644
--- a/third_party/blink/renderer/core/timing/performance_resource_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_resource_timing.cc
@@ -45,8 +45,7 @@
 PerformanceResourceTiming::PerformanceResourceTiming(
     const WebResourceTimingInfo& info,
     TimeTicks time_origin,
-    const AtomicString& initiator_type,
-    bool is_secure_context)
+    const AtomicString& initiator_type)
     : PerformanceEntry(info.name,
                        Performance::MonotonicTimeToDOMHighResTimeStamp(
                            time_origin,
@@ -73,7 +72,7 @@
       allow_timing_details_(info.allow_timing_details),
       allow_redirect_details_(info.allow_redirect_details),
       allow_negative_value_(info.allow_negative_values),
-      is_secure_context_(is_secure_context),
+      is_secure_context_(info.is_secure_context),
       server_timing_(
           PerformanceServerTiming::FromParsedServerTiming(info.server_timing)) {
 }
diff --git a/third_party/blink/renderer/core/timing/performance_resource_timing.h b/third_party/blink/renderer/core/timing/performance_resource_timing.h
index 21405a4..6057885 100644
--- a/third_party/blink/renderer/core/timing/performance_resource_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_resource_timing.h
@@ -56,15 +56,13 @@
                             const WebVector<WebServerTimingInfo>&);
   PerformanceResourceTiming(const WebResourceTimingInfo&,
                             TimeTicks time_origin,
-                            const AtomicString& initiator_type,
-                            bool is_secure_context);
+                            const AtomicString& initiator_type);
   ~PerformanceResourceTiming() override;
   static PerformanceResourceTiming* Create(const WebResourceTimingInfo& info,
                                            TimeTicks time_origin,
-                                           const AtomicString& initiator_type,
-                                           bool is_secure_context) {
-    return MakeGarbageCollected<PerformanceResourceTiming>(
-        info, time_origin, initiator_type, is_secure_context);
+                                           const AtomicString& initiator_type) {
+    return MakeGarbageCollected<PerformanceResourceTiming>(info, time_origin,
+                                                           initiator_type);
   }
 
   AtomicString entryType() const override;
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker.cc b/third_party/blink/renderer/core/workers/dedicated_worker.cc
index e05d63b7..3447a81 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker.cc
+++ b/third_party/blink/renderer/core/workers/dedicated_worker.cc
@@ -225,7 +225,8 @@
                  .GetFetchClientSettingsObject());
     context_proxy_->StartWorkerGlobalScope(
         CreateGlobalScopeCreationParams(
-            script_request_url_, network::mojom::ReferrerPolicy::kDefault),
+            script_request_url_, OffMainThreadWorkerScriptFetchOption::kEnabled,
+            network::mojom::ReferrerPolicy::kDefault),
         options_, script_request_url_, outside_settings_object, stack_id,
         String() /* source_code */);
     return;
@@ -358,7 +359,9 @@
                  ->Context()
                  .GetFetchClientSettingsObject());
     context_proxy_->StartWorkerGlobalScope(
-        CreateGlobalScopeCreationParams(script_response_url, referrer_policy),
+        CreateGlobalScopeCreationParams(
+            script_response_url,
+            OffMainThreadWorkerScriptFetchOption::kDisabled, referrer_policy),
         options_, script_response_url, outside_settings_object, stack_id,
         classic_script_loader_->SourceText());
     probe::scriptImported(GetExecutionContext(),
@@ -371,6 +374,7 @@
 std::unique_ptr<GlobalScopeCreationParams>
 DedicatedWorker::CreateGlobalScopeCreationParams(
     const KURL& script_url,
+    OffMainThreadWorkerScriptFetchOption off_main_thread_fetch_option,
     network::mojom::ReferrerPolicy referrer_policy) {
   base::UnguessableToken parent_devtools_token;
   std::unique_ptr<WorkerSettings> settings;
@@ -409,8 +413,8 @@
   }
 
   return std::make_unique<GlobalScopeCreationParams>(
-      script_url, script_type, GetExecutionContext()->UserAgent(),
-      std::move(web_worker_fetch_context),
+      script_url, script_type, off_main_thread_fetch_option,
+      GetExecutionContext()->UserAgent(), std::move(web_worker_fetch_context),
       GetExecutionContext()->GetContentSecurityPolicy()->Headers(),
       referrer_policy, GetExecutionContext()->GetSecurityOrigin(),
       GetExecutionContext()->IsSecureContext(),
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker.h b/third_party/blink/renderer/core/workers/dedicated_worker.h
index 80e7073..0a17083 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker.h
+++ b/third_party/blink/renderer/core/workers/dedicated_worker.h
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
 #include "third_party/blink/renderer/core/messaging/message_port.h"
 #include "third_party/blink/renderer/core/workers/abstract_worker.h"
+#include "third_party/blink/renderer/core/workers/global_scope_creation_params.h"
 #include "third_party/blink/renderer/core/workers/worker_options.h"
 #include "third_party/blink/renderer/platform/graphics/begin_frame_provider.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
@@ -30,7 +31,6 @@
 class ScriptState;
 class WorkerClassicScriptLoader;
 class WorkerClients;
-struct GlobalScopeCreationParams;
 
 // Implementation of the Worker interface defined in the WebWorker HTML spec:
 // https://html.spec.whatwg.org/multipage/workers.html#worker
@@ -90,6 +90,7 @@
 
   std::unique_ptr<GlobalScopeCreationParams> CreateGlobalScopeCreationParams(
       const KURL& script_url,
+      OffMainThreadWorkerScriptFetchOption,
       network::mojom::ReferrerPolicy);
 
   WorkerClients* CreateWorkerClients();
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker_messaging_proxy.cc b/third_party/blink/renderer/core/workers/dedicated_worker_messaging_proxy.cc
index 8a78aa6c..0663b31f 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker_messaging_proxy.cc
+++ b/third_party/blink/renderer/core/workers/dedicated_worker_messaging_proxy.cc
@@ -51,6 +51,9 @@
     return;
   }
 
+  OffMainThreadWorkerScriptFetchOption off_main_thread_fetch_option =
+      creation_params->off_main_thread_fetch_option;
+
   InitializeWorkerThread(
       std::move(creation_params),
       CreateBackingThreadStartupData(GetExecutionContext()->GetIsolate()));
@@ -60,15 +63,20 @@
   if (options->type() == "classic") {
     // "classic: Fetch a classic worker script given url, outside settings,
     // destination, and inside settings."
-    if (RuntimeEnabledFeatures::OffMainThreadWorkerScriptFetchEnabled()) {
-      GetWorkerThread()->ImportClassicScript(script_url,
-                                             outside_settings_object, stack_id);
-    } else {
-      // Legacy code path (to be deprecated, see https://crbug.com/835717):
-      GetWorkerThread()->EvaluateClassicScript(
-          script_url, source_code, nullptr /* cached_meta_data */, stack_id);
+    switch (off_main_thread_fetch_option) {
+      case OffMainThreadWorkerScriptFetchOption::kEnabled:
+        GetWorkerThread()->ImportClassicScript(
+            script_url, outside_settings_object, stack_id);
+        break;
+      case OffMainThreadWorkerScriptFetchOption::kDisabled:
+        // Legacy code path (to be deprecated, see https://crbug.com/835717):
+        GetWorkerThread()->EvaluateClassicScript(
+            script_url, source_code, nullptr /* cached_meta_data */, stack_id);
+        break;
     }
   } else if (options->type() == "module") {
+    DCHECK_EQ(off_main_thread_fetch_option,
+              OffMainThreadWorkerScriptFetchOption::kEnabled);
     // "module: Fetch a module worker script graph given url, outside settings,
     // destination, the value of the credentials member of options, and inside
     // settings."
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker_test.cc b/third_party/blink/renderer/core/workers/dedicated_worker_test.cc
index ab02737e..6ee8ad3 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker_test.cc
+++ b/third_party/blink/renderer/core/workers/dedicated_worker_test.cc
@@ -134,7 +134,8 @@
         To<Document>(GetExecutionContext())->GetSettings());
     InitializeWorkerThread(
         std::make_unique<GlobalScopeCreationParams>(
-            script_url, mojom::ScriptType::kClassic, "fake user agent",
+            script_url, mojom::ScriptType::kClassic,
+            OffMainThreadWorkerScriptFetchOption::kDisabled, "fake user agent",
             nullptr /* web_worker_fetch_context */, headers,
             network::mojom::ReferrerPolicy::kDefault, security_origin_.get(),
             false /* starter_secure_context */,
diff --git a/third_party/blink/renderer/core/workers/experimental/thread_pool.cc b/third_party/blink/renderer/core/workers/experimental/thread_pool.cc
index 0a93e758..93e729cb 100644
--- a/third_party/blink/renderer/core/workers/experimental/thread_pool.cc
+++ b/third_party/blink/renderer/core/workers/experimental/thread_pool.cc
@@ -112,7 +112,8 @@
   // WebWorkerFetchContext is provided later in
   // ThreadedMessagingProxyBase::InitializeWorkerThread().
   proxy->StartWorker(std::make_unique<GlobalScopeCreationParams>(
-      context->Url(), mojom::ScriptType::kClassic, context->UserAgent(),
+      context->Url(), mojom::ScriptType::kClassic,
+      OffMainThreadWorkerScriptFetchOption::kDisabled, context->UserAgent(),
       nullptr /* web_worker_fetch_context */,
       context->GetContentSecurityPolicy()->Headers(),
       network::mojom::ReferrerPolicy::kDefault, context->GetSecurityOrigin(),
diff --git a/third_party/blink/renderer/core/workers/global_scope_creation_params.cc b/third_party/blink/renderer/core/workers/global_scope_creation_params.cc
index 51b2c32b..0e51209 100644
--- a/third_party/blink/renderer/core/workers/global_scope_creation_params.cc
+++ b/third_party/blink/renderer/core/workers/global_scope_creation_params.cc
@@ -6,12 +6,14 @@
 
 #include <memory>
 #include "third_party/blink/renderer/platform/network/content_security_policy_parsers.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
 namespace blink {
 
 GlobalScopeCreationParams::GlobalScopeCreationParams(
     const KURL& script_url,
     mojom::ScriptType script_type,
+    OffMainThreadWorkerScriptFetchOption off_main_thread_fetch_option,
     const String& user_agent,
     scoped_refptr<WebWorkerFetchContext> web_worker_fetch_context,
     const Vector<CSPHeaderAndType>& content_security_policy_parsed_headers,
@@ -33,6 +35,7 @@
     base::UnguessableToken agent_cluster_id)
     : script_url(script_url.Copy()),
       script_type(script_type),
+      off_main_thread_fetch_option(off_main_thread_fetch_option),
       user_agent(user_agent.IsolatedCopy()),
       web_worker_fetch_context(std::move(web_worker_fetch_context)),
       referrer_policy(referrer_policy),
@@ -54,6 +57,19 @@
           ParsedFeaturePolicy() /* container_policy */,
           starter_origin->ToUrlOrigin())),
       agent_cluster_id(agent_cluster_id) {
+  switch (this->script_type) {
+    case mojom::ScriptType::kClassic:
+      if (this->off_main_thread_fetch_option ==
+          OffMainThreadWorkerScriptFetchOption::kEnabled) {
+        DCHECK(RuntimeEnabledFeatures::OffMainThreadWorkerScriptFetchEnabled());
+      }
+      break;
+    case mojom::ScriptType::kModule:
+      DCHECK_EQ(this->off_main_thread_fetch_option,
+                OffMainThreadWorkerScriptFetchOption::kEnabled);
+      break;
+  }
+
   this->content_security_policy_parsed_headers.ReserveInitialCapacity(
       content_security_policy_parsed_headers.size());
   for (const auto& header : content_security_policy_parsed_headers) {
diff --git a/third_party/blink/renderer/core/workers/global_scope_creation_params.h b/third_party/blink/renderer/core/workers/global_scope_creation_params.h
index 395e3bf..af7ed99 100644
--- a/third_party/blink/renderer/core/workers/global_scope_creation_params.h
+++ b/third_party/blink/renderer/core/workers/global_scope_creation_params.h
@@ -29,6 +29,10 @@
 
 class WorkerClients;
 
+// TODO(nhiroki): Remove this option after off-the-main-thread worker script
+// fetch is enabled for all worker types (https://crbug.com/835717).
+enum class OffMainThreadWorkerScriptFetchOption { kDisabled, kEnabled };
+
 // GlobalScopeCreationParams contains parameters for initializing
 // WorkerGlobalScope or WorkletGlobalScope.
 struct CORE_EXPORT GlobalScopeCreationParams final {
@@ -38,6 +42,7 @@
   GlobalScopeCreationParams(
       const KURL& script_url,
       mojom::ScriptType script_type,
+      OffMainThreadWorkerScriptFetchOption,
       const String& user_agent,
       scoped_refptr<WebWorkerFetchContext>,
       const Vector<CSPHeaderAndType>& content_security_policy_parsed_headers,
@@ -74,6 +79,8 @@
   KURL script_url;
 
   mojom::ScriptType script_type;
+  OffMainThreadWorkerScriptFetchOption off_main_thread_fetch_option;
+
   String user_agent;
 
   scoped_refptr<WebWorkerFetchContext> web_worker_fetch_context;
diff --git a/third_party/blink/renderer/core/workers/main_thread_worklet_test.cc b/third_party/blink/renderer/core/workers/main_thread_worklet_test.cc
index d07a313..70fc818 100644
--- a/third_party/blink/renderer/core/workers/main_thread_worklet_test.cc
+++ b/third_party/blink/renderer/core/workers/main_thread_worklet_test.cc
@@ -64,7 +64,8 @@
     reporting_proxy_ =
         std::make_unique<MainThreadWorkletReportingProxyForTest>(document);
     auto creation_params = std::make_unique<GlobalScopeCreationParams>(
-        document->Url(), mojom::ScriptType::kModule, document->UserAgent(),
+        document->Url(), mojom::ScriptType::kModule,
+        OffMainThreadWorkerScriptFetchOption::kEnabled, document->UserAgent(),
         nullptr /* web_worker_fetch_context */,
         document->GetContentSecurityPolicy()->Headers(),
         document->GetReferrerPolicy(), document->GetSecurityOrigin(),
diff --git a/third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.cc b/third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.cc
index dc7d022c..6eeb1fca 100644
--- a/third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.cc
+++ b/third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.cc
@@ -54,7 +54,8 @@
 
   auto global_scope_creation_params =
       std::make_unique<GlobalScopeCreationParams>(
-          document->Url(), mojom::ScriptType::kModule, document->UserAgent(),
+          document->Url(), mojom::ScriptType::kModule,
+          OffMainThreadWorkerScriptFetchOption::kEnabled, document->UserAgent(),
           document->GetFrame()->Client()->CreateWorkerFetchContext(),
           csp->Headers(), document->GetReferrerPolicy(),
           document->GetSecurityOrigin(), document->IsSecureContext(),
diff --git a/third_party/blink/renderer/core/workers/threaded_worklet_test.cc b/third_party/blink/renderer/core/workers/threaded_worklet_test.cc
index 79c2172..45198d0 100644
--- a/third_party/blink/renderer/core/workers/threaded_worklet_test.cc
+++ b/third_party/blink/renderer/core/workers/threaded_worklet_test.cc
@@ -203,8 +203,9 @@
     std::unique_ptr<WorkerSettings> worker_settings = nullptr;
     InitializeWorkerThread(
         std::make_unique<GlobalScopeCreationParams>(
-            document->Url(), mojom::ScriptType::kModule, document->UserAgent(),
-            nullptr /* web_worker_fetch_context */,
+            document->Url(), mojom::ScriptType::kModule,
+            OffMainThreadWorkerScriptFetchOption::kEnabled,
+            document->UserAgent(), nullptr /* web_worker_fetch_context */,
             document->GetContentSecurityPolicy()->Headers(),
             document->GetReferrerPolicy(), document->GetSecurityOrigin(),
             document->IsSecureContext(), document->GetHttpsState(),
diff --git a/third_party/blink/renderer/core/workers/worker_global_scope.cc b/third_party/blink/renderer/core/workers/worker_global_scope.cc
index dc742622..64448f5 100644
--- a/third_party/blink/renderer/core/workers/worker_global_scope.cc
+++ b/third_party/blink/renderer/core/workers/worker_global_scope.cc
@@ -528,7 +528,8 @@
   // Set the referrer policy here for workers whose script is fetched on the
   // main thread. For off-the-main-thread fetches, it is instead set after the
   // script is fetched.
-  if (IsScriptFetchedOnMainThread())
+  if (creation_params->off_main_thread_fetch_option ==
+      OffMainThreadWorkerScriptFetchOption::kDisabled)
     SetReferrerPolicy(creation_params->referrer_policy);
 
   SetAddressSpace(creation_params->address_space);
@@ -609,20 +610,6 @@
       worker_settings_->GetGenericFontFamilySettings());
 }
 
-bool WorkerGlobalScope::IsScriptFetchedOnMainThread() {
-  if (script_type_ == mojom::ScriptType::kModule)
-    return false;
-  // It's now supported only for dedicated workers to load top-level classic
-  // worker script off the main thread.
-  // TODO(nhiroki): Support loading top-level classic worker script off the main
-  // thread for shared workers and service workers.
-  if (IsDedicatedWorkerGlobalScope() &&
-      RuntimeEnabledFeatures::OffMainThreadWorkerScriptFetchEnabled()) {
-    return false;
-  }
-  return true;
-}
-
 TrustedTypePolicyFactory* WorkerGlobalScope::trustedTypes() {
   if (!trusted_types_) {
     trusted_types_ = TrustedTypePolicyFactory::Create(this);
diff --git a/third_party/blink/renderer/core/workers/worker_global_scope.h b/third_party/blink/renderer/core/workers/worker_global_scope.h
index f0d0c98b..92c77ed 100644
--- a/third_party/blink/renderer/core/workers/worker_global_scope.h
+++ b/third_party/blink/renderer/core/workers/worker_global_scope.h
@@ -205,10 +205,6 @@
 
   void SetWorkerSettings(std::unique_ptr<WorkerSettings>);
 
-  // Returns true if this worker script is supposed to be fetched on the main
-  // thread and passed to the worker thread.
-  bool IsScriptFetchedOnMainThread();
-
   void DidReceiveResponseForClassicScript(
       WorkerClassicScriptLoader* classic_script_loader);
   void DidImportClassicScript(WorkerClassicScriptLoader* classic_script_loader,
diff --git a/third_party/blink/renderer/core/workers/worker_thread_test.cc b/third_party/blink/renderer/core/workers/worker_thread_test.cc
index 35c2fe1e..0df86fe 100644
--- a/third_party/blink/renderer/core/workers/worker_thread_test.cc
+++ b/third_party/blink/renderer/core/workers/worker_thread_test.cc
@@ -364,7 +364,8 @@
   auto global_scope_creation_params =
       std::make_unique<GlobalScopeCreationParams>(
           KURL("http://fake.url/"), mojom::ScriptType::kClassic,
-          "fake user agent", nullptr /* web_worker_fetch_context */, headers,
+          OffMainThreadWorkerScriptFetchOption::kDisabled, "fake user agent",
+          nullptr /* web_worker_fetch_context */, headers,
           network::mojom::ReferrerPolicy::kDefault, security_origin_.get(),
           false /* starter_secure_context */,
           CalculateHttpsState(security_origin_.get()), WorkerClients::Create(),
diff --git a/third_party/blink/renderer/core/workers/worker_thread_test_helper.h b/third_party/blink/renderer/core/workers/worker_thread_test_helper.h
index 7e41bf8..9eae2789 100644
--- a/third_party/blink/renderer/core/workers/worker_thread_test_helper.h
+++ b/third_party/blink/renderer/core/workers/worker_thread_test_helper.h
@@ -91,7 +91,8 @@
     Vector<CSPHeaderAndType> headers{
         {"contentSecurityPolicy", kContentSecurityPolicyHeaderTypeReport}};
     auto creation_params = std::make_unique<GlobalScopeCreationParams>(
-        script_url, mojom::ScriptType::kClassic, "fake user agent",
+        script_url, mojom::ScriptType::kClassic,
+        OffMainThreadWorkerScriptFetchOption::kDisabled, "fake user agent",
         nullptr /* web_worker_fetch_context */, headers,
         network::mojom::ReferrerPolicy::kDefault, security_origin,
         false /* starter_secure_context */,
diff --git a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
index 6ee2d70..87bdb599 100644
--- a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
+++ b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
@@ -1128,17 +1128,25 @@
       // Update histogram for usage of sync xhr within pagedismissal.
       auto pagedismissal = GetDocument()->PageDismissalEventBeingDispatched();
       if (pagedismissal != Document::kNoDismissal) {
-        UseCounter::Count(GetDocument(), WebFeature::kSyncXhrInPageDismissal);
-        DEFINE_STATIC_LOCAL(EnumerationHistogram, syncxhr_pagedismissal_histogram,
-                            ("XHR.Sync.PageDismissal", 5));
-        syncxhr_pagedismissal_histogram.Count(pagedismissal);
         // Disallow synchronous requests on page dismissal
         if (base::FeatureList::IsEnabled(
                 features::kForbidSyncXHRInPageDismissal)) {
+          UseCounter::Count(GetDocument(),
+                            WebFeature::kForbiddenSyncXhrInPageDismissal);
+          DEFINE_STATIC_LOCAL(EnumerationHistogram,
+                              forbidden_syncxhr_pagedismissal_histogram,
+                              ("XHR.Sync.PageDismissal_forbidden", 5));
+          forbidden_syncxhr_pagedismissal_histogram.Count(pagedismissal);
           HandleNetworkError();
           ThrowForLoadFailureIfNeeded(exception_state,
                                       "Synchronous XHR in page dismissal.");
           return;
+        } else {
+          UseCounter::Count(GetDocument(), WebFeature::kSyncXhrInPageDismissal);
+          DEFINE_STATIC_LOCAL(EnumerationHistogram,
+                              syncxhr_pagedismissal_histogram,
+                              ("XHR.Sync.PageDismissal", 5));
+          syncxhr_pagedismissal_histogram.Count(pagedismissal);
         }
       }
     }
diff --git a/third_party/blink/renderer/devtools/front_end/elements_test_runner/ElementsTestRunner.js b/third_party/blink/renderer/devtools/front_end/elements_test_runner/ElementsTestRunner.js
index 7195398b9..c660186 100644
--- a/third_party/blink/renderer/devtools/front_end/elements_test_runner/ElementsTestRunner.js
+++ b/third_party/blink/renderer/devtools/front_end/elements_test_runner/ElementsTestRunner.js
@@ -613,8 +613,8 @@
 };
 
 ElementsTestRunner.dumpStyleTreeItem = function(treeItem, prefix, depth) {
-  if (treeItem.listItemElement.textContent.indexOf(' width:') !== -1 ||
-      treeItem.listItemElement.textContent.indexOf(' height:') !== -1)
+  const textContent = TestRunner.textContentWithoutStyles(treeItem.listItemElement);
+  if (textContent.indexOf(' width:') !== -1 || textContent.indexOf(' height:') !== -1)
     return;
 
   if (treeItem.listItemElement.classList.contains('inherited'))
@@ -630,7 +630,6 @@
   if (treeItem.listItemElement.classList.contains('disabled'))
     typePrefix += '/-- disabled --/ ';
 
-  const textContent = treeItem.listItemElement.textContent;
   TestRunner.addResult(prefix + typePrefix + textContent);
 
   if (--depth) {
diff --git a/third_party/blink/renderer/devtools/front_end/emulation/SensorsView.js b/third_party/blink/renderer/devtools/front_end/emulation/SensorsView.js
index 18dab20..514b5657e 100644
--- a/third_party/blink/renderer/devtools/front_end/emulation/SensorsView.js
+++ b/third_party/blink/renderer/devtools/front_end/emulation/SensorsView.js
@@ -125,6 +125,11 @@
       this._fieldsetElement.disabled = true;
     } else if (value === Emulation.SensorsView.NonPresetOptions.Custom) {
       this._geolocationOverrideEnabled = true;
+      const geolocation = SDK.EmulationModel.Geolocation.parseUserInput(
+          this._latitudeInput.value.trim(), this._longitudeInput.value.trim(), '');
+      if (!geolocation)
+        return;
+      this._geolocation = geolocation;
     } else if (value === Emulation.SensorsView.NonPresetOptions.Unavailable) {
       this._geolocationOverrideEnabled = true;
       this._geolocation = new SDK.EmulationModel.Geolocation(0, 0, true);
diff --git a/third_party/blink/renderer/devtools/front_end/emulation/sensors.css b/third_party/blink/renderer/devtools/front_end/emulation/sensors.css
index a261d73..e7177d9 100644
--- a/third_party/blink/renderer/devtools/front_end/emulation/sensors.css
+++ b/third_party/blink/renderer/devtools/front_end/emulation/sensors.css
@@ -310,6 +310,6 @@
     margin-left: 10px;
 }
 
-button[is=text-button] {
+button.text-button {
     margin: 0 10px;
 }
diff --git a/third_party/blink/renderer/devtools/front_end/ui/TextPrompt.js b/third_party/blink/renderer/devtools/front_end/ui/TextPrompt.js
index f3305be..9aeaf15 100644
--- a/third_party/blink/renderer/devtools/front_end/ui/TextPrompt.js
+++ b/third_party/blink/renderer/devtools/front_end/ui/TextPrompt.js
@@ -110,12 +110,11 @@
     this._boundOnMouseWheel = this.onMouseWheel.bind(this);
     this._boundClearAutocomplete = this.clearAutocomplete.bind(this);
     this._proxyElement = element.ownerDocument.createElement('span');
-    const shadowRoot = UI.createShadowRootWithCoreStyles(this._proxyElement, 'ui/textPrompt.css');
-    this._contentElement = shadowRoot.createChild('div', 'text-prompt-root');
-    this._contentElement.createChild('content');
+    UI.appendStyle(this._proxyElement, 'ui/textPrompt.css');
+    this._contentElement = this._proxyElement.createChild('div', 'text-prompt-root');
     this._proxyElement.style.display = this._proxyElementDisplay;
     element.parentElement.insertBefore(this._proxyElement, element);
-    this._proxyElement.appendChild(element);
+    this._contentElement.appendChild(element);
     this._element.classList.add('text-prompt');
     UI.ARIAUtils.markAsTextBox(this._element);
     this._element.setAttribute('contenteditable', 'plaintext-only');
diff --git a/third_party/blink/renderer/devtools/front_end/ui/UIUtils.js b/third_party/blink/renderer/devtools/front_end/ui/UIUtils.js
index 1f2aeb17..45d2074 100644
--- a/third_party/blink/renderer/devtools/front_end/ui/UIUtils.js
+++ b/third_party/blink/renderer/devtools/front_end/ui/UIUtils.js
@@ -674,9 +674,7 @@
  * @param {!Element} element
  */
 UI.installComponentRootStyles = function(element) {
-  UI.appendStyle(element, 'ui/inspectorCommon.css');
-  UI.themeSupport.injectHighlightStyleSheets(element);
-  UI.themeSupport.injectCustomStyleSheets(element);
+  UI._injectCoreStyles(element);
   element.classList.add('platform-' + Host.platform());
 
   // Detect overlay scrollbar enable by checking for nonzero scrollbar width.
@@ -708,9 +706,7 @@
  */
 UI.createShadowRootWithCoreStyles = function(element, cssFile) {
   const shadowRoot = element.createShadowRoot();
-  UI.appendStyle(shadowRoot, 'ui/inspectorCommon.css');
-  UI.themeSupport.injectHighlightStyleSheets(shadowRoot);
-  UI.themeSupport.injectCustomStyleSheets(shadowRoot);
+  UI._injectCoreStyles(shadowRoot);
   if (cssFile)
     UI.appendStyle(shadowRoot, cssFile);
   shadowRoot.addEventListener('focus', UI._focusChanged.bind(UI), true);
@@ -718,6 +714,16 @@
 };
 
 /**
+ * @param {!Element|!ShadowRoot} root
+ */
+UI._injectCoreStyles = function(root) {
+  UI.appendStyle(root, 'ui/inspectorCommon.css');
+  UI.appendStyle(root, 'ui/textButton.css');
+  UI.themeSupport.injectHighlightStyleSheets(root);
+  UI.themeSupport.injectCustomStyleSheets(root);
+};
+
+/**
  * @param {!Document} document
  * @param {!Event} event
  */
@@ -1183,12 +1189,14 @@
  * @return {!Element}
  */
 UI.createTextButton = function(text, clickHandler, className, primary) {
-  const element = createElementWithClass('button', className || '', 'text-button');
+  const element = createElementWithClass('button', className || '');
   element.textContent = text;
+  element.classList.add('text-button');
   if (primary)
     element.classList.add('primary-button');
   if (clickHandler)
     element.addEventListener('click', clickHandler, false);
+  element.type = 'button';
   return element;
 };
 
@@ -1350,19 +1358,6 @@
 };
 
 (function() {
-  UI.registerCustomElement('button', 'text-button', {
-    /**
-     * @this {Element}
-     */
-    createdCallback: function() {
-      this.type = 'button';
-      const root = UI.createShadowRootWithCoreStyles(this, 'ui/textButton.css');
-      root.createChild('content');
-    },
-
-    __proto__: HTMLButtonElement.prototype
-  });
-
   UI.registerCustomElement('label', 'dt-radio', {
     /**
      * @this {Element}
@@ -1681,7 +1676,7 @@
   }
 
   /**
-   * @param {!Element} element
+   * @param {!Element|!ShadowRoot} element
    */
   injectHighlightStyleSheets(element) {
     this._injectingStyleSheet = true;
diff --git a/third_party/blink/renderer/devtools/front_end/ui/textButton.css b/third_party/blink/renderer/devtools/front_end/ui/textButton.css
index b18868b..05fd2ca 100644
--- a/third_party/blink/renderer/devtools/front_end/ui/textButton.css
+++ b/third_party/blink/renderer/devtools/front_end/ui/textButton.css
@@ -4,7 +4,7 @@
  * found in the LICENSE file.
  */
 
-:host {
+.text-button {
     margin: 2px;
     height: 24px;
     font-size: 12px;
@@ -18,49 +18,49 @@
     white-space: nowrap;
 }
 
-:host(:not(:disabled):focus),
-:host(:not(:disabled):hover),
-:host(:not(:disabled):active) {
+.text-button:not(:disabled):focus,
+.text-button:not(:disabled):hover,
+.text-button:not(:disabled):active {
     background-color: var(--toolbar-bg-color);
     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
     cursor: pointer;
 }
 
-:host(:not(:disabled):active) {
+.text-button:not(:disabled):active {
     background-color: #f2f2f2;
 }
 
-:host(:not(:disabled):focus) {
+.text-button:not(:disabled):focus {
     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(66, 133, 244, 0.4);
 }
 
-:host(:disabled) {
+.text-button:disabled {
     opacity: 0.38;
 }
 
-:host(.primary-button), -theme-preserve {
+.text-button.primary-button, -theme-preserve {
     background-color: var(--accent-color);
     border: none;
     color: #fff;
 }
 
-:host(.primary-button:not(:disabled):focus),
-:host(.primary-button:not(:disabled):hover),
-:host(.primary-button:not(:disabled):active), -theme-preserve {
+.text-button.primary-button:not(:disabled):focus,
+.text-button.primary-button:not(:disabled):hover,
+.text-button.primary-button:not(:disabled):active, -theme-preserve {
     background-color: var(--accent-color-hover);
 }
 
-:host-context(.-theme-with-dark-background):host(:not(.primary-button):not(:disabled):focus),
-:host-context(.-theme-with-dark-background):host(:not(.primary-button):not(:disabled):hover),
-:host-context(.-theme-with-dark-background):host(:not(.primary-button):not(:disabled):active) {
+.-theme-with-dark-background .text-button:not(.primary-button):not(:disabled):focus,
+.-theme-with-dark-background .text-button:not(.primary-button):not(:disabled):hover,
+.-theme-with-dark-background .text-button:not(.primary-button):not(:disabled):active {
     background-color: #313131;
     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
 }
 
-:host-context(.-theme-with-dark-background):host(:not(.primary-button):not(:disabled):focus) {
+.-theme-with-dark-background .text-button:not(.primary-button):not(:disabled):focus {
     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(94, 151, 246, 0.6);
 }
 
-:host-context(.-theme-with-dark-background):host(:not(.primary-button):not(:disabled):active) {
+.-theme-with-dark-background .text-button:not(.primary-button):not(:disabled):active {
     background-color: #3e3e3e;
 }
diff --git a/third_party/blink/renderer/devtools/front_end/ui/textPrompt.css b/third_party/blink/renderer/devtools/front_end/ui/textPrompt.css
index 5fb0dd61..b229032 100644
--- a/third_party/blink/renderer/devtools/front_end/ui/textPrompt.css
+++ b/third_party/blink/renderer/devtools/front_end/ui/textPrompt.css
@@ -4,7 +4,7 @@
  * found in the LICENSE file.
  */
 
-.text-prompt-root {
+ .text-prompt-root {
     display: flex;
     align-items: center;
 }
@@ -22,46 +22,45 @@
     opacity: 1.0 !important;
 }
 
-.text-prompt-editing,
-.text-prompt-editing ::content * {
+.text-prompt-editing > .text-prompt {
     color: #222 !important;
     text-decoration: none !important;
     white-space: pre;
 }
 
-::content .auto-complete-text {
+.text-prompt > .auto-complete-text {
     color: rgb(128, 128, 128) !important;
 }
 
-::content .text-prompt[data-placeholder]:empty::before {
+.text-prompt[data-placeholder]:empty::before {
     content: attr(data-placeholder);
     color: rgb(128, 128, 128);
 }
 
-::content .text-prompt:not([data-placeholder]):empty::after {
+.text-prompt:not([data-placeholder]):empty::after {
     content: '\00A0';
     width: 0;
     display: block;
 }
 
-::content .text-prompt {
+.text-prompt {
     cursor: text;
     overflow-x: visible;
 }
 
-::content .text-prompt::-webkit-scrollbar {
+.text-prompt::-webkit-scrollbar {
     display: none;
 }
 
-::content .text-prompt.disabled {
+.text-prompt.disabled {
     opacity: 0.5;
     cursor: default;
 }
 
-.text-prompt-editing ::content br {
+.text-prompt-editing br {
     display: none;
 }
 
-:host-context(:not(:focus-within)) ::content ::selection {
+.text-prompt-root:not(:focus-within) ::selection {
     background: transparent;
 }
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
index 099c296..718d367b 100644
--- a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
+++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
@@ -79,11 +79,12 @@
     Document* document = &GetDocument();
     thread->Start(
         std::make_unique<GlobalScopeCreationParams>(
-            document->Url(), mojom::ScriptType::kModule, document->UserAgent(),
-            nullptr /* web_worker_fetch_context */, Vector<CSPHeaderAndType>(),
-            document->GetReferrerPolicy(), document->GetSecurityOrigin(),
-            document->IsSecureContext(), document->GetHttpsState(), clients,
-            document->AddressSpace(),
+            document->Url(), mojom::ScriptType::kModule,
+            OffMainThreadWorkerScriptFetchOption::kEnabled,
+            document->UserAgent(), nullptr /* web_worker_fetch_context */,
+            Vector<CSPHeaderAndType>(), document->GetReferrerPolicy(),
+            document->GetSecurityOrigin(), document->IsSecureContext(),
+            document->GetHttpsState(), clients, document->AddressSpace(),
             OriginTrialContext::GetTokens(document).get(),
             base::UnguessableToken::Create(), nullptr /* worker_settings */,
             kV8CacheOptionsDefault,
diff --git a/third_party/blink/renderer/modules/cache_storage/cache.cc b/third_party/blink/renderer/modules/cache_storage/cache.cc
index 2ae91d9..8f8750f 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache.cc
@@ -31,6 +31,7 @@
 #include "third_party/blink/renderer/core/frame/deprecation.h"
 #include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
+#include "third_party/blink/renderer/modules/cache_storage/cache_storage.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_error.h"
 #include "third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -496,13 +497,6 @@
                                    base::Time response_time,
                                    base::TimeTicks) {
     ServiceWorkerGlobalScope* global_scope = GetServiceWorkerGlobalScope();
-    // |cache_ptr_| can be disconnected when the wrapper of CacheStorage is
-    // gone.
-    if (!cache_->cache_ptr_.is_bound()) {
-      global_scope->DidEndTask(task_id);
-      return;
-    }
-
     scoped_refptr<CachedMetadata> cached_metadata =
         GenerateFullCodeCache(array_buffer);
     if (!cached_metadata) {
@@ -534,9 +528,11 @@
 
 Cache* Cache::Create(
     GlobalFetch::ScopedFetcher* fetcher,
+    CacheStorage* cache_storage,
     mojom::blink::CacheStorageCacheAssociatedPtrInfo cache_ptr_info,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
-  return MakeGarbageCollected<Cache>(fetcher, std::move(cache_ptr_info),
+  return MakeGarbageCollected<Cache>(fetcher, cache_storage,
+                                     std::move(cache_ptr_info),
                                      std::move(task_runner));
 }
 
@@ -671,14 +667,16 @@
 }
 
 Cache::Cache(GlobalFetch::ScopedFetcher* fetcher,
+             CacheStorage* cache_storage,
              mojom::blink::CacheStorageCacheAssociatedPtrInfo cache_ptr_info,
              scoped_refptr<base::SingleThreadTaskRunner> task_runner)
-    : scoped_fetcher_(fetcher) {
+    : scoped_fetcher_(fetcher), cache_storage_(cache_storage) {
   cache_ptr_.Bind(std::move(cache_ptr_info), std::move(task_runner));
 }
 
 void Cache::Trace(blink::Visitor* visitor) {
   visitor->Trace(scoped_fetcher_);
+  visitor->Trace(cache_storage_);
   ScriptWrappable::Trace(visitor);
 }
 
diff --git a/third_party/blink/renderer/modules/cache_storage/cache.h b/third_party/blink/renderer/modules/cache_storage/cache.h
index ce0bffb4..e7fe158 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache.h
+++ b/third_party/blink/renderer/modules/cache_storage/cache.h
@@ -20,6 +20,7 @@
 
 namespace blink {
 
+class CacheStorage;
 class ExceptionState;
 class Response;
 class Request;
@@ -32,10 +33,12 @@
 
  public:
   static Cache* Create(GlobalFetch::ScopedFetcher*,
+                       CacheStorage*,
                        mojom::blink::CacheStorageCacheAssociatedPtrInfo,
                        scoped_refptr<base::SingleThreadTaskRunner>);
 
   Cache(GlobalFetch::ScopedFetcher*,
+        CacheStorage*,
         mojom::blink::CacheStorageCacheAssociatedPtrInfo,
         scoped_refptr<base::SingleThreadTaskRunner>);
 
@@ -101,6 +104,10 @@
                          const CacheQueryOptions*);
 
   Member<GlobalFetch::ScopedFetcher> scoped_fetcher_;
+  // Hold a reference to CacheStorage to keep |cache_ptr_| alive.
+  // This is required because |cache_ptr_| is associated with CacheStorage's
+  // mojo message pipe.
+  Member<CacheStorage> cache_storage_;
 
   mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr_;
 
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_storage.cc b/third_party/blink/renderer/modules/cache_storage/cache_storage.cc
index 7420328..2f6303f 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_storage.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache_storage.cc
@@ -40,7 +40,7 @@
       WTF::Bind(
           [](ScriptPromiseResolver* resolver,
              GlobalFetch::ScopedFetcher* fetcher, TimeTicks start_time,
-             CacheStorage*, mojom::blink::OpenResultPtr result) {
+             CacheStorage* cache_storage, mojom::blink::OpenResultPtr result) {
             if (!resolver->GetExecutionContext() ||
                 resolver->GetExecutionContext()->IsContextDestroyed()) {
               return;
@@ -60,10 +60,10 @@
               UMA_HISTOGRAM_TIMES("ServiceWorkerCache.CacheStorage.Open",
                                   TimeTicks::Now() - start_time);
               // See https://bit.ly/2S0zRAS for task types.
-              resolver->Resolve(
-                  Cache::Create(fetcher, std::move(result->get_cache()),
-                                resolver->GetExecutionContext()->GetTaskRunner(
-                                    blink::TaskType::kMiscPlatformAPI)));
+              resolver->Resolve(Cache::Create(
+                  fetcher, cache_storage, std::move(result->get_cache()),
+                  resolver->GetExecutionContext()->GetTaskRunner(
+                      blink::TaskType::kMiscPlatformAPI)));
             }
           },
           WrapPersistent(resolver), WrapPersistent(scoped_fetcher_.Get()),
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_test.cc b/third_party/blink/renderer/modules/cache_storage/cache_test.cc
index 229ed0a..7a15862 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_test.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache_test.cc
@@ -278,7 +278,7 @@
         mojo::AssociatedBinding<mojom::blink::CacheStorageCache>>(
         cache_.get(), std::move(request));
     return Cache::Create(
-        fetcher, cache_ptr.PassInterface(),
+        fetcher, nullptr /* cache_storage */, cache_ptr.PassInterface(),
         blink::scheduler::GetSingleThreadTaskRunnerForTesting());
   }
 
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_proxy.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_proxy.cc
index 833e827..523e734 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_proxy.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_proxy.cc
@@ -41,7 +41,8 @@
       worker_clients, frame->Client()->CreateWorkerContentSettingsClient());
 
   auto creation_params = std::make_unique<GlobalScopeCreationParams>(
-      document->Url(), mojom::ScriptType::kModule, document->UserAgent(),
+      document->Url(), mojom::ScriptType::kModule,
+      OffMainThreadWorkerScriptFetchOption::kEnabled, document->UserAgent(),
       frame->Client()->CreateWorkerFetchContext(),
       document->GetContentSecurityPolicy()->Headers(),
       document->GetReferrerPolicy(), document->GetSecurityOrigin(),
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc
index 60b4911a..7ba26c6 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc
@@ -61,11 +61,12 @@
     Document* document = &GetDocument();
     thread->Start(
         std::make_unique<GlobalScopeCreationParams>(
-            document->Url(), mojom::ScriptType::kModule, document->UserAgent(),
-            nullptr /* web_worker_fetch_context */, Vector<CSPHeaderAndType>(),
-            document->GetReferrerPolicy(), document->GetSecurityOrigin(),
-            document->IsSecureContext(), document->GetHttpsState(), clients,
-            document->AddressSpace(),
+            document->Url(), mojom::ScriptType::kModule,
+            OffMainThreadWorkerScriptFetchOption::kEnabled,
+            document->UserAgent(), nullptr /* web_worker_fetch_context */,
+            Vector<CSPHeaderAndType>(), document->GetReferrerPolicy(),
+            document->GetSecurityOrigin(), document->IsSecureContext(),
+            document->GetHttpsState(), clients, document->AddressSpace(),
             OriginTrialContext::GetTokens(document).get(),
             base::UnguessableToken::Create(), nullptr /* worker_settings */,
             kV8CacheOptionsDefault,
diff --git a/third_party/blink/renderer/modules/exported/web_embedded_worker_impl.cc b/third_party/blink/renderer/modules/exported/web_embedded_worker_impl.cc
index d5f411ca3..7282eebe 100644
--- a/third_party/blink/renderer/modules/exported/web_embedded_worker_impl.cc
+++ b/third_party/blink/renderer/modules/exported/web_embedded_worker_impl.cc
@@ -395,6 +395,13 @@
   String source_code;
   std::unique_ptr<Vector<uint8_t>> cached_meta_data;
 
+  // TODO(nhiroki): Implement off-the-main-thread worker script fetch for
+  // service workers (https://crbug.com/835717).
+  const OffMainThreadWorkerScriptFetchOption off_main_thread_fetch_option =
+      worker_start_data_.script_type == mojom::ScriptType::kModule
+          ? OffMainThreadWorkerScriptFetchOption::kEnabled
+          : OffMainThreadWorkerScriptFetchOption::kDisabled;
+
   // |main_script_loader_| isn't created if the InstalledScriptsManager had the
   // script.
   if (main_script_loader_) {
@@ -409,7 +416,8 @@
     }
     global_scope_creation_params = std::make_unique<GlobalScopeCreationParams>(
         worker_start_data_.script_url, worker_start_data_.script_type,
-        worker_start_data_.user_agent, std::move(web_worker_fetch_context),
+        off_main_thread_fetch_option, worker_start_data_.user_agent,
+        std::move(web_worker_fetch_context),
         content_security_policy ? content_security_policy->Headers()
                                 : Vector<CSPHeaderAndType>(),
         referrer_policy, starter_origin, starter_secure_context,
@@ -428,12 +436,12 @@
     // served by the installed scripts manager on the worker thread.
     global_scope_creation_params = std::make_unique<GlobalScopeCreationParams>(
         worker_start_data_.script_url, worker_start_data_.script_type,
-        worker_start_data_.user_agent, std::move(web_worker_fetch_context),
-        Vector<CSPHeaderAndType>(), network::mojom::ReferrerPolicy::kDefault,
-        starter_origin, starter_secure_context, starter_https_state,
-        worker_clients, worker_start_data_.address_space,
-        nullptr /* OriginTrialTokens */, devtools_worker_token_,
-        std::move(worker_settings),
+        off_main_thread_fetch_option, worker_start_data_.user_agent,
+        std::move(web_worker_fetch_context), Vector<CSPHeaderAndType>(),
+        network::mojom::ReferrerPolicy::kDefault, starter_origin,
+        starter_secure_context, starter_https_state, worker_clients,
+        worker_start_data_.address_space, nullptr /* OriginTrialTokens */,
+        devtools_worker_token_, std::move(worker_settings),
         static_cast<V8CacheOptions>(worker_start_data_.v8_cache_options),
         nullptr /* worklet_module_respones_map */,
         std::move(interface_provider_info_));
diff --git a/third_party/blink/renderer/modules/mediasession/media_metadata_sanitizer.cc b/third_party/blink/renderer/modules/mediasession/media_metadata_sanitizer.cc
index c94931b..501686f 100644
--- a/third_party/blink/renderer/modules/mediasession/media_metadata_sanitizer.cc
+++ b/third_party/blink/renderer/modules/mediasession/media_metadata_sanitizer.cc
@@ -100,6 +100,9 @@
   mojo_metadata->artist = metadata->artist().Left(kMaxStringLength);
   mojo_metadata->album = metadata->album().Left(kMaxStringLength);
 
+  // |source_title_| is populated by content::MediaSessionImpl.
+  mojo_metadata->source_title = g_empty_string16_bit;
+
   for (const MediaImage* image : metadata->artwork()) {
     media_session::mojom::blink::MediaImagePtr mojo_image =
         SanitizeMediaImageAndConvertToMojo(image, context);
diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc b/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
index 1e10cd0d..5beab54f 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
@@ -68,11 +68,13 @@
     Document* document = &GetDocument();
     thread->Start(
         std::make_unique<GlobalScopeCreationParams>(
-            document->Url(), mojom::ScriptType::kModule, document->UserAgent(),
-            nullptr /* web_worker_fetch_context */, Vector<CSPHeaderAndType>(),
-            document->GetReferrerPolicy(), document->GetSecurityOrigin(),
-            document->IsSecureContext(), document->GetHttpsState(),
-            nullptr /* worker_clients */, document->AddressSpace(),
+            document->Url(), mojom::ScriptType::kModule,
+            OffMainThreadWorkerScriptFetchOption::kEnabled,
+            document->UserAgent(), nullptr /* web_worker_fetch_context */,
+            Vector<CSPHeaderAndType>(), document->GetReferrerPolicy(),
+            document->GetSecurityOrigin(), document->IsSecureContext(),
+            document->GetHttpsState(), nullptr /* worker_clients */,
+            document->AddressSpace(),
             OriginTrialContext::GetTokens(document).get(),
             base::UnguessableToken::Create(), nullptr /* worker_settings */,
             kV8CacheOptionsDefault,
diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_thread_test.cc b/third_party/blink/renderer/modules/webaudio/audio_worklet_thread_test.cc
index 3847c25..52503a9 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_thread_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_thread_test.cc
@@ -52,11 +52,13 @@
     Document* document = &GetDocument();
     thread->Start(
         std::make_unique<GlobalScopeCreationParams>(
-            document->Url(), mojom::ScriptType::kModule, document->UserAgent(),
-            nullptr /* web_worker_fetch_context */, Vector<CSPHeaderAndType>(),
-            document->GetReferrerPolicy(), document->GetSecurityOrigin(),
-            document->IsSecureContext(), document->GetHttpsState(),
-            nullptr /* worker_clients */, document->AddressSpace(),
+            document->Url(), mojom::ScriptType::kModule,
+            OffMainThreadWorkerScriptFetchOption::kEnabled,
+            document->UserAgent(), nullptr /* web_worker_fetch_context */,
+            Vector<CSPHeaderAndType>(), document->GetReferrerPolicy(),
+            document->GetSecurityOrigin(), document->IsSecureContext(),
+            document->GetHttpsState(), nullptr /* worker_clients */,
+            document->AddressSpace(),
             OriginTrialContext::GetTokens(document).get(),
             base::UnguessableToken::Create(), nullptr /* worker_settings */,
             kV8CacheOptionsDefault,
diff --git a/third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread_test.cc b/third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread_test.cc
index bab1a0b65..f021033a 100644
--- a/third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread_test.cc
+++ b/third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread_test.cc
@@ -67,11 +67,12 @@
     Document* document = &GetDocument();
     thread->Start(
         std::make_unique<GlobalScopeCreationParams>(
-            document->Url(), mojom::ScriptType::kModule, document->UserAgent(),
-            nullptr /* web_worker_fetch_context */, Vector<CSPHeaderAndType>(),
-            document->GetReferrerPolicy(), document->GetSecurityOrigin(),
-            document->IsSecureContext(), document->GetHttpsState(), clients,
-            document->AddressSpace(),
+            document->Url(), mojom::ScriptType::kModule,
+            OffMainThreadWorkerScriptFetchOption::kEnabled,
+            document->UserAgent(), nullptr /* web_worker_fetch_context */,
+            Vector<CSPHeaderAndType>(), document->GetReferrerPolicy(),
+            document->GetSecurityOrigin(), document->IsSecureContext(),
+            document->GetHttpsState(), clients, document->AddressSpace(),
             OriginTrialContext::GetTokens(document).get(),
             base::UnguessableToken::Create(), nullptr /* worker_settings */,
             kV8CacheOptionsDefault,
diff --git a/third_party/blink/renderer/platform/exported/web_runtime_features.cc b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
index 74216d6..fe1f8dd3 100644
--- a/third_party/blink/renderer/platform/exported/web_runtime_features.cc
+++ b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
@@ -364,6 +364,10 @@
   return RuntimeEnabledFeatures::CompositeAfterPaintEnabled();
 }
 
+void WebRuntimeFeatures::EnableUserActivationSameOriginVisibility(bool enable) {
+  RuntimeEnabledFeatures::SetUserActivationSameOriginVisibilityEnabled(enable);
+}
+
 void WebRuntimeFeatures::EnableUserActivationV2(bool enable) {
   RuntimeEnabledFeatures::SetUserActivationV2Enabled(enable);
 }
diff --git a/third_party/blink/renderer/platform/graphics/DEPS b/third_party/blink/renderer/platform/graphics/DEPS
index a0ce109..e341255 100644
--- a/third_party/blink/renderer/platform/graphics/DEPS
+++ b/third_party/blink/renderer/platform/graphics/DEPS
@@ -22,6 +22,7 @@
     "+gpu/ipc/common/mailbox.mojom-blink.h",
     "+media/base/media_switches.h",
     "+media/base/video_frame.h",
+    "+media/base/video_types.h",
     "+media/renderers/video_resource_updater.h",
     "+services/viz/public/interfaces",
     "+services/ws/public/cpp/gpu/context_provider_command_buffer.h",
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc b/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
index 48bec8070..e2bf49c 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
+++ b/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
@@ -14,6 +14,7 @@
 #include "components/viz/common/resources/resource_id.h"
 #include "components/viz/common/resources/returned_resource.h"
 #include "media/base/video_frame.h"
+#include "media/base/video_types.h"
 #include "services/viz/public/interfaces/compositing/compositor_frame_sink.mojom-blink.h"
 #include "services/ws/public/cpp/gpu/context_provider_command_buffer.h"
 #include "third_party/blink/public/platform/interface_provider.h"
@@ -41,30 +42,30 @@
       enable_surface_synchronization_(
           ::features::IsSurfaceSynchronizationEnabled()),
       weak_ptr_factory_(this) {
-  DETACH_FROM_THREAD(media_thread_checker_);
+  DETACH_FROM_THREAD(thread_checker_);
 }
 
 VideoFrameSubmitter::~VideoFrameSubmitter() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (context_provider_)
     context_provider_->RemoveObserver(this);
+
+  // Release VideoFrameResourceProvider early since its destruction will make
+  // calls back into this class via the viz::SharedBitmapReporter interface.
+  resource_provider_.reset();
 }
 
 void VideoFrameSubmitter::SetRotation(media::VideoRotation rotation) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   rotation_ = rotation;
 }
 
-void VideoFrameSubmitter::SetIsOpaque(bool is_opaque) {
-  if (is_opaque_ == is_opaque)
-    return;
-
-  is_opaque_ = is_opaque;
-  UpdateSubmissionStateInternal();
-}
-
 void VideoFrameSubmitter::EnableSubmission(
     viz::SurfaceId surface_id,
     base::TimeTicks local_surface_id_allocation_time,
     WebFrameSinkDestroyedCallback frame_sink_destroyed_callback) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
   // TODO(lethalantidote): Set these fields earlier in the constructor. Will
   // need to construct VideoFrameSubmitter later in order to do this.
   frame_sink_id_ = surface_id.frame_sink_id();
@@ -77,74 +78,90 @@
 }
 
 void VideoFrameSubmitter::SetIsSurfaceVisible(bool is_visible) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   is_surface_visible_ = is_visible;
-  UpdateSubmissionStateInternal();
+  UpdateSubmissionState();
 }
 
 void VideoFrameSubmitter::SetIsPageVisible(bool is_visible) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   is_page_visible_ = is_visible;
-  UpdateSubmissionStateInternal();
+  UpdateSubmissionState();
 }
 
 void VideoFrameSubmitter::SetForceSubmit(bool force_submit) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   force_submit_ = force_submit;
-  UpdateSubmissionStateInternal();
+  UpdateSubmissionState();
 }
 
-void VideoFrameSubmitter::UpdateSubmissionStateInternal() {
-  if (compositor_frame_sink_) {
-    compositor_frame_sink_->SetNeedsBeginFrame(IsDrivingFrameUpdates());
-    if (ShouldSubmit())
-      SubmitSingleFrame();
-    else if (!frame_size_.IsEmpty())
-      SubmitEmptyFrame();
-  }
+void VideoFrameSubmitter::UpdateSubmissionState() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (!compositor_frame_sink_)
+    return;
+
+  compositor_frame_sink_->SetNeedsBeginFrame(IsDrivingFrameUpdates());
+
+  // TODO(dalecurtis): Document how these are responsible for saving significant
+  // amounts of cc memory by dealing with off-screen resources more efficiently.
+  if (ShouldSubmit())
+    SubmitSingleFrame();
+  else if (!frame_size_.IsEmpty())
+    SubmitEmptyFrame();
 }
 
 void VideoFrameSubmitter::StopUsingProvider() {
-  DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (is_rendering_)
     StopRendering();
   video_frame_provider_ = nullptr;
 }
 
 void VideoFrameSubmitter::StopRendering() {
-  DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(is_rendering_);
   DCHECK(video_frame_provider_);
 
   is_rendering_ = false;
-  UpdateSubmissionStateInternal();
+  UpdateSubmissionState();
 }
 
 void VideoFrameSubmitter::SubmitSingleFrame() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
   // If we haven't gotten a valid result yet from |context_provider_callback_|
-  // |resource_provider_| will remain uninitalized.
+  // |resource_provider_| will remain uninitialized.
   // |video_frame_provider_| may be null if StopUsingProvider has been called,
   // which could happen if the |video_frame_provider_| is destructing while we
   // are waiting for the ContextProvider.
   if (!resource_provider_->IsInitialized() || !video_frame_provider_)
     return;
 
-  viz::BeginFrameAck current_begin_frame_ack =
-      viz::BeginFrameAck::CreateManualAckWithDamage();
-  scoped_refptr<media::VideoFrame> video_frame =
-      video_frame_provider_->GetCurrentFrame();
-  if (video_frame) {
-    base::SequencedTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE,
-        base::BindOnce(base::IgnoreResult(&VideoFrameSubmitter::SubmitFrame),
-                       weak_ptr_factory_.GetWeakPtr(), current_begin_frame_ack,
-                       video_frame));
-    video_frame_provider_->PutCurrentFrame();
-  }
+  auto video_frame = video_frame_provider_->GetCurrentFrame();
+  if (!video_frame)
+    return;
+
+  // TODO(dalecurtis): This probably shouldn't be posted since it runs the risk
+  // of having state change out from under it. All call sites into this method
+  // should be from posted tasks so it should be safe to remove the post.
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(base::IgnoreResult(&VideoFrameSubmitter::SubmitFrame),
+                     weak_ptr_factory_.GetWeakPtr(),
+                     viz::BeginFrameAck::CreateManualAckWithDamage(),
+                     std::move(video_frame)));
+
+  video_frame_provider_->PutCurrentFrame();
 }
 
 bool VideoFrameSubmitter::ShouldSubmit() const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   return (is_surface_visible_ && is_page_visible_) || force_submit_;
 }
 
 bool VideoFrameSubmitter::IsDrivingFrameUpdates() const {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
   // We drive frame updates only when we believe that something is consuming
   // them.  This is different than VideoLayer, which drives updates any time
   // they're in the layer tree.
@@ -152,18 +169,17 @@
 }
 
 void VideoFrameSubmitter::DidReceiveFrame() {
-  DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(video_frame_provider_);
 
-  // DidReceiveFrame is called before renderering has started, as a part of
-  // PaintSingleFrame.
-  if (!is_rendering_) {
+  // DidReceiveFrame is called before rendering has started, as a part of
+  // VideoRendererSink::PaintSingleFrame.
+  if (!is_rendering_)
     SubmitSingleFrame();
-  }
 }
 
 void VideoFrameSubmitter::StartRendering() {
-  DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(!is_rendering_);
   is_rendering_ = true;
 
@@ -172,19 +188,21 @@
 }
 
 void VideoFrameSubmitter::Initialize(cc::VideoFrameProvider* provider) {
-  DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
-  if (provider) {
-    DCHECK(!video_frame_provider_);
-    video_frame_provider_ = provider;
-    context_provider_callback_.Run(
-        nullptr, base::BindOnce(&VideoFrameSubmitter::OnReceivedContextProvider,
-                                weak_ptr_factory_.GetWeakPtr()));
-  }
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (!provider)
+    return;
+
+  DCHECK(!video_frame_provider_);
+  video_frame_provider_ = provider;
+  context_provider_callback_.Run(
+      nullptr, base::BindOnce(&VideoFrameSubmitter::OnReceivedContextProvider,
+                              weak_ptr_factory_.GetWeakPtr()));
 }
 
 void VideoFrameSubmitter::OnReceivedContextProvider(
     bool use_gpu_compositing,
     scoped_refptr<viz::ContextProvider> context_provider) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (!use_gpu_compositing) {
     resource_provider_->Initialize(nullptr, this);
     return;
@@ -222,7 +240,7 @@
 }
 
 void VideoFrameSubmitter::StartSubmitting() {
-  DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(frame_sink_id_.is_valid());
 
   mojom::blink::EmbeddedFrameSinkProviderPtr provider;
@@ -239,15 +257,16 @@
   compositor_frame_sink_.set_connection_error_handler(base::BindOnce(
       &VideoFrameSubmitter::OnContextLost, base::Unretained(this)));
 
-  UpdateSubmissionStateInternal();
+  UpdateSubmissionState();
 }
 
 bool VideoFrameSubmitter::SubmitFrame(
     const viz::BeginFrameAck& begin_frame_ack,
     scoped_refptr<media::VideoFrame> video_frame) {
-  TRACE_EVENT0("media", "VideoFrameSubmitter::SubmitFrame");
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(video_frame);
-  DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
+  TRACE_EVENT0("media", "VideoFrameSubmitter::SubmitFrame");
+
   if (!compositor_frame_sink_ || !ShouldSubmit())
     return false;
 
@@ -276,9 +295,10 @@
                       gfx::Transform());
   render_pass->filters = cc::FilterOperations();
   resource_provider_->AppendQuads(render_pass.get(), video_frame, rotation_,
-                                  is_opaque_);
+                                  media::IsOpaque(video_frame->format()));
   compositor_frame.metadata.begin_frame_ack = begin_frame_ack;
   compositor_frame.metadata.frame_token = ++next_frame_token_;
+
   // We don't assume that the ack is marked as having damage.  However, we're
   // definitely emitting a CompositorFrame that damages the entire surface.
   compositor_frame.metadata.begin_frame_ack.has_damage = true;
@@ -286,25 +306,24 @@
   compositor_frame.metadata.may_contain_video = true;
 
   std::vector<viz::ResourceId> resources;
-  DCHECK_LE(render_pass->quad_list.size(), 1u);
   if (!render_pass->quad_list.empty()) {
-    for (viz::ResourceId resource_id :
-         render_pass->quad_list.front()->resources) {
-      resources.push_back(resource_id);
-    }
+    DCHECK_EQ(render_pass->quad_list.size(), 1u);
+    resources.assign(render_pass->quad_list.front()->resources.begin(),
+                     render_pass->quad_list.front()->resources.end());
   }
   resource_provider_->PrepareSendToParent(resources,
                                           &compositor_frame.resource_list);
   compositor_frame.render_pass_list.push_back(std::move(render_pass));
+
+  const auto& current_surface =
+      child_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation();
   compositor_frame.metadata.local_surface_id_allocation_time =
-      child_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation()
-          .allocation_time();
+      current_surface.allocation_time();
 
   // TODO(lethalantidote): Address third/fourth arg in SubmitCompositorFrame.
   compositor_frame_sink_->SubmitCompositorFrame(
-      child_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation()
-          .local_surface_id(),
-      std::move(compositor_frame), nullptr, 0);
+      current_surface.local_surface_id(), std::move(compositor_frame), nullptr,
+      0);
   resource_provider_->ReleaseFrameResources();
 
   waiting_for_compositor_ack_ = true;
@@ -312,10 +331,10 @@
 }
 
 void VideoFrameSubmitter::SubmitEmptyFrame() {
-  TRACE_EVENT0("media", "VideoFrameSubmitter::SubmitEmptyFrame");
-  DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(compositor_frame_sink_ && !ShouldSubmit());
   DCHECK(!frame_size_.IsEmpty());
+  TRACE_EVENT0("media", "VideoFrameSubmitter::SubmitEmptyFrame");
 
   viz::CompositorFrame compositor_frame;
 
@@ -324,9 +343,12 @@
   compositor_frame.metadata.frame_token = ++next_frame_token_;
   compositor_frame.metadata.device_scale_factor = 1;
   compositor_frame.metadata.may_contain_video = true;
+
+  const auto& current_surface =
+      child_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation();
+
   compositor_frame.metadata.local_surface_id_allocation_time =
-      child_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation()
-          .allocation_time();
+      current_surface.allocation_time();
 
   std::unique_ptr<viz::RenderPass> render_pass = viz::RenderPass::Create();
   render_pass->SetNew(1, gfx::Rect(frame_size_), gfx::Rect(frame_size_),
@@ -334,17 +356,17 @@
   compositor_frame.render_pass_list.push_back(std::move(render_pass));
 
   compositor_frame_sink_->SubmitCompositorFrame(
-      child_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation()
-          .local_surface_id(),
-      std::move(compositor_frame), nullptr, 0);
+      current_surface.local_surface_id(), std::move(compositor_frame), nullptr,
+      0);
   waiting_for_compositor_ack_ = true;
 }
 
 void VideoFrameSubmitter::OnBeginFrame(
     const viz::BeginFrameArgs& args,
     WTF::HashMap<uint32_t, ::gfx::mojom::blink::PresentationFeedbackPtr>) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   TRACE_EVENT0("media", "VideoFrameSubmitter::OnBeginFrame");
-  DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
+
   viz::BeginFrameAck current_begin_frame_ack(args, false);
   if (args.type == viz::BeginFrameArgs::MISSED) {
     compositor_frame_sink_->DidNotProduceFrame(current_begin_frame_ack);
@@ -383,6 +405,8 @@
 }
 
 void VideoFrameSubmitter::OnContextLost() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
   // TODO(lethalantidote): This check will be obsolete once other TODO to move
   // field initialization earlier is fulfilled.
   if (frame_sink_destroyed_callback_)
@@ -391,9 +415,9 @@
   if (binding_.is_bound())
     binding_.Unbind();
 
-  if (context_provider_) {
+  if (context_provider_)
     context_provider_->RemoveObserver(this);
-  }
+
   waiting_for_compositor_ack_ = false;
 
   resource_provider_->OnContextLost();
@@ -418,15 +442,14 @@
 
 void VideoFrameSubmitter::DidReceiveCompositorFrameAck(
     const WTF::Vector<viz::ReturnedResource>& resources) {
-  DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   ReclaimResources(resources);
-
   waiting_for_compositor_ack_ = false;
 }
 
 void VideoFrameSubmitter::ReclaimResources(
     const WTF::Vector<viz::ReturnedResource>& resources) {
-  DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   WebVector<viz::ReturnedResource> temp_resources = resources;
   std::vector<viz::ReturnedResource> std_resources =
       temp_resources.ReleaseVector();
@@ -436,12 +459,14 @@
 void VideoFrameSubmitter::DidAllocateSharedBitmap(
     mojo::ScopedSharedBufferHandle buffer,
     const viz::SharedBitmapId& id) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(compositor_frame_sink_);
   compositor_frame_sink_->DidAllocateSharedBitmap(
       std::move(buffer), SharedBitmapIdToGpuMailboxPtr(id));
 }
 
 void VideoFrameSubmitter::DidDeleteSharedBitmap(const viz::SharedBitmapId& id) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(compositor_frame_sink_);
   compositor_frame_sink_->DidDeleteSharedBitmap(
       SharedBitmapIdToGpuMailboxPtr(id));
@@ -450,6 +475,7 @@
 void VideoFrameSubmitter::SetSurfaceIdForTesting(
     const viz::SurfaceId& surface_id,
     base::TimeTicks allocation_time) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   frame_sink_id_ = surface_id.frame_sink_id();
   child_local_surface_id_allocator_.UpdateFromParent(
       viz::LocalSurfaceIdAllocation(surface_id.local_surface_id(),
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_submitter.h b/third_party/blink/renderer/platform/graphics/video_frame_submitter.h
index 42df64a..e81bfcd5 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_submitter.h
+++ b/third_party/blink/renderer/platform/graphics/video_frame_submitter.h
@@ -28,19 +28,18 @@
 
 // This single-threaded class facilitates the communication between the media
 // stack and browser renderer, providing compositor frames containing video
-// frames and corresponding resources to the |compositor_frame_sink_|. This
-// class has dependencies on classes that use the media thread's OpenGL
-// ContextProvider, and thus, besides construction, should be consistently ran
-// from the same media SingleThreadTaskRunner.
+// frames and corresponding resources to the |compositor_frame_sink_|.
+//
+// This class requires and uses a viz::ContextProvider, and thus, besides
+// construction, must be consistently accessed from the same thread.
 class PLATFORM_EXPORT VideoFrameSubmitter
     : public WebVideoFrameSubmitter,
       public viz::ContextLostObserver,
       public viz::SharedBitmapReporter,
       public viz::mojom::blink::CompositorFrameSinkClient {
  public:
-  explicit VideoFrameSubmitter(WebContextProviderCallback,
-                               std::unique_ptr<VideoFrameResourceProvider>);
-
+  VideoFrameSubmitter(WebContextProviderCallback,
+                      std::unique_ptr<VideoFrameResourceProvider>);
   ~VideoFrameSubmitter() override;
 
   bool Rendering() { return is_rendering_; }
@@ -61,7 +60,6 @@
   // WebVideoFrameSubmitter implementation.
   void Initialize(cc::VideoFrameProvider*) override;
   void SetRotation(media::VideoRotation) override;
-  void SetIsOpaque(bool) override;
   void EnableSubmission(viz::SurfaceId,
                         base::TimeTicks local_surface_id_allocation_time,
                         WebFrameSinkDestroyedCallback) override;
@@ -102,15 +100,15 @@
   friend class VideoFrameSubmitterTest;
 
   void StartSubmitting();
-  void UpdateSubmissionStateInternal();
+  void UpdateSubmissionState();
+
   // Returns whether a frame was submitted.
   bool SubmitFrame(const viz::BeginFrameAck&, scoped_refptr<media::VideoFrame>);
   void SubmitEmptyFrame();
 
-  // Pulls frame and submits it to compositor.
-  // Used in cases like PaintSingleFrame, which occurs before video rendering
-  // has started to post a poster image, or to submit a final frame before
-  // ending rendering.
+  // Pulls frame and submits it to compositor. Used in cases like
+  // DidReceiveFrame(), which occurs before video rendering has started to post
+  // the first frame or to submit a final frame before ending rendering.
   void SubmitSingleFrame();
 
   // Return whether the submitter should submit frames based on its current
@@ -142,7 +140,6 @@
   // Needs to be initialized in implementation because media isn't a public_dep
   // of blink/platform.
   media::VideoRotation rotation_;
-  bool is_opaque_ = true;
 
   viz::FrameSinkId frame_sink_id_;
 
@@ -158,7 +155,7 @@
   const bool enable_surface_synchronization_;
   viz::FrameTokenGenerator next_frame_token_;
 
-  THREAD_CHECKER(media_thread_checker_);
+  THREAD_CHECKER(thread_checker_);
 
   base::WeakPtrFactory<VideoFrameSubmitter> weak_ptr_factory_;
 
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_submitter_test.cc b/third_party/blink/renderer/platform/graphics/video_frame_submitter_test.cc
index 5123814..62e37f91 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_submitter_test.cc
+++ b/third_party/blink/renderer/platform/graphics/video_frame_submitter_test.cc
@@ -145,9 +145,6 @@
         video_frame_provider_(new StrictMock<MockVideoFrameProvider>()),
         context_provider_(viz::TestContextProvider::Create()) {
     context_provider_->BindToCurrentThread();
-  }
-
-  void SetUp() override {
     MakeSubmitter();
     scoped_task_environment_.RunUntilIdle();
   }
@@ -473,138 +470,6 @@
   scoped_task_environment_.RunUntilIdle();
 }
 
-TEST_F(VideoFrameSubmitterTest, IsOpaquePassedToResourceProvider) {
-  // Check to see if is_opaque is communicated pre-rendering.
-  EXPECT_FALSE(submitter_->Rendering());
-
-  // We submit a frame on opacity change.
-  EXPECT_CALL(*sink_, SetNeedsBeginFrame(false));
-  EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
-      .WillOnce(Return(media::VideoFrame::CreateFrame(
-          media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
-          gfx::Size(8, 8), base::TimeDelta())));
-  EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _)).Times(1);
-  EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
-  EXPECT_CALL(*resource_provider_, AppendQuads(_, _, _, _));
-  EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
-  EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
-
-  submitter_->SetIsOpaque(false);
-  scoped_task_environment_.RunUntilIdle();
-
-  {
-    WTF::Vector<viz::ReturnedResource> resources;
-    EXPECT_CALL(*resource_provider_, ReceiveReturnsFromParent(_));
-    submitter_->DidReceiveCompositorFrameAck(resources);
-  }
-
-  EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
-      .WillOnce(Return(media::VideoFrame::CreateFrame(
-          media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
-          gfx::Size(8, 8), base::TimeDelta())));
-  EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _));
-  EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
-  EXPECT_CALL(*resource_provider_, AppendQuads(_, _, _, false));
-  EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
-  EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
-
-  submitter_->DidReceiveFrame();
-  scoped_task_environment_.RunUntilIdle();
-
-  {
-    WTF::Vector<viz::ReturnedResource> resources;
-    EXPECT_CALL(*resource_provider_, ReceiveReturnsFromParent(_));
-    submitter_->DidReceiveCompositorFrameAck(resources);
-  }
-
-  // Check to see if an update to is_opaque just before rendering is
-  // communicated.
-  EXPECT_CALL(*sink_, SetNeedsBeginFrame(false));
-  EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
-      .WillOnce(Return(media::VideoFrame::CreateFrame(
-          media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
-          gfx::Size(8, 8), base::TimeDelta())));
-  EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _)).Times(1);
-  EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
-  EXPECT_CALL(*resource_provider_, AppendQuads(_, _, _, _));
-  EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
-  EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
-  submitter_->SetIsOpaque(true);
-  scoped_task_environment_.RunUntilIdle();
-
-  EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
-  submitter_->StartRendering();
-  scoped_task_environment_.RunUntilIdle();
-
-  {
-    WTF::Vector<viz::ReturnedResource> resources;
-    EXPECT_CALL(*resource_provider_, ReceiveReturnsFromParent(_));
-    submitter_->DidReceiveCompositorFrameAck(resources);
-  }
-
-  EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame(_, _))
-      .WillOnce(Return(true));
-  EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
-      .WillOnce(Return(media::VideoFrame::CreateFrame(
-          media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
-          gfx::Size(8, 8), base::TimeDelta())));
-  EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _));
-  EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
-  EXPECT_CALL(*resource_provider_, AppendQuads(_, _, _, true));
-  EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
-  EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
-
-  viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
-      BEGINFRAME_FROM_HERE, now_src_.get());
-  submitter_->OnBeginFrame(args, {});
-  scoped_task_environment_.RunUntilIdle();
-
-  {
-    WTF::Vector<viz::ReturnedResource> resources;
-    EXPECT_CALL(*resource_provider_, ReceiveReturnsFromParent(_));
-    submitter_->DidReceiveCompositorFrameAck(resources);
-  }
-
-  // Check to see if changing is_opaque while rendering is handled.
-  EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
-  EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
-      .WillOnce(Return(media::VideoFrame::CreateFrame(
-          media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
-          gfx::Size(8, 8), base::TimeDelta())));
-  EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _)).Times(1);
-  EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
-  EXPECT_CALL(*resource_provider_, AppendQuads(_, _, _, _));
-  EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
-  EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
-
-  submitter_->SetIsOpaque(false);
-  scoped_task_environment_.RunUntilIdle();
-
-  {
-    WTF::Vector<viz::ReturnedResource> resources;
-    EXPECT_CALL(*resource_provider_, ReceiveReturnsFromParent(_));
-    submitter_->DidReceiveCompositorFrameAck(resources);
-  }
-
-  EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame(_, _))
-      .WillOnce(Return(true));
-  EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
-      .WillOnce(Return(media::VideoFrame::CreateFrame(
-          media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
-          gfx::Size(8, 8), base::TimeDelta())));
-  EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _));
-  EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
-  EXPECT_CALL(*resource_provider_, AppendQuads(_, _, _, false));
-  EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
-  EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
-
-  submitter_->OnBeginFrame(args, {});
-  scoped_task_environment_.RunUntilIdle();
-
-  // Updating |is_opaque_| with the same value should not cause a frame submit.
-  submitter_->SetIsOpaque(false);
-}
-
 TEST_F(VideoFrameSubmitterTest, OnBeginFrameSubmitsFrame) {
   EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.cc b/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.cc
index 01674de..497d88b 100644
--- a/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.cc
@@ -123,13 +123,6 @@
   SetAllowImagePlaceholder();
 }
 
-void FetchParameters::SetLazyImageDeferred() {
-  resource_request_.SetPreviewsState(resource_request_.GetPreviewsState() |
-                                     WebURLRequest::kLazyImageLoadDeferred);
-  DCHECK_EQ(kNone, image_request_optimization_);
-  image_request_optimization_ = kDeferImageLoad;
-}
-
 void FetchParameters::SetAllowImagePlaceholder() {
   DCHECK_EQ(kNone, image_request_optimization_);
   if (!resource_request_.Url().ProtocolIsInHTTPFamily() ||
diff --git a/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h b/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h
index 76b6099..229410e 100644
--- a/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h
+++ b/third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h
@@ -182,10 +182,9 @@
   // Client LoFi preview bit.
   void SetClientLoFiPlaceholder();
 
-  // Configures the request to load an image as a placeholder or defers the
-  // image and sets the lazy image load bit.
+  // Configures the request to load an image as a placeholder and sets the
+  // lazy image load bit.
   void SetLazyImagePlaceholder();
-  void SetLazyImageDeferred();
 
   // Configures the request to load an image placeholder if the request is
   // eligible (e.g. the url's protocol is HTTP, etc.). If this request is
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 0c6f40f..84a1b98 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1308,6 +1308,9 @@
       status: "stable",
     },
     {
+      name: "UserActivationSameOriginVisibility",
+    },
+    {
       name: "UserActivationV2",
     },
     {
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 463c38e..b3cc6d4 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -81,6 +81,17 @@
 # The following tests passes only with User Activation v2 (UAv2) disabled.
 crbug.com/908841 external/wpt/fullscreen/api/element-ready-check-containing-iframe-manual.html [ Timeout ]
 
+# The following tests fail with UserActivationSameOriginVisibility plus UAv2.
+crbug.com/922725 external/wpt/fullscreen/api/element-ready-check-containing-iframe-manual.tentative.html [ Failure ]
+crbug.com/922725 external/wpt/html/user-activation/activation-api-iframe-no-activate.tenative.html [ Failure ]
+crbug.com/922725 fullscreen/full-screen-iframe-zIndex.html [ Timeout ]
+crbug.com/922725 virtual/android/fullscreen/full-screen-iframe-zIndex.html [ Timeout ]
+crbug.com/922725 virtual/user-activation-v2/fullscreen/full-screen-iframe-zIndex.html [ Timeout ]
+crbug.com/922725 http/tests/media/autoplay/document-user-activation-cross-origin-feature-policy-disabled.html [ Failure ]
+crbug.com/922725 http/tests/media/autoplay/document-user-activation-cross-origin-feature-policy-header.html [ Failure ]
+crbug.com/922725 virtual/user-activation-v2/http/tests/media/autoplay/document-user-activation-cross-origin-feature-policy-disabled.html [ Failure ]
+crbug.com/922725 virtual/user-activation-v2/http/tests/media/autoplay/document-user-activation-cross-origin-feature-policy-header.html [ Failure ]
+
 # The following fail only on Mac.
 crbug.com/891427 [ Mac ] fast/events/touch/gesture/touch-gesture-scroll-listbox.html [ Pass Failure Timeout Crash ]
 crbug.com/891427 [ Mac ] virtual/scroll_customization/fast/events/touch/gesture/touch-gesture-scroll-listbox.html [ Pass Failure Timeout Crash ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 5d233a7..2dd79ac 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -29,11 +29,6 @@
     "base": "animations/animationworklet",
     "args": ["--enable-threaded-compositing"]
   },
-   {
-    "prefix": "threaded",
-    "base": "external/wpt/animation-worklet",
-    "args": ["--enable-threaded-compositing"]
-  },
   {
     "prefix": "threaded",
     "base": "lifecycle",
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse.html b/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse.html
index 7a31a7e1..f347a0b 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse.html
@@ -41,7 +41,7 @@
         test_equals(entry.fetchStart, entry.connectStart, 'connectStart and fetchStart should be the same');
         test_equals(entry.fetchStart, entry.connectEnd, 'connectEnd and fetchStart should be the same');
         if(!window.isSecureConnection) {
-            test_equals(entry.secureConnectionStart, 0, 'secureConnectStart should be zero');
+            test_equals(entry.secureConnectionStart, 0, 'secureConnectionStart should be zero');
         }
         test_equals(entry.fetchStart, entry.domainLookupStart, 'domainLookupStart and fetchStart should be the same')
         test_equals(entry.fetchStart, entry.domainLookupEnd, 'domainLookupEnd and fetchStart should be the same')
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse.https.html b/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse.https.html
index bc79a851..8686409 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse.https.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse.https.html
@@ -40,7 +40,7 @@
         const entry = entries[1];
         test_equals(entry.fetchStart, entry.connectStart, 'connectStart and fetchStart should be the same');
         test_equals(entry.fetchStart, entry.connectEnd, 'connectEnd and fetchStart should be the same');
-        test_equals(entry.fetchStart, entry.secureConnectionStart, 'secureConnectStart and fetchStart should be the same');
+        test_equals(entry.fetchStart, entry.secureConnectionStart, 'secureConnectionStart and fetchStart should be the same');
         test_equals(entry.fetchStart, entry.domainLookupStart, 'domainLookupStart and fetchStart should be the same')
         test_equals(entry.fetchStart, entry.domainLookupEnd, 'domainLookupEnd and fetchStart should be the same')
     }
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse_mixed_content.html b/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse_mixed_content.html
new file mode 100644
index 0000000..51c04ee
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse_mixed_content.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing connection reuse</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+setup({explicit_done: true});
+let iframe;
+let d;
+let body;
+
+// Explicitly test the namespace before we start testing.
+test_namespace('getEntriesByType');
+
+function setup_iframe() {
+    iframe = document.getElementById('frameContext');
+    d = iframe.contentWindow.document;
+    iframe.addEventListener('load', onload_test, false);
+}
+
+function onload_test() {
+    const entries = iframe.contentWindow.performance.getEntriesByType('resource');
+
+    // When a persistent connection is used, follow-on resources should be included as PerformanceResourceTiming objects.
+    test_equals(entries.length, 2, 'There should be 2 PerformanceEntries');
+
+    if (entries.length >= 2) {
+        // When a persistent connection is used, for the resource that reuses the socket, connectStart and connectEnd should have the same value as fetchStart.
+        const entry = entries[1];
+        test_equals(entry.fetchStart, entry.connectStart, 'connectStart and fetchStart should be the same');
+        test_equals(entry.fetchStart, entry.connectEnd, 'connectEnd and fetchStart should be the same');
+        // secureConnectionStart is the same as fetchStart since the subresource is fetched over https
+        test_equals(entry.fetchStart, entry.secureConnectionStart, 'secureConnectionStart and fetchStart should be the same');
+        test_equals(entry.fetchStart, entry.domainLookupStart, 'domainLookupStart and fetchStart should be the same')
+        test_equals(entry.fetchStart, entry.domainLookupEnd, 'domainLookupEnd and fetchStart should be the same')
+    }
+
+    done();
+}
+
+window.setup_iframe = setup_iframe;
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that connectStart and connectEnd are the same when a connection is reused (e.g. when a persistent connection is used).</p>
+<div id="log"></div>
+<iframe id="frameContext" src="resources/fake_responses_https.sub.html"></iframe>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse_mixed_content_redirect.html b/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse_mixed_content_redirect.html
new file mode 100644
index 0000000..a46d14c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resource_connection_reuse_mixed_content_redirect.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<title>Resource Timing connection reuse</title>
+<link rel="author" title="Google" href="http://www.google.com/" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/webperftestharness.js"></script>
+<script src="resources/webperftestharnessextension.js"></script>
+<script>
+setup({explicit_done: true});
+let iframe;
+let d;
+let body;
+
+// Explicitly test the namespace before we start testing.
+test_namespace('getEntriesByType');
+
+function setup_iframe() {
+    iframe = document.getElementById('frameContext');
+    d = iframe.contentWindow.document;
+    iframe.addEventListener('load', onload_test, false);
+}
+
+function onload_test() {
+    const entries = iframe.contentWindow.performance.getEntriesByType('resource');
+
+    // When a persistent connection is used, follow-on resources should be included as PerformanceResourceTiming objects.
+    test_equals(entries.length, 2, 'There should be 2 PerformanceEntries');
+
+    if (entries.length >= 2) {
+        // When a persistent connection is used, for the resource that reuses the socket, connectStart and connectEnd should have the same value as fetchStart.
+        const entry = entries[1];
+        test_equals(entry.fetchStart, entry.connectStart, 'connectStart and fetchStart should be the same');
+        test_equals(entry.fetchStart, entry.connectEnd, 'connectEnd and fetchStart should be the same');
+        // secureConnectionStart is the same as fetchStart since the subresource is eventually redirected to https.
+        test_equals(entry.fetchStart, entry.secureConnectionStart, 'secureConnectionStart and fetchStart should be the same');
+        test_equals(entry.fetchStart, entry.domainLookupStart, 'domainLookupStart and fetchStart should be the same')
+        test_equals(entry.fetchStart, entry.domainLookupEnd, 'domainLookupEnd and fetchStart should be the same')
+    }
+
+    done();
+}
+
+window.setup_iframe = setup_iframe;
+</script>
+</head>
+<body>
+<h1>Description</h1>
+<p>This test validates that connectStart and connectEnd are the same when a connection is reused (e.g. when a persistent connection is used).</p>
+<div id="log"></div>
+<iframe id="frameContext" src="resources/fake_responses_https_redirect.sub.html"></iframe>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resources/fake_responses.py b/third_party/blink/web_tests/external/wpt/resource-timing/resources/fake_responses.py
index f7169381..289c179 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/resources/fake_responses.py
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resources/fake_responses.py
@@ -2,13 +2,20 @@
 
 def main(request, response):
     tag = request.GET.first("tag", None)
+    redirect = request.GET.first("redirect", None)
     match = request.headers.get("If-None-Match", None)
     date = request.GET.first("date", "")
     modified = request.headers.get("If-Modified-Since", None)
+    response.headers.set("Access-Control-Allow-Origin", "*");
+    response.headers.set("Timing-Allow-Origin", "*");
     if tag:
         response.headers.set("ETag", '"%s"' % tag)
     elif date:
         response.headers.set("Last-Modified", date)
+    if redirect:
+        response.headers.set("Location", redirect)
+        response.status = (302, "Moved")
+        return ""
 
     if ((match is not None and match == tag) or
         (modified is not None and modified == date)):
@@ -16,4 +23,4 @@
         return ""
     else:
         response.headers.set("Content-Type", "text/plain")
-        return "MAYBE NOT"
\ No newline at end of file
+        return "MAYBE NOT"
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resources/fake_responses_https.sub.html b/third_party/blink/web_tests/external/wpt/resource-timing/resources/fake_responses_https.sub.html
new file mode 100644
index 0000000..cf49fb9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resources/fake_responses_https.sub.html
@@ -0,0 +1,18 @@
+<body>
+<script>
+function request() {
+  var client = new XMLHttpRequest,
+      baseurl = "https://{{hosts[][www]}}:{{ports[https][0]}}{{location[pathname]}}",
+      url = new URL("fake_responses.py", baseurl).href;
+    client.open("GET", url, false)
+    client.send(null)
+    client.open("GET", url, false)
+    client.send(null)
+}
+
+if(window.parent.setup_iframe) {
+  window.parent.setup_iframe();
+  request();
+}
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resources/fake_responses_https_redirect.sub.html b/third_party/blink/web_tests/external/wpt/resource-timing/resources/fake_responses_https_redirect.sub.html
new file mode 100644
index 0000000..c55e037d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resources/fake_responses_https_redirect.sub.html
@@ -0,0 +1,20 @@
+<body>
+<script>
+function request() {
+  var client = new XMLHttpRequest,
+      baseurl = "http://{{hosts[][www]}}:{{ports[http][0]}}{{location[pathname]}}",
+      subresource = "fake_responses.py",
+      redirecturl = new URL(subresource, "https://{{hosts[][www]}}:{{ports[https][0]}}{{location[pathname]}}").href,
+      url = new URL(subresource + "?redirect=" + redirecturl, baseurl).href;
+    client.open("GET", url, false)
+    client.send(null)
+    client.open("GET", url, false)
+    client.send(null)
+}
+
+if(window.parent.setup_iframe) {
+  window.parent.setup_iframe();
+  request();
+}
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/workers/README.md b/third_party/blink/web_tests/external/wpt/workers/README.md
index 78cc74371..b78a05e 100644
--- a/third_party/blink/web_tests/external/wpt/workers/README.md
+++ b/third_party/blink/web_tests/external/wpt/workers/README.md
@@ -1,2 +1,137 @@
+# Worker WPT tests
+
 These are the workers (`Worker`, `SharedWorker`) tests for the
 [Web workers chapter of the HTML Standard](https://html.spec.whatwg.org/multipage/workers.html).
+
+See also
+[testharness.js API > Web Workers](https://web-platform-tests.org/writing-tests/testharness-api.html#web-workers).
+
+## Writing `*.any.js`
+
+The easiest and most recommended way to write tests for workers
+is to create .any.js-style tests.
+
+Official doc:
+[WPT > File Name Flags > Test Features](https://web-platform-tests.org/writing-tests/file-names.html#test-features).
+
+- Standard `testharness.js`-style can be used (and is enforced).
+- The same test can be run on window and many types of workers.
+- All glue code are automatically generated.
+- No need to care about how to create and communicate with each type of workers,
+  thanks to `fetch_tests_from_worker` in `testharness.js`.
+
+Converting existing tests into `.any.js`-style also has benefits:
+
+- Multiple tests can be merged into one.
+- Tests written for window can be run on workers
+  with a very low development cost.
+
+### How to write tests
+
+If you write `testharness.js`-based tests in `foo.any.js` and
+specify types of workers to be tested,
+the test can run on any of dedicated, shared and service workers.
+
+See `examples/general.any.js` for example.
+
+Even for testing specific features in a specific type of workers
+(e.g. shared worker's `onconnect`), `.any.js`-style tests can be used.
+
+See `examples/onconnect.any.js` for example.
+
+### How to debug tests
+
+Whether each individual test passed or failed,
+and its assertion failures (if any) are all reported in the final results.
+
+`console.log()` might not appear in the test results and
+thus might not be useful for printf debugging.
+For example, in Chromium, this message
+
+- Appears (in stderr) on a window or a dedicated worker, but
+- Does NOT appear on a shared worker or a service worker.
+
+### How it works
+
+`.any.js`-style tests use
+`fetch_tests_from_worker` functionality of `testharness.js`.
+
+The WPT test server generates necessary glue code
+(including generated Document HTML and worker top-level scripts).
+See
+[serve.py](https://github.com/web-platform-tests/wpt/blob/master/tools/serve/serve.py)
+for the actual glue code.
+
+Note that `.any.js` file is not the worker top-level script,
+and currently we cannot set response headers to the worker top-level script,
+e.g. to set Referrer Policy of the workers.
+
+## Writing `*.worker.js`
+
+Similar to `.any.js`, you can also write `.worker.js`
+for tests only for dedicated workers.
+Almost the same as `.any.js`, except for the things listed below.
+
+Official doc:
+[WPT > File Name Flags > Test Features](https://web-platform-tests.org/writing-tests/file-names.html#test-features).
+
+### How to write tests
+
+You have to write two things manually (which is generated in `.any.js` tests):
+
+- `importScripts("/resources/testharness.js");` at the beginning.
+- `done();` at the bottom.
+
+Note: Even if you write `async_test()` or `promise_test()`,
+this global `done()` is always needed
+(this is different from async_test's `done()`)
+for dedicated workers and shared workers.
+See official doc:
+[testharness.js API > Determining when all tests are complete](https://web-platform-tests.org/writing-tests/testharness-api.html#determining-when-all-tests-are-complete).
+
+See `examples/general.worker.js` for example.
+
+### How it works
+
+`.worker.js`-style tests also use
+`fetch_tests_from_worker` functionality of `testharness.js`.
+
+The WPT test server generates glue code in Document HTML-side,
+but not for worker top-level scripts.
+This is why you have to manually write `importScripts()` etc.
+See
+[serve.py](https://github.com/web-platform-tests/wpt/blob/master/tools/serve/serve.py)
+for the actual glue code.
+
+Unlike `*.any.js` cases, the `*.worker.js` is the worker top-level script.
+
+## Using `fetch_tests_from_worker`
+
+If you need more flexibility,
+writing tests using `fetch_tests_from_worker` is the way to go.
+For example, when
+
+- Additional processing is needed on the parent Document.
+- Workers should be created in a specific way.
+- You are writing non-WPT tests using `testharness.js`.
+
+You have to write the main HTMLs and the worker scripts,
+but most of the glue code needed for running tests on workers
+are provided by `fetch_tests_from_worker`.
+
+### How to write tests
+
+See
+
+- `examples/fetch_tests_from_worker.html` and
+  `examples/fetch_tests_from_worker.js`.
+
+## Writing the whole tests manually
+
+If `fetch_tests_from_worker` isn't suitable for your specific case
+(which should be rare but might be still possible),
+you have to write the whole tests,
+including the main Document HTML, worker scripts,
+and message passing code between them.
+
+TODO: Supply the templates for writing this kind of tests.
diff --git a/third_party/blink/web_tests/external/wpt/workers/examples/fetch_tests_from_worker.html b/third_party/blink/web_tests/external/wpt/workers/examples/fetch_tests_from_worker.html
new file mode 100644
index 0000000..5ac765c7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/examples/fetch_tests_from_worker.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<!--
+  This file is an example of a hand-written test using
+  fetch_tests_from_worker().
+  Unlike *.any.js or *.worker.js tests, fetch_tests_from_worker.html/js files
+  are manually written and no generated glue code are involved.
+-->
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+fetch_tests_from_worker(new Worker("fetch_tests_from_worker.js"));
+
+// If you want to test on SharedWorker,
+// fetch_tests_from_worker(new SharedWorker("fetch_tests_from_worker.js"));
+
+// See ServiceWorkersHandler in
+// https://github.com/web-platform-tests/wpt/blob/master/tools/serve/serve.py
+// for the generated snippet used in .any.js for service workers.
+// Note: when testing service workers, also add ".https." file flag in the
+// main HTML's file name to run the test on HTTPS.
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/workers/examples/fetch_tests_from_worker.js b/third_party/blink/web_tests/external/wpt/workers/examples/fetch_tests_from_worker.js
new file mode 100644
index 0000000..01ba12a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/examples/fetch_tests_from_worker.js
@@ -0,0 +1,28 @@
+// This file is an example of a hand-written test using
+// fetch_tests_from_worker().
+// Unlike *.any.js or *.worker.js tests, fetch_tests_from_worker.html/js files
+// are manually written and no generated glue code are involved.
+
+// fetch_tests_from_worker() requires testharness.js both on the parent
+// document and on the worker.
+importScripts("/resources/testharness.js");
+
+// ============================================================================
+
+// Test body.
+test(() => {
+    assert_equals(1, 1, "1 == 1");
+  },
+  "Test that should pass"
+);
+
+// ============================================================================
+
+// `done()` is always needed at the bottom for dedicated workers and shared
+// workers, even if you write `async_test()` or `promise_test()`.
+// `async_test()` and `promise_test()` called before this `done()`
+// will continue and assertions/failures after this `done()` are not ignored.
+// See
+// https://web-platform-tests.org/writing-tests/testharness-api.html#determining-when-all-tests-are-complete
+// for details.
+done();
diff --git a/third_party/blink/web_tests/external/wpt/workers/examples/general.any.js b/third_party/blink/web_tests/external/wpt/workers/examples/general.any.js
new file mode 100644
index 0000000..2aa82e3d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/examples/general.any.js
@@ -0,0 +1,34 @@
+// META: global=!default,worker
+
+// See
+// https://web-platform-tests.org/writing-tests/testharness.html#multi-global-tests
+// for how to specify in which global scopes to run this tests,
+// how to specify additional scripts needed, etc.
+
+// testharness.js is imported (via importScripts()) by generated glue code by
+// WPT server.
+// See AnyWorkerHandler in
+// https://github.com/web-platform-tests/wpt/blob/master/tools/serve/serve.py.
+
+// ============================================================================
+
+// Test body.
+// .any.js tests are always testharness.js-based.
+test(() => {
+    assert_equals(1, 1, "1 == 1");
+  },
+  "Test that should pass"
+);
+
+test(() => {
+    // This file is "general.any.js" but the worker top-level script is
+    // "general.any.worker.js", which is generated by the WPT server.
+    assert_equals(location.pathname, "/workers/examples/general.any.worker.js");
+  },
+  "Worker top-level script is a generated script."
+);
+
+// done() is NOT needed in .any.js tests, as it is called by generated
+// glue code by the WPT server.
+// See AnyWorkerHandler in
+// https://github.com/web-platform-tests/wpt/blob/master/tools/serve/serve.py.
diff --git a/third_party/blink/web_tests/external/wpt/workers/examples/general.worker.js b/third_party/blink/web_tests/external/wpt/workers/examples/general.worker.js
new file mode 100644
index 0000000..aeca236
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/examples/general.worker.js
@@ -0,0 +1,35 @@
+// This file is an example of a test using *.worker.js mechanism.
+// The parent document that calls fetch_tests_from_worker() is auto-generated
+// but there are no generated code in the worker side.
+
+// fetch_tests_from_worker() requires testharness.js both on the parent
+// document and on the worker.
+importScripts("/resources/testharness.js");
+
+// ============================================================================
+
+// Test body.
+test(() => {
+    assert_equals(1, 1, "1 == 1");
+  },
+  "Test that should pass"
+);
+
+test(() => {
+    // This file is "general.worker.js" and this file itself is the worker
+    // top-level script (which is different from the .any.js case).
+    assert_equals(location.pathname, "/workers/examples/general.worker.js");
+  },
+  "Worker top-level script is the .worker.js file itself."
+);
+
+// ============================================================================
+
+// `done()` is always needed at the bottom for dedicated workers and shared
+// workers, even if you write `async_test()` or `promise_test()`.
+// `async_test()` and `promise_test()` called before this `done()`
+// will continue and assertions/failures after this `done()` are not ignored.
+// See
+// https://web-platform-tests.org/writing-tests/testharness-api.html#determining-when-all-tests-are-complete
+// for details.
+done();
diff --git a/third_party/blink/web_tests/external/wpt/workers/examples/onconnect.any.js b/third_party/blink/web_tests/external/wpt/workers/examples/onconnect.any.js
new file mode 100644
index 0000000..85546f2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/examples/onconnect.any.js
@@ -0,0 +1,4 @@
+// META: global=!default,sharedworker
+const t = async_test("onconnect is called");
+onconnect = t.step_func_done((event) => {
+});
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/window-resize/window-resize-child-background-image-fixed-tiled-expected.txt b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/window-resize/window-resize-child-background-image-fixed-tiled-expected.txt
new file mode 100644
index 0000000..627612a
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/window-resize/window-resize-child-background-image-fixed-tiled-expected.txt
@@ -0,0 +1,191 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [1008, 1016],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "HorizontalScrollbar",
+      "bounds": [600, 250],
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 600, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "HorizontalScrollbar",
+          "rect": [0, 235, 585, 15],
+          "reason": "scroll control"
+        },
+        {
+          "object": "VerticalScrollbar",
+          "rect": [585, 0, 15, 250],
+          "reason": "scroll control"
+        }
+      ]
+    },
+    {
+      "name": "LayoutBlockFlow HTML",
+      "position": [8, 8],
+      "bounds": [1000, 1000],
+      "contentsOpaque": true,
+      "backgroundColor": "#000000",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow DIV id='target'",
+          "rect": [0, 0, 1000, 1000],
+          "reason": "background"
+        }
+      ]
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [1008, 1016],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "HorizontalScrollbar",
+      "bounds": [400, 250],
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 400, 250],
+          "reason": "geometry"
+        },
+        {
+          "object": "HorizontalScrollbar",
+          "rect": [0, 235, 400, 15],
+          "reason": "scroll control"
+        },
+        {
+          "object": "VerticalScrollbar",
+          "rect": [385, 0, 15, 235],
+          "reason": "scroll control"
+        }
+      ]
+    },
+    {
+      "name": "LayoutBlockFlow HTML",
+      "position": [8, 8],
+      "bounds": [1000, 1000],
+      "contentsOpaque": true,
+      "backgroundColor": "#000000",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow DIV id='target'",
+          "rect": [0, 0, 1000, 1000],
+          "reason": "background"
+        }
+      ]
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [1008, 1016],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "HorizontalScrollbar",
+      "bounds": [400, 600],
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 400, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "HorizontalScrollbar",
+          "rect": [0, 585, 385, 15],
+          "reason": "scroll control"
+        },
+        {
+          "object": "HorizontalScrollbar",
+          "rect": [0, 235, 385, 15],
+          "reason": "scroll control"
+        },
+        {
+          "object": "VerticalScrollbar",
+          "rect": [385, 0, 15, 585],
+          "reason": "scroll control"
+        }
+      ]
+    },
+    {
+      "name": "LayoutBlockFlow HTML",
+      "position": [8, 8],
+      "bounds": [1000, 1000],
+      "contentsOpaque": true,
+      "backgroundColor": "#000000",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow DIV id='target'",
+          "rect": [0, 0, 1000, 1000],
+          "reason": "background"
+        }
+      ]
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [1008, 1016],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "HorizontalScrollbar",
+      "bounds": [800, 600],
+      "paintInvalidations": [
+        {
+          "object": "LayoutView #document",
+          "rect": [0, 0, 800, 600],
+          "reason": "geometry"
+        },
+        {
+          "object": "HorizontalScrollbar",
+          "rect": [0, 585, 785, 15],
+          "reason": "scroll control"
+        },
+        {
+          "object": "VerticalScrollbar",
+          "rect": [785, 0, 15, 585],
+          "reason": "scroll control"
+        },
+        {
+          "object": "VerticalScrollbar",
+          "rect": [385, 0, 15, 585],
+          "reason": "scroll control"
+        }
+      ]
+    },
+    {
+      "name": "LayoutBlockFlow HTML",
+      "position": [8, 8],
+      "bounds": [1000, 1000],
+      "contentsOpaque": true,
+      "backgroundColor": "#000000",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow DIV id='target'",
+          "rect": [0, 0, 1000, 1000],
+          "reason": "background"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles/styles-mouse-test.js b/third_party/blink/web_tests/http/tests/devtools/elements/styles/styles-mouse-test.js
index d7a3878..a0dc655 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles/styles-mouse-test.js
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles/styles-mouse-test.js
@@ -69,11 +69,11 @@
       TestRunner.addResult('Not editing');
       return;
     }
-    TestRunner.addResult('Editing: "' + document.deepActiveElement().textContent + '"');
+    TestRunner.addResult('Editing: "' + TestRunner.textContentWithoutStyles(document.deepActiveElement()) + '"');
   }
 
   function mouseDown(element, offset = 0) {
-    TestRunner.addResult('mouse down: ' + element.tagName + ':' + element.textContent);
+    TestRunner.addResult('mouse down: ' + element.tagName + ':' + TestRunner.textContentWithoutStyles(element));
     var rect = element.getBoundingClientRect();
     eventSender.mouseMoveTo((rect.left + rect.right) / 2 + offset, (rect.top + rect.bottom) / 2);
     eventSender.mouseDown();
@@ -81,7 +81,7 @@
   }
 
   function mouseUp(element, offset = 0) {
-    TestRunner.addResult('mouse up: ' + element.tagName + ':' + element.textContent);
+    TestRunner.addResult('mouse up: ' + element.tagName + ':' + TestRunner.textContentWithoutStyles(element));
     var rect = element.getBoundingClientRect();
     eventSender.mouseMoveTo((rect.left + rect.right) / 2 + offset, (rect.top + rect.bottom) / 2);
     eventSender.mouseUp();
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles/up-down-numerics-and-colors.js b/third_party/blink/web_tests/http/tests/devtools/elements/styles/up-down-numerics-and-colors.js
index bc9cff46..217a980b 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles/up-down-numerics-and-colors.js
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles/up-down-numerics-and-colors.js
@@ -36,7 +36,7 @@
         colorTreeElement.valueElement.dispatchEvent(
             TestRunner.createKeyEvent('ArrowDown', /*Ctrl*/ true, /*Alt*/ false, /*Shift*/ true, /*Meta*/ false));
 
-      TestRunner.addResult(colorTreeElement.listItemElement.textContent);
+      TestRunner.addResult(TestRunner.textContentWithoutStyles(colorTreeElement.listItemElement));
       next();
     },
 
@@ -51,7 +51,7 @@
       // Shift + PageUp should change to 11.6
       opacityTreeElement.valueElement.dispatchEvent(
           TestRunner.createKeyEvent('PageUp', /*Ctrl*/ false, /*Alt*/ false, /*Shift*/ true));
-      TestRunner.addResult(opacityTreeElement.listItemElement.textContent);
+      TestRunner.addResult(TestRunner.textContentWithoutStyles(opacityTreeElement.listItemElement));
       next();
     },
 
@@ -67,7 +67,7 @@
       selection.addRange(newRange);
       treeElement.valueElement.dispatchEvent(TestRunner.createKeyEvent('ArrowUp'));
       treeElement.valueElement.dispatchEvent(TestRunner.createKeyEvent('PageUp'));
-      TestRunner.addResult(treeElement.listItemElement.textContent);
+      TestRunner.addResult(TestRunner.textContentWithoutStyles(treeElement.listItemElement));
       next();
     }
   ]);
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar-expected.txt b/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar-expected.txt
index decd73e..95b45ff 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar-expected.txt
@@ -9,6 +9,8 @@
         </STYLE>
         <STYLE type=text/css >
         </STYLE>
+        <STYLE type=text/css >
+        </STYLE>
         <DIV class=tree-outline-disclosure >
             <OL class=tree-outline role=tree tabindex=-1 >
                 <LI role=treeitem class=security-main-view-sidebar-tree-item selected force-white-icons tabindex=0 >
@@ -128,6 +130,8 @@
         </STYLE>
         <STYLE type=text/css >
         </STYLE>
+        <STYLE type=text/css >
+        </STYLE>
         <DIV class=tree-outline-disclosure >
             <OL class=tree-outline role=tree tabindex=-1 >
                 <LI role=treeitem class=security-main-view-sidebar-tree-item selected force-white-icons tabindex=0 >
@@ -247,6 +251,8 @@
         </STYLE>
         <STYLE type=text/css >
         </STYLE>
+        <STYLE type=text/css >
+        </STYLE>
         <DIV class=tree-outline-disclosure >
             <OL class=tree-outline role=tree tabindex=-1 >
                 <LI role=treeitem class=security-main-view-sidebar-tree-item selected force-white-icons tabindex=0 >
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/origin-view-ct-compliance-expected.txt b/third_party/blink/web_tests/http/tests/devtools/security/origin-view-ct-compliance-expected.txt
index af134f2d..ba9f064 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/origin-view-ct-compliance-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/security/origin-view-ct-compliance-expected.txt
@@ -26,18 +26,8 @@
             </SPAN>
         </DIV>
         <DIV class=view-network-button >
-            <BUTTON is=text-button type=button class=origin-button >
+            <BUTTON class=origin-button text-button type=button >
 View requests in Network Panel
-                <#document-fragment >
-                    <STYLE type=text/css >
-                    </STYLE>
-                    <STYLE type=text/css >
-                    </STYLE>
-                    <STYLE type=text/css >
-                    </STYLE>
-                    <CONTENT >
-                    </CONTENT>
-                </#document-fragment>
             </BUTTON>
         </DIV>
     </DIV>
@@ -128,18 +118,8 @@
                 <DIV >
                 </DIV>
                 <DIV >
-                    <BUTTON is=text-button type=button class=origin-button >
+                    <BUTTON class=origin-button text-button type=button >
 Open full certificate details
-                        <#document-fragment >
-                            <STYLE type=text/css >
-                            </STYLE>
-                            <STYLE type=text/css >
-                            </STYLE>
-                            <STYLE type=text/css >
-                            </STYLE>
-                            <CONTENT >
-                            </CONTENT>
-                        </#document-fragment>
                     </BUTTON>
                 </DIV>
             </DIV>
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/origin-view-then-interstitial-expected.txt b/third_party/blink/web_tests/http/tests/devtools/security/origin-view-then-interstitial-expected.txt
index b525227..1a6bee5 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/origin-view-then-interstitial-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/security/origin-view-then-interstitial-expected.txt
@@ -54,18 +54,8 @@
             </SPAN>
         </DIV>
         <DIV class=view-network-button >
-            <BUTTON is=text-button type=button class=origin-button >
+            <BUTTON class=origin-button text-button type=button >
 View requests in Network Panel
-                <#document-fragment >
-                    <STYLE type=text/css >
-                    </STYLE>
-                    <STYLE type=text/css >
-                    </STYLE>
-                    <STYLE type=text/css >
-                    </STYLE>
-                    <CONTENT >
-                    </CONTENT>
-                </#document-fragment>
             </BUTTON>
         </DIV>
     </DIV>
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/security-details-updated-with-security-state-expected.txt b/third_party/blink/web_tests/http/tests/devtools/security/security-details-updated-with-security-state-expected.txt
index 5638d88..9877d71 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/security-details-updated-with-security-state-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/security/security-details-updated-with-security-state-expected.txt
@@ -70,18 +70,8 @@
             </SPAN>
         </DIV>
         <DIV class=view-network-button >
-            <BUTTON is=text-button type=button class=origin-button >
+            <BUTTON class=origin-button text-button type=button >
 View requests in Network Panel
-                <#document-fragment >
-                    <STYLE type=text/css >
-                    </STYLE>
-                    <STYLE type=text/css >
-                    </STYLE>
-                    <STYLE type=text/css >
-                    </STYLE>
-                    <CONTENT >
-                    </CONTENT>
-                </#document-fragment>
             </BUTTON>
         </DIV>
     </DIV>
@@ -172,18 +162,8 @@
                 <DIV >
                 </DIV>
                 <DIV >
-                    <BUTTON is=text-button type=button class=origin-button >
+                    <BUTTON class=origin-button text-button type=button >
 Open full certificate details
-                        <#document-fragment >
-                            <STYLE type=text/css >
-                            </STYLE>
-                            <STYLE type=text/css >
-                            </STYLE>
-                            <STYLE type=text/css >
-                            </STYLE>
-                            <CONTENT >
-                            </CONTENT>
-                        </#document-fragment>
                     </BUTTON>
                 </DIV>
             </DIV>
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/security-explanation-ordering-expected.txt b/third_party/blink/web_tests/http/tests/devtools/security/security-explanation-ordering-expected.txt
index 104c328..56baad85 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/security-explanation-ordering-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/security/security-explanation-ordering-expected.txt
@@ -10,18 +10,8 @@
         <DIV >
 The connection to this site is using a valid, trusted server certificate.
         </DIV>
-        <BUTTON is=text-button type=button class=security-certificate-button >
+        <BUTTON class=security-certificate-button text-button type=button >
 View certificate
-            <#document-fragment >
-                <STYLE type=text/css >
-                </STYLE>
-                <STYLE type=text/css >
-                </STYLE>
-                <STYLE type=text/css >
-                </STYLE>
-                <CONTENT >
-                </CONTENT>
-            </#document-fragment>
         </BUTTON>
     </DIV>
 </DIV>
diff --git a/third_party/blink/web_tests/http/tests/devtools/unit/text-prompt-hint.js b/third_party/blink/web_tests/http/tests/devtools/unit/text-prompt-hint.js
index ad715484..109e8b9e 100644
--- a/third_party/blink/web_tests/http/tests/devtools/unit/text-prompt-hint.js
+++ b/third_party/blink/web_tests/http/tests/devtools/unit/text-prompt-hint.js
@@ -91,22 +91,6 @@
 
   function typeCharacter(character)
   {
-      var keyboardEvent = new KeyboardEvent("keydown", {
-          key: character || "Backspace",
-          charCode: character ? character.charCodeAt(0) : ""
-      });
-      element.dispatchEvent(keyboardEvent);
-
-      var selection = element.getComponentSelection();
-      var range = selection.getRangeAt(0);
-      var textNode = prompt._ghostTextElement.parentNode ? prompt._ghostTextElement.previousSibling : element.childTextNodes()[element.childTextNodes().length - 1];
-      if (!character)
-          textNode.textContent = textNode.textContent.substring(0,textNode.textContent.length-1);
-      else
-          textNode.textContent += character;
-      range.setStart(range.startContainer, range.startContainer.textContent.length);
-      selection.removeAllRanges();
-      selection.addRange(range);
-      element.dispatchEvent(new Event("input", {bubbles: true, cancelable: false}));
+    eventSender.keyDown(character || 'Backspace');
   }
 })();
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-child-background-image-fixed-tiled-expected.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-child-background-image-fixed-tiled-expected.html
new file mode 100644
index 0000000..af649c18
--- /dev/null
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-child-background-image-fixed-tiled-expected.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+#target {
+    width: 1000px;
+    height: 1000px;
+    background-image: url(../resources/apple.jpg);
+    background-attachment: fixed;
+}
+</style>
+<div id="target"></div>
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-child-background-image-fixed-tiled-expected.txt b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-child-background-image-fixed-tiled-expected.txt
new file mode 100644
index 0000000..13a87c0e
--- /dev/null
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-child-background-image-fixed-tiled-expected.txt
@@ -0,0 +1,113 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [600, 250],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [585, 235],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [1008, 1016],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow DIV id='target'",
+          "rect": [8, 8, 1000, 1000],
+          "reason": "background"
+        }
+      ]
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 250],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [385, 235],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [1008, 1016],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow DIV id='target'",
+          "rect": [8, 8, 1000, 1000],
+          "reason": "background"
+        }
+      ]
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [385, 585],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [1008, 1016],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow DIV id='target'",
+          "rect": [8, 8, 1000, 1000],
+          "reason": "background"
+        }
+      ]
+    }
+  ]
+}
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [785, 585],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [1008, 1016],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow DIV id='target'",
+          "rect": [8, 8, 1000, 1000],
+          "reason": "background"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-child-background-image-fixed-tiled.html b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-child-background-image-fixed-tiled.html
new file mode 100644
index 0000000..cbc1912
--- /dev/null
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-child-background-image-fixed-tiled.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script src="../../../resources/run-after-layout-and-paint.js"></script>
+<script src="../resources/window-resize-repaint.js"></script>
+<style>
+#target {
+    width: 1000px;
+    height: 1000px;
+    background-image: url(../resources/apple.jpg);
+    background-attachment: fixed;
+}
+</style>
+<div id="target"></div>
diff --git a/third_party/blink/web_tests/reporting-observer/buffer-overflow.html b/third_party/blink/web_tests/reporting-observer/buffer-overflow.html
new file mode 100644
index 0000000..8815c546
--- /dev/null
+++ b/third_party/blink/web_tests/reporting-observer/buffer-overflow.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="resources/intervention.js"></script>
+
+<div id="target" style="padding: 10px; background-color: blue;">
+  <p>Testing ReportingObserver's buffer size.</p>
+</div>
+
+<script>
+async_test(function(test) {
+  var observer3 = new ReportingObserver(function(reports, observer) {
+    test.step(function() {
+      // All 101 buffered reports should be received.
+      assert_equals(reports.length, 101);
+    });
+
+    test.done();
+  }, { buffered: true });
+
+  // Generate 110 intervention reports and 1 deprecation report. Only 100
+  // reports should be buffered per report type, so 101 total.
+  for (var i = 0; i != 110; ++i) {
+    causeIntervention();
+  }
+  window.webkitStorageInfo;
+
+  observer3.observe();
+  observer3.disconnect();
+}, "Buffer overflow");
+</script>
diff --git a/third_party/blink/web_tests/reporting-observer/resources/intervention.js b/third_party/blink/web_tests/reporting-observer/resources/intervention.js
index 4b2fe00..dc883cbb 100644
--- a/third_party/blink/web_tests/reporting-observer/resources/intervention.js
+++ b/third_party/blink/web_tests/reporting-observer/resources/intervention.js
@@ -21,7 +21,7 @@
   };
   var event = new TouchEvent('touchstart', touchEventInit);
 
-  var deadline = performance.now() + 100;
+  var deadline = performance.now() + 1;
   while (performance.now() < deadline) {};
 
   document.body.dispatchEvent(event);
diff --git a/third_party/blink/web_tests/virtual/threaded/external/wpt/animation-worklet/README.txt b/third_party/blink/web_tests/virtual/threaded/external/wpt/animation-worklet/README.txt
deleted file mode 100644
index 7f4b48be..0000000
--- a/third_party/blink/web_tests/virtual/threaded/external/wpt/animation-worklet/README.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-# This suite runs external/wpt/animation-worklet tests with threaded
-# compositing enabled.
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 5a25c9dd..b45ab03 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -19870,6 +19870,14 @@
   <description>Please enter the description of this user action.</description>
 </action>
 
+<action name="TabContextMenu_RemoveFromGroup">
+  <owner>bsep@google.com</owner>
+  <owner>tbergquist@google.com</owner>
+  <description>
+    User selected Remove from group from the tab context menu.
+  </description>
+</action>
+
 <action name="TabContextMenu_RestoreTab">
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <description>Please enter the description of this user action.</description>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 9ad0783..07c73d30 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -21299,6 +21299,7 @@
   <int value="2729" label="V8HTMLMediaElement_CaptureStream_Method"/>
   <int value="2730" label="QuirkyLineBoxBackgroundSize"/>
   <int value="2731" label="DirectlyCompositedImage"/>
+  <int value="2732" label="ForbiddenSyncXhrInPageDismissal"/>
 </enum>
 
 <enum name="FeaturePolicyFeature">
@@ -31663,6 +31664,8 @@
   <int value="316182183" label="MediaDocumentDownloadButton:disabled"/>
   <int value="319683583" label="ContentSuggestionsDebugLog:enabled"/>
   <int value="323605372" label="ui-disable-compositor-animation-timelines"/>
+  <int value="324060906"
+      label="enable-experimental-accessibility-language-detection"/>
   <int value="324522065" label="app-menu-icon"/>
   <int value="324631366" label="enable-drive-search-in-app-launcher"/>
   <int value="326876883" label="HappinessTrackingSurveysForDesktop:disabled"/>
@@ -39868,6 +39871,15 @@
   </int>
 </enum>
 
+<enum name="OSXStagingDirectoryStep">
+  <int value="0" label="failure"/>
+  <int value="1" label="NSItemReplacementDirectory"/>
+  <int value="2" label="Sibling directory"/>
+  <int value="3" label="NSTemporaryDirectory"/>
+  <int value="4" label="TMPDIR"/>
+  <int value="5" label="/tmp"/>
+</enum>
+
 <enum name="OtherPossibleUsernamesUsage">
   <int value="0" label="Nothing to Autofill"/>
   <int value="1" label="No other possible usernames"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 8067dd1..4352103 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -75076,6 +75076,14 @@
   </summary>
 </histogram>
 
+<histogram name="OSX.StagingDirectoryLocation" enum="OSXStagingDirectoryStep"
+    expires_after="2019-12-31">
+  <owner>avi@chromium.org</owner>
+  <owner>rsesek@chromium.org</owner>
+  <owner>mark@chromium.org</owner>
+  <summary>Records first staging directory location that works.</summary>
+</histogram>
+
 <histogram name="OSX.SystemHotkeyMap.LoadSuccess" enum="BooleanSuccess"
     expires_after="2018-08-30">
   <owner>erikchen@chromium.org</owner>
diff --git a/ui/accessibility/accessibility_switches.cc b/ui/accessibility/accessibility_switches.cc
index 06315d994..4aae94c9 100644
--- a/ui/accessibility/accessibility_switches.cc
+++ b/ui/accessibility/accessibility_switches.cc
@@ -21,6 +21,11 @@
 const char kEnableExperimentalAccessibilityLabels[] =
     "enable-experimental-accessibility-labels";
 
+// Enables language detection on in-page text content which is then exposed to
+// accessibility technology such as screen readers.
+const char kEnableExperimentalAccessibilityLanguageDetection[] =
+    "enable-experimental-accessibility-language-detection";
+
 // Shows setting to enable Switch Access before it has launched.
 const char kEnableExperimentalAccessibilitySwitchAccess[] =
     "enable-experimental-accessibility-switch-access";
@@ -30,4 +35,9 @@
       ::switches::kEnableExperimentalAccessibilityFeatures);
 }
 
+bool AreExperimentalAccessibilityLanguageDetectionEnabled() {
+  return base::CommandLine::ForCurrentProcess()->HasSwitch(
+      ::switches::kEnableExperimentalAccessibilityLanguageDetection);
+}
+
 }  // namespace switches
diff --git a/ui/accessibility/accessibility_switches.h b/ui/accessibility/accessibility_switches.h
index 31e05d9..c8678de 100644
--- a/ui/accessibility/accessibility_switches.h
+++ b/ui/accessibility/accessibility_switches.h
@@ -13,11 +13,14 @@
 AX_EXPORT extern const char kEnableExperimentalAccessibilityFeatures[];
 AX_EXPORT extern const char kEnableExperimentalAccessibilityAutoclick[];
 AX_EXPORT extern const char kEnableExperimentalAccessibilityLabels[];
+AX_EXPORT extern const char kEnableExperimentalAccessibilityLanguageDetection[];
 AX_EXPORT extern const char kEnableExperimentalAccessibilitySwitchAccess[];
 
 // Returns true if experimental accessibility features are enabled.
 AX_EXPORT bool AreExperimentalAccessibilityFeaturesEnabled();
 
+// Returns true if experimental accessibility language detection is enabled.
+AX_EXPORT bool AreExperimentalAccessibilityLanguageDetectionEnabled();
 }  // namespace switches
 
 #endif  // UI_ACCESSIBILITY_ACCESSIBILITY_SWITCHES_H_
diff --git a/ui/accessibility/ax_language_info_unittest.cc b/ui/accessibility/ax_language_info_unittest.cc
index 464aa59..f2d9b7a4 100644
--- a/ui/accessibility/ax_language_info_unittest.cc
+++ b/ui/accessibility/ax_language_info_unittest.cc
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #include "ui/accessibility/ax_language_info.h"
+#include "base/command_line.h"
+#include "ui/accessibility/accessibility_switches.h"
 #include "ui/accessibility/ax_node.h"
 #include "ui/accessibility/ax_tree.h"
 
@@ -15,6 +17,18 @@
 
 namespace ui {
 
+TEST(AXLanguageInfoTest, TestSwitch) {
+  // TODO(crbug/889370): Remove this test once this feature is stable
+  EXPECT_FALSE(
+      ::switches::AreExperimentalAccessibilityLanguageDetectionEnabled());
+
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      ::switches::kEnableExperimentalAccessibilityLanguageDetection);
+
+  EXPECT_TRUE(
+      ::switches::AreExperimentalAccessibilityLanguageDetectionEnabled());
+}
+
 // Tests that AXNode::GetLanguage() terminates when there is no lang attribute.
 TEST(AXLanguageInfoTest, TestGetLanguageNoLangAttr) {
   // build tree including parenting, this is to exercise the code paths within
diff --git a/ui/android/java/src/org/chromium/ui/OverscrollRefreshHandler.java b/ui/android/java/src/org/chromium/ui/OverscrollRefreshHandler.java
index 85b45d11..333d6cd 100644
--- a/ui/android/java/src/org/chromium/ui/OverscrollRefreshHandler.java
+++ b/ui/android/java/src/org/chromium/ui/OverscrollRefreshHandler.java
@@ -12,17 +12,22 @@
 public interface OverscrollRefreshHandler {
     /**
      * Signals the start of an overscrolling pull.
+     * @param xDelta The change in horizontal pull distance (positive if pulling down, negative if
+     *         up).
+     * @param yDelta The change in vertical pull distance.
      * @return Whether the handler will consume the overscroll sequence.
      */
     @CalledByNative
-    public boolean start();
+    public boolean start(float xDelta, float yDelta);
 
     /**
      * Signals a pull update.
-     * @param delta The change in pull distance (positive if pulling down, negative if up).
+     * @param xDelta The change in horizontal pull distance (positive if pulling down, negative if
+     *         up).
+     * @param yDelta The change in vertical pull distance.
      */
     @CalledByNative
-    public void pull(float delta);
+    public void pull(float xDelta, float yDelta);
 
     /**
      * Signals the release of the pull.
diff --git a/ui/android/overscroll_refresh.cc b/ui/android/overscroll_refresh.cc
index 60b54aa..2b6077dc 100644
--- a/ui/android/overscroll_refresh.cc
+++ b/ui/android/overscroll_refresh.cc
@@ -18,6 +18,7 @@
 
 OverscrollRefresh::OverscrollRefresh(OverscrollRefreshHandler* handler)
     : scrolled_to_top_(true),
+      top_at_scroll_start_(true),
       overflow_y_hidden_(false),
       scroll_consumption_state_(DISABLED),
       handler_(handler) {
@@ -35,13 +36,15 @@
 
 void OverscrollRefresh::Reset() {
   scroll_consumption_state_ = DISABLED;
+  cumulative_scroll_.set_x(0);
+  cumulative_scroll_.set_y(0);
   handler_->PullReset();
 }
 
 void OverscrollRefresh::OnScrollBegin() {
+  top_at_scroll_start_ = scrolled_to_top_;
   ReleaseWithoutActivation();
-  if (scrolled_to_top_ && !overflow_y_hidden_)
-    scroll_consumption_state_ = AWAITING_SCROLL_UPDATE_ACK;
+  scroll_consumption_state_ = AWAITING_SCROLL_UPDATE_ACK;
 }
 
 void OverscrollRefresh::OnScrollEnd(const gfx::Vector2dF& scroll_velocity) {
@@ -53,7 +56,10 @@
   if (scroll_consumption_state_ != AWAITING_SCROLL_UPDATE_ACK)
     return;
 
-  scroll_consumption_state_ = handler_->PullStart() ? ENABLED : DISABLED;
+  scroll_consumption_state_ =
+      handler_->PullStart(cumulative_scroll_.x(), cumulative_scroll_.y())
+          ? ENABLED
+          : DISABLED;
 }
 
 bool OverscrollRefresh::WillHandleScrollUpdate(
@@ -63,13 +69,21 @@
       return false;
 
     case AWAITING_SCROLL_UPDATE_ACK:
-      // If the initial scroll motion is downward, never allow activation.
-      if (scroll_delta.y() <= 0)
-        scroll_consumption_state_ = DISABLED;
+      // Check applies for the pull-to-refresh condition only.
+      if (std::abs(scroll_delta.y()) > std::abs(scroll_delta.x())) {
+        // If the initial scroll motion is downward, or we're in other cases
+        // where activation shouldn't have happened, stop here.
+        if (scroll_delta.y() <= 0 || !top_at_scroll_start_ ||
+            overflow_y_hidden_) {
+          scroll_consumption_state_ = DISABLED;
+          return false;
+        }
+      }
+      cumulative_scroll_.Add(scroll_delta);
       return false;
 
     case ENABLED:
-      handler_->PullUpdate(scroll_delta.y());
+      handler_->PullUpdate(scroll_delta.x(), scroll_delta.y());
       return true;
   }
 
@@ -101,6 +115,8 @@
   if (scroll_consumption_state_ == ENABLED)
     handler_->PullRelease(allow_refresh);
   scroll_consumption_state_ = DISABLED;
+  cumulative_scroll_.set_x(0);
+  cumulative_scroll_.set_y(0);
 }
 
 }  // namespace ui
diff --git a/ui/android/overscroll_refresh.h b/ui/android/overscroll_refresh.h
index b9067f1..25bc168 100644
--- a/ui/android/overscroll_refresh.h
+++ b/ui/android/overscroll_refresh.h
@@ -77,6 +77,9 @@
   void Release(bool allow_refresh);
 
   bool scrolled_to_top_;
+  // True if the content y offset was zero before scroll began. Overscroll
+  // should not be triggered for the scroll that started from non-zero offset.
+  bool top_at_scroll_start_;
   bool overflow_y_hidden_;
 
   enum ScrollConsumptionState {
@@ -85,6 +88,7 @@
     ENABLED,
   } scroll_consumption_state_;
 
+  gfx::Vector2dF cumulative_scroll_;
   OverscrollRefreshHandler* const handler_;
 
   DISALLOW_COPY_AND_ASSIGN(OverscrollRefresh);
diff --git a/ui/android/overscroll_refresh_handler.cc b/ui/android/overscroll_refresh_handler.cc
index 49b2f71..63e9873 100644
--- a/ui/android/overscroll_refresh_handler.cc
+++ b/ui/android/overscroll_refresh_handler.cc
@@ -19,14 +19,14 @@
 
 OverscrollRefreshHandler::~OverscrollRefreshHandler() {}
 
-bool OverscrollRefreshHandler::PullStart() {
-  return Java_OverscrollRefreshHandler_start(AttachCurrentThread(),
-                                             j_overscroll_refresh_handler_);
+bool OverscrollRefreshHandler::PullStart(float x_delta, float y_delta) {
+  return Java_OverscrollRefreshHandler_start(
+      AttachCurrentThread(), j_overscroll_refresh_handler_, x_delta, y_delta);
 }
 
-void OverscrollRefreshHandler::PullUpdate(float delta) {
-  Java_OverscrollRefreshHandler_pull(AttachCurrentThread(),
-                                     j_overscroll_refresh_handler_, delta);
+void OverscrollRefreshHandler::PullUpdate(float x_delta, float y_delta) {
+  Java_OverscrollRefreshHandler_pull(
+      AttachCurrentThread(), j_overscroll_refresh_handler_, x_delta, y_delta);
 }
 
 void OverscrollRefreshHandler::PullRelease(bool allow_refresh) {
diff --git a/ui/android/overscroll_refresh_handler.h b/ui/android/overscroll_refresh_handler.h
index 60184bf..7b4b7fa 100644
--- a/ui/android/overscroll_refresh_handler.h
+++ b/ui/android/overscroll_refresh_handler.h
@@ -23,10 +23,10 @@
   // Signals the start of an overscrolling pull. Returns whether the handler
   // will consume the overscroll gesture, in which case it will receive the
   // remaining pull updates.
-  virtual bool PullStart();
+  virtual bool PullStart(float x_delta, float y_delta);
 
-  // Signals a pull update, where |delta| is in device pixels.
-  virtual void PullUpdate(float delta);
+  // Signals a pull update, where |x_delta| and |y_delta| are in device pixels.
+  virtual void PullUpdate(float x_delta, float y_delta);
 
   // Signals the release of the pull, and whether the release is allowed to
   // trigger the refresh action.
diff --git a/ui/android/overscroll_refresh_unittest.cc b/ui/android/overscroll_refresh_unittest.cc
index f31b85f4..bcd1e657 100644
--- a/ui/android/overscroll_refresh_unittest.cc
+++ b/ui/android/overscroll_refresh_unittest.cc
@@ -16,12 +16,12 @@
   OverscrollRefreshTest() : OverscrollRefreshHandler(nullptr) {}
 
   // OverscrollRefreshHandler implementation.
-  bool PullStart() override {
+  bool PullStart(float, float) override {
     started_ = true;
     return true;
   }
 
-  void PullUpdate(float delta) override { delta_ += delta; }
+  void PullUpdate(float x_delta, float y_delta) override { delta_ += y_delta; }
 
   void PullRelease(bool allow_refresh) override {
     released_ = true;
diff --git a/ui/base/clipboard/scoped_clipboard_writer.cc b/ui/base/clipboard/scoped_clipboard_writer.cc
index 12680a6..3932401f 100644
--- a/ui/base/clipboard/scoped_clipboard_writer.cc
+++ b/ui/base/clipboard/scoped_clipboard_writer.cc
@@ -88,9 +88,11 @@
 }
 
 void ScopedClipboardWriter::WriteImage(const SkBitmap& bitmap) {
-  if (bitmap.drawsNothing()) {
+  if (bitmap.drawsNothing())
     return;
-  }
+  // TODO(crbug.com/918717): Remove CHECK if no crashes occur on it in canary.
+  CHECK(bitmap.getPixels());
+
   bitmap_ = bitmap;
   // TODO(dcheng): This is slightly less horrible than what we used to do, but
   // only very slightly less.
diff --git a/ui/base/ime/BUILD.gn b/ui/base/ime/BUILD.gn
index 6b8e71f..d686d1d 100644
--- a/ui/base/ime/BUILD.gn
+++ b/ui/base/ime/BUILD.gn
@@ -143,7 +143,6 @@
     "//net",
     "//third_party/icu",
     "//ui/base",
-    "//ui/base/ime/chromeos/public/interfaces",
     "//ui/display",
     "//ui/events",
     "//ui/gfx",
@@ -189,6 +188,7 @@
     deps += [
       "//chromeos",
       "//services/ws/public/cpp/input_devices",
+      "//ui/base/ime/chromeos/public/interfaces",
       "//ui/chromeos/strings",
       "//ui/events:dom_keycode_converter",
     ]
diff --git a/ui/base/ime/chromeos/public/interfaces/BUILD.gn b/ui/base/ime/chromeos/public/interfaces/BUILD.gn
index 625cdd4c..3f07e550 100644
--- a/ui/base/ime/chromeos/public/interfaces/BUILD.gn
+++ b/ui/base/ime/chromeos/public/interfaces/BUILD.gn
@@ -4,6 +4,8 @@
 
 import("//mojo/public/tools/bindings/mojom.gni")
 
+assert(is_chromeos)
+
 mojom("interfaces") {
   sources = [
     "ime_keyset.mojom",
diff --git a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html
index d9dcfac..7f5b1360 100644
--- a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html
+++ b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html
@@ -24,6 +24,14 @@
         @apply --cr-dialog-native;
       }
 
+      :host-context([dark]) dialog {
+        background-color: var(--google-grey-refresh-900);
+        /* Note: the colors in linear-gradient() are intentionally the same to
+         * add a 4% white layer on top of the fully opaque background-color. */
+        background-image: linear-gradient(rgba(255, 255, 255, .04),
+                                          rgba(255, 255, 255, .04));
+      }
+
       dialog[open] #content-wrapper {
         /* Keep max-height within viewport, and flex content accordingly. */
         display: flex;
diff --git a/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_selection_overlay.html b/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_selection_overlay.html
index 53dfb4a..acd1f036 100644
--- a/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_selection_overlay.html
+++ b/ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_selection_overlay.html
@@ -3,6 +3,7 @@
 <link rel="import" href="../cr_icons_css.html">
 <link rel="import" href="../icons.html">
 <link rel="import" href="../paper_button_style_css.html">
+<link rel="import" href="../shared_vars_css.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
 
@@ -27,6 +28,14 @@
         visibility: hidden;
       }
 
+      :host-context([dark]) {
+        background: var(--google-grey-refresh-900);
+        background-image: linear-gradient(rgba(255, 255, 255, .04),
+                                          rgba(255, 255, 255, .04));
+        border-bottom-color: var(--google-grey-refresh-700);
+        color: var(--cr-secondary-text-color);
+      }
+
       :host([show]) {
         opacity: 1;
         pointer-events: initial;
diff --git a/webrunner/common/webrunner_export.h b/webrunner/common/webrunner_export.h
deleted file mode 100644
index aaa3242..0000000
--- a/webrunner/common/webrunner_export.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef WEBRUNNER_COMMON_WEBRUNNER_EXPORT_H_
-#define WEBRUNNER_COMMON_WEBRUNNER_EXPORT_H_
-
-#if defined(COMPONENT_BUILD)
-
-#if defined(WEBRUNNER_IMPLEMENTATION)
-#define WEBRUNNER_EXPORT __attribute__((visibility("default")))
-#else
-#define WEBRUNNER_EXPORT
-#endif
-
-#else  // defined(COMPONENT_BUILD)
-#define WEBRUNNER_EXPORT
-#endif
-
-#endif  // WEBRUNNER_COMMON_WEBRUNNER_EXPORT_H_