diff --git a/AUTHORS b/AUTHORS
index a6175fb..cfd4a394 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -81,6 +81,7 @@
 Antonio Gomes <a1.gomes@sisa.samsung.com>
 Anuj Kumar Sharma <anujk.sharma@samsung.com>
 Arjun Karthik <arjunkar@amazon.com>
+Arman Ghotb <armanghotb@gmail.com>
 Armin Burgmeier <aburgmeier@bloomberg.net>
 Arnaud Renevier <a.renevier@samsung.com>
 Arpita Bahuguna <a.bah@samsung.com>
@@ -429,6 +430,7 @@
 Jüri Valdmann <juri.valdmann@qt.io>
 Kai Jiang <jiangkai@gmail.com>
 Kai Köhne <kai.koehne@qt.io>
+Kai Uwe Broulik <kde@privat.broulik.de>
 Kal Conley <kcconley@gmail.com>
 Kalyan Kondapally <kalyan.kondapally@intel.com>
 Kamil Jiwa <kamil.jiwa@gmail.com>
diff --git a/DEPS b/DEPS
index 316414c0..20408a20 100644
--- a/DEPS
+++ b/DEPS
@@ -79,7 +79,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': 'fe0253f8bb64857df088d5ff54e2b5b66b102b46',
+  'skia_revision': 'c9a642edf2d1c7f5380fe829adbb1a692f9969a6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -91,7 +91,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': 'dfebe9b2d5d9b02d6c6d026ba5add51556c49b4d',
+  'angle_revision': 'e3dc5dd0cb164d73725af27f2b2624657f1281d9',
   # 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.
@@ -103,7 +103,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': '30e0498962e8e4d99225a4da854ffd342677922c',
+  'pdfium_revision': '20c94774cc7efb3d90d3181539714f43fdcf01d2',
   # 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.
@@ -135,7 +135,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'fa0f0f2dd7e9ee09a5bed38bba060750d92c2ef2',
+  'catapult_revision': '2a467ca516e12b535f7d29d505fb1104f793faed',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -657,7 +657,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '3c1cb0203b6cfc10389e85a350b2ea6ca29d01ce',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '9fa35e528528838db4e57ea6e33f5e89a9305c74', # commit position 21742
+    Var('webrtc_git') + '/src.git' + '@' + 'd2c8332e2b033801b396ab2f2430a1579d7dec5a', # commit position 21742
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
diff --git a/WATCHLISTS b/WATCHLISTS
index e1e0eea..3f44309 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1694,7 +1694,8 @@
                  'vabr+watchlistautofill@chromium.org',
                  'mathp+autofillwatch@chromium.org',
                  'sebsg+autofillwatch@chromium.org',
-                 'rogerm+autofillwatch@chromium.org'],
+                 'rogerm+autofillwatch@chromium.org',
+                 'anthonyvd+autofillwatch@chromium.org'],
     'background_fetch': ['delphick+watch@chromium.org',
                          'peter@chromium.org'],
     'background_sync': ['chasej+watch@chromium.org',
@@ -2198,7 +2199,8 @@
     'payments': ['rouslan+payments@chromium.org',
                  'sebsg+paymentswatch@chromium.org',
                  'gogerald+paymentswatch@chromium.org',
-                 'mahmadi+paymentswatch@chromium.org'],
+                 'mahmadi+paymentswatch@chromium.org',
+                 'anthonyvd+paymentswatch@chromium.org'],
     'payments_ios': ['mahmadi+paymentsioswatch@chromium.org'],
     'pepper_api': ['binji+watch@chromium.org',
                    'bradnelson+warch@chromium.org',
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AndroidViewIntegrationTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AndroidViewIntegrationTest.java
index 7b840b8..1a16030 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AndroidViewIntegrationTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AndroidViewIntegrationTest.java
@@ -239,8 +239,9 @@
     private void loadPageOfSizeAndWaitForSizeChange(AwContents awContents,
             OnContentSizeChangedHelper helper, int widthCss, int heightCss,
             boolean heightPercent) throws Exception {
-
-        final String htmlData = makeHtmlPageOfSize(widthCss, heightCss, heightPercent);
+        // loadDataAsync loads HTML as a data URI, which requires encoding '#' characters as '%23'.
+        final String htmlData =
+                makeHtmlPageOfSize(widthCss, heightCss, heightPercent).replace("#", "%23");
         final int contentSizeChangeCallCount = helper.getCallCount();
         mActivityTestRule.loadDataAsync(awContents, htmlData, "text/html", false);
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldOverrideUrlLoadingTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldOverrideUrlLoadingTest.java
index b9cf0e43..a071b3d 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldOverrideUrlLoadingTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldOverrideUrlLoadingTest.java
@@ -304,7 +304,7 @@
 
         if (useLoadData) {
             mActivityTestRule.loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
-                    CommonResources.makeHtmlPageWithSimpleLinkTo("#anchor"), "text/html", false);
+                    CommonResources.makeHtmlPageWithSimpleLinkTo("%23anchor"), "text/html", false);
         } else {
             mActivityTestRule.loadUrlSync(
                     mAwContents, mContentsClient.getOnPageFinishedHelper(), anchorLinkUrl);
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsRenderTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsRenderTest.java
index fffc00d..936720a 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsRenderTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsRenderTest.java
@@ -68,7 +68,7 @@
         GraphicsTestUtils.pollForBackgroundColor(mAwContents, Color.YELLOW);
 
         mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
-                "data:text/html,<html><head><style>body {background-color:#227788}</style></head>"
+                "data:text/html,<html><head><style>body {background-color:%23227788}</style></head>"
                         + "<body></body></html>");
         final int teal = 0xFF227788;
         GraphicsTestUtils.pollForBackgroundColor(mAwContents, teal);
@@ -100,7 +100,7 @@
     @Feature({"AndroidWebView"})
     public void testForceDrawWhenInvisible() throws Throwable {
         mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
-                "data:text/html,<html><head><style>body {background-color:#227788}</style></head>"
+                "data:text/html,<html><head><style>body {background-color:%23227788}</style></head>"
                         + "<body>Hello world!</body></html>");
 
         Bitmap visibleBitmap = null;
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java
index c0a31f5e..0a0e27a 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwSettingsTest.java
@@ -3075,7 +3075,7 @@
         final String page = "<!doctype html>"
                 + "<script>"
                 + "window.onload = function() {"
-                + "  document.title = CSS.supports('color', '#AABBCCDD');"
+                + "  document.title = CSS.supports('color', '%23AABBCCDD');"
                 + "};"
                 + "</script>";
         mActivityTestRule.loadDataSync(awContents, onPageFinishedHelper, page, "text/html", false);
diff --git a/ash/autoclick/autoclick_controller.cc b/ash/autoclick/autoclick_controller.cc
index e7946538..8d01455 100644
--- a/ash/autoclick/autoclick_controller.cc
+++ b/ash/autoclick/autoclick_controller.cc
@@ -99,12 +99,12 @@
     return;
   enabled_ = enabled;
 
-  if (enabled_) {
+  if (enabled_)
     Shell::Get()->AddPreTargetHandler(this);
-    autoclick_controller_common_->CancelAutoclick();
-  } else {
+  else
     Shell::Get()->RemovePreTargetHandler(this);
-  }
+
+  autoclick_controller_common_->CancelAutoclick();
 }
 
 bool AutoclickControllerImpl::IsEnabled() const {
diff --git a/ash/login/login_screen_controller.cc b/ash/login/login_screen_controller.cc
index e024c81..1f4f88c 100644
--- a/ash/login/login_screen_controller.cc
+++ b/ash/login/login_screen_controller.cc
@@ -312,6 +312,18 @@
   login_screen_client_->ShowGaiaSignin();
 }
 
+void LoginScreenController::OnRemoveUserWarningShown() {
+  if (!login_screen_client_)
+    return;
+  login_screen_client_->OnRemoveUserWarningShown();
+}
+
+void LoginScreenController::RemoveUser(const AccountId& account_id) {
+  if (!login_screen_client_)
+    return;
+  login_screen_client_->RemoveUser(account_id);
+}
+
 void LoginScreenController::AddLockScreenAppsFocusObserver(
     LockScreenAppsFocusObserver* observer) {
   lock_screen_apps_focus_observers_.AddObserver(observer);
diff --git a/ash/login/login_screen_controller.h b/ash/login/login_screen_controller.h
index 499ebe6..e573f37 100644
--- a/ash/login/login_screen_controller.h
+++ b/ash/login/login_screen_controller.h
@@ -93,6 +93,8 @@
   void OnMaxIncorrectPasswordAttempted(const AccountId& account_id);
   void FocusLockScreenApps(bool reverse);
   void ShowGaiaSignin();
+  void OnRemoveUserWarningShown();
+  void RemoveUser(const AccountId& account_id);
 
   // Methods to manage lock screen apps focus observers.
   // The observers will be notified when lock screen apps focus changes are
diff --git a/ash/login/mock_login_screen_client.cc b/ash/login/mock_login_screen_client.cc
index d70995c8..2ad25b4 100644
--- a/ash/login/mock_login_screen_client.cc
+++ b/ash/login/mock_login_screen_client.cc
@@ -36,9 +36,9 @@
 }
 
 std::unique_ptr<MockLoginScreenClient> BindMockLoginScreenClient() {
-  LoginScreenController* controller = Shell::Get()->login_screen_controller();
   auto client = std::make_unique<MockLoginScreenClient>();
-  controller->SetClient(client->CreateInterfacePtrAndBind());
+  Shell::Get()->login_screen_controller()->SetClient(
+      client->CreateInterfacePtrAndBind());
   return client;
 }
 
diff --git a/ash/login/mock_login_screen_client.h b/ash/login/mock_login_screen_client.h
index c5026b16..5a9ce6d 100644
--- a/ash/login/mock_login_screen_client.h
+++ b/ash/login/mock_login_screen_client.h
@@ -59,6 +59,8 @@
                void(const AccountId& account_id));
   MOCK_METHOD1(FocusLockScreenApps, void(bool reverse));
   MOCK_METHOD0(ShowGaiaSignin, void());
+  MOCK_METHOD0(OnRemoveUserWarningShown, void());
+  MOCK_METHOD1(RemoveUser, void(const AccountId& account_id));
 
  private:
   bool authenticate_user_callback_result_ = true;
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 90b346d..b3a9ad4 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -814,7 +814,7 @@
 
 void LockContentsView::SwapToAuthUser(int user_index) {
   DCHECK(users_list_);
-  auto* view = users_list_->GetUserViewAtIndex(user_index);
+  LoginUserView* view = users_list_->user_view_at(user_index);
   DCHECK(view);
   mojom::LoginUserInfoPtr previous_auth_user =
       primary_auth_->current_user()->Clone();
@@ -826,6 +826,34 @@
   OnAuthUserChanged();
 }
 
+void LockContentsView::OnRemoveUserWarningShown(bool is_primary) {
+  Shell::Get()->login_screen_controller()->OnRemoveUserWarningShown();
+}
+
+void LockContentsView::RemoveUser(bool is_primary) {
+  LoginAuthUserView* to_remove =
+      is_primary ? primary_auth_ : opt_secondary_auth_;
+  DCHECK(to_remove->current_user()->can_remove);
+  AccountId user = to_remove->current_user()->basic_user_info->account_id;
+
+  // Ask chrome to remove the user.
+  Shell::Get()->login_screen_controller()->RemoveUser(user);
+
+  // Display the new user list less |user|.
+  std::vector<mojom::LoginUserInfoPtr> new_users;
+  if (!is_primary)
+    new_users.push_back(primary_auth_->current_user()->Clone());
+  if (is_primary && opt_secondary_auth_)
+    new_users.push_back(opt_secondary_auth_->current_user()->Clone());
+  if (users_list_) {
+    for (int i = 0; i < users_list_->user_count(); ++i) {
+      new_users.push_back(
+          users_list_->user_view_at(i)->current_user()->Clone());
+    }
+  }
+  data_dispatcher_->NotifyUsers(new_users);
+}
+
 void LockContentsView::OnAuthUserChanged() {
   const AccountId new_auth_user =
       CurrentAuthUserView()->current_user()->basic_user_info->account_id;
@@ -961,15 +989,22 @@
 LoginAuthUserView* LockContentsView::AllocateLoginAuthUserView(
     const mojom::LoginUserInfoPtr& user,
     bool is_primary) {
-  return new LoginAuthUserView(
-      user,
-      base::Bind(&LockContentsView::OnAuthenticate, base::Unretained(this)),
-      base::Bind(&LockContentsView::SwapActiveAuthBetweenPrimaryAndSecondary,
-                 base::Unretained(this), is_primary),
-      base::Bind(&LockContentsView::OnEasyUnlockIconHovered,
-                 base::Unretained(this)),
-      base::Bind(&LockContentsView::OnEasyUnlockIconTapped,
-                 base::Unretained(this)));
+  LoginAuthUserView::Callbacks callbacks;
+  callbacks.on_auth = base::BindRepeating(&LockContentsView::OnAuthenticate,
+                                          base::Unretained(this)),
+  callbacks.on_tap = base::BindRepeating(
+      &LockContentsView::SwapActiveAuthBetweenPrimaryAndSecondary,
+      base::Unretained(this), is_primary),
+  callbacks.on_remove_warning_shown =
+      base::BindRepeating(&LockContentsView::OnRemoveUserWarningShown,
+                          base::Unretained(this), is_primary);
+  callbacks.on_remove = base::BindRepeating(&LockContentsView::RemoveUser,
+                                            base::Unretained(this), is_primary);
+  callbacks.on_easy_unlock_icon_hovered = base::BindRepeating(
+      &LockContentsView::OnEasyUnlockIconHovered, base::Unretained(this));
+  callbacks.on_easy_unlock_icon_tapped = base::BindRepeating(
+      &LockContentsView::OnEasyUnlockIconTapped, base::Unretained(this));
+  return new LoginAuthUserView(user, callbacks);
 }
 
 LoginAuthUserView* LockContentsView::TryToFindAuthUser(
diff --git a/ash/login/ui/lock_contents_view.h b/ash/login/ui/lock_contents_view.h
index 4c0ee10..a0d2eee 100644
--- a/ash/login/ui/lock_contents_view.h
+++ b/ash/login/ui/lock_contents_view.h
@@ -213,6 +213,11 @@
   // the actual user may change.
   void SwapToAuthUser(int user_index);
 
+  // Warning to remove a user is shown.
+  void OnRemoveUserWarningShown(bool is_primary);
+  // Remove one of the auth users.
+  void RemoveUser(bool is_primary);
+
   // Called after the auth user change has taken place.
   void OnAuthUserChanged();
 
diff --git a/ash/login/ui/lock_screen_sanity_unittest.cc b/ash/login/ui/lock_screen_sanity_unittest.cc
index bcb5723f..2ecbecb 100644
--- a/ash/login/ui/lock_screen_sanity_unittest.cc
+++ b/ash/login/ui/lock_screen_sanity_unittest.cc
@@ -8,6 +8,8 @@
 #include "ash/login/mock_login_screen_client.h"
 #include "ash/login/ui/fake_login_detachable_base_model.h"
 #include "ash/login/ui/lock_contents_view.h"
+#include "ash/login/ui/login_auth_user_view.h"
+#include "ash/login/ui/login_bubble.h"
 #include "ash/login/ui/login_test_base.h"
 #include "ash/login/ui/login_test_utils.h"
 #include "ash/public/cpp/config.h"
@@ -113,8 +115,9 @@
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
 
   // Textfield should have focus.
-  EXPECT_EQ(MakeLoginPasswordTestApi(contents).textfield(),
-            contents->GetFocusManager()->GetFocusedView());
+  EXPECT_EQ(
+      MakeLoginPasswordTestApi(contents, AuthTarget::kPrimary).textfield(),
+      contents->GetFocusManager()->GetFocusedView());
 }
 
 // Verifies submitting the password invokes mojo lock screen client.
@@ -153,7 +156,7 @@
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
   LoginPasswordView::TestApi password_test_api =
-      MakeLoginPasswordTestApi(contents);
+      MakeLoginPasswordTestApi(contents, AuthTarget::kPrimary);
 
   MockLoginScreenClient::AuthenticateUserCallback callback;
   auto submit_password = [&]() {
@@ -370,4 +373,73 @@
   EXPECT_TRUE(VerifyFocused(shelf));
 }
 
+TEST_F(LockScreenSanityTest, RemoveUser) {
+  std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient();
+  LoginScreenController* controller =
+      ash::Shell::Get()->login_screen_controller();
+
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
+
+  // Add two users, the first of which can be removed.
+  users().push_back(CreateUser("test1@test"));
+  users()[0]->can_remove = true;
+  users().push_back(CreateUser("test2@test"));
+  data_dispatcher()->NotifyUsers(users());
+
+  std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
+
+  auto primary = [&]() {
+    return LoginUserView::TestApi(
+        MakeLoginAuthTestApi(contents, AuthTarget::kPrimary).user_view());
+  };
+  auto secondary = [&]() {
+    return LoginUserView::TestApi(
+        MakeLoginAuthTestApi(contents, AuthTarget::kSecondary).user_view());
+  };
+
+  // Fires a return and validates that mock expectations have been satisfied.
+  auto submit = [&]() {
+    GetEventGenerator().PressKey(ui::VKEY_RETURN, 0);
+    controller->FlushForTesting();
+    testing::Mock::VerifyAndClearExpectations(client.get());
+  };
+  auto focus_and_submit = [&](views::View* view) {
+    view->RequestFocus();
+    DCHECK(view->HasFocus());
+    submit();
+  };
+
+  // The secondary user is not removable (as configured above) so showing the
+  // dropdown does not result in an interactive/focusable view.
+  focus_and_submit(secondary().dropdown());
+  EXPECT_TRUE(secondary().menu());
+  EXPECT_FALSE(
+      HasFocusInAnyChildView(secondary().menu()->bubble_view_for_test()));
+  // TODO(jdufault): Run submit() and then EXPECT_FALSE(secondary().menu()); to
+  // verify that double-enter closes the bubble.
+
+  // The primary user is removable, so the menu is interactive. Submitting the
+  // first time shows the remove user warning, submitting the second time
+  // actually removes the user. Removing the user triggers a mojo API call as
+  // well as removes the user from the UI.
+  focus_and_submit(primary().dropdown());
+  EXPECT_TRUE(primary().menu());
+  EXPECT_TRUE(HasFocusInAnyChildView(primary().menu()->bubble_view_for_test()));
+  EXPECT_CALL(*client, OnRemoveUserWarningShown()).Times(1);
+  submit();
+  EXPECT_CALL(*client, RemoveUser(users()[0]->basic_user_info->account_id))
+      .Times(1);
+  submit();
+
+  // Secondary auth should be gone because it is now the primary auth.
+  EXPECT_FALSE(MakeLockContentsViewTestApi(contents).opt_secondary_auth());
+  EXPECT_TRUE(MakeLockContentsViewTestApi(contents)
+                  .primary_auth()
+                  ->current_user()
+                  ->basic_user_info->account_id ==
+              users()[1]->basic_user_info->account_id);
+}
+
 }  // namespace ash
diff --git a/ash/login/ui/login_auth_user_view.cc b/ash/login/ui/login_auth_user_view.cc
index 9e7dca8..ddf306b 100644
--- a/ash/login/ui/login_auth_user_view.cc
+++ b/ash/login/ui/login_auth_user_view.cc
@@ -101,23 +101,31 @@
   return view_->pin_view_;
 }
 
-LoginAuthUserView::LoginAuthUserView(
-    const mojom::LoginUserInfoPtr& user,
-    const OnAuthCallback& on_auth,
-    const LoginUserView::OnTap& on_tap,
-    const OnEasyUnlockIconHovered& on_easy_unlock_icon_hovered,
-    const OnEasyUnlockIconTapped& on_easy_unlock_icon_tapped)
+LoginAuthUserView::Callbacks::Callbacks() {}
+
+LoginAuthUserView::Callbacks::~Callbacks() {}
+
+LoginAuthUserView::LoginAuthUserView(const mojom::LoginUserInfoPtr& user,
+                                     const Callbacks& callbacks)
     : NonAccessibleView(kLoginAuthUserViewClassName),
-      on_auth_(on_auth),
-      on_tap_(on_tap),
+      on_auth_(callbacks.on_auth),
+      on_tap_(callbacks.on_tap),
       weak_factory_(this) {
+  DCHECK(callbacks.on_auth);
+  DCHECK(callbacks.on_tap);
+  DCHECK(callbacks.on_remove_warning_shown);
+  DCHECK(callbacks.on_remove);
+  DCHECK(callbacks.on_easy_unlock_icon_hovered);
+  DCHECK(callbacks.on_easy_unlock_icon_tapped);
   DCHECK_NE(user->basic_user_info->type,
             user_manager::USER_TYPE_PUBLIC_ACCOUNT);
 
   // Build child views.
   user_view_ = new LoginUserView(
       LoginDisplayStyle::kLarge, true /*show_dropdown*/, false /*show_domain*/,
-      base::Bind(&LoginAuthUserView::OnUserViewTap, base::Unretained(this)));
+      base::BindRepeating(&LoginAuthUserView::OnUserViewTap,
+                          base::Unretained(this)),
+      callbacks.on_remove_warning_shown, callbacks.on_remove);
 
   password_view_ = new LoginPasswordView();
   password_view_->SetPaintToLayer();  // Needed for opacity animation.
@@ -137,7 +145,8 @@
       base::Bind(&LoginAuthUserView::OnAuthSubmit, base::Unretained(this)),
       base::Bind(&LoginPinView::OnPasswordTextChanged,
                  base::Unretained(pin_view_)),
-      on_easy_unlock_icon_hovered, on_easy_unlock_icon_tapped);
+      callbacks.on_easy_unlock_icon_hovered,
+      callbacks.on_easy_unlock_icon_tapped);
 
   // Child views animate outside view bounds.
   SetPaintToLayer(ui::LayerType::LAYER_NOT_DRAWN);
diff --git a/ash/login/ui/login_auth_user_view.h b/ash/login/ui/login_auth_user_view.h
index 6441f86e0..806aba53 100644
--- a/ash/login/ui/login_auth_user_view.h
+++ b/ash/login/ui/login_auth_user_view.h
@@ -44,6 +44,30 @@
     LoginAuthUserView* const view_;
   };
 
+  using OnAuthCallback = base::RepeatingCallback<void(bool auth_success)>;
+  using OnEasyUnlockIconTapped = base::RepeatingClosure;
+  using OnEasyUnlockIconHovered = base::RepeatingClosure;
+
+  struct Callbacks {
+    Callbacks();
+    ~Callbacks();
+
+    // Executed whenever an authentication result is available, such as when the
+    // user submits a password or taps the user icon when AUTH_TAP is enabled.
+    OnAuthCallback on_auth;
+    // Called when the user taps the user view and AUTH_TAP is not enabled.
+    LoginUserView::OnTap on_tap;
+    // Called when the remove user warning message has been shown.
+    LoginUserView::OnRemoveWarningShown on_remove_warning_shown;
+    // Called when the user should be removed. The callback should do the actual
+    // removal.
+    LoginUserView::OnRemove on_remove;
+    // Called when the easy unlock icon is hovered.
+    OnEasyUnlockIconHovered on_easy_unlock_icon_hovered;
+    // Called when the easy unlock icon is tapped.
+    OnEasyUnlockIconTapped on_easy_unlock_icon_tapped;
+  };
+
   // Flags which describe the set of currently visible auth methods.
   enum AuthMethods {
     AUTH_NONE = 0,           // No extra auth methods.
@@ -52,23 +76,8 @@
     AUTH_TAP = 1 << 2,       // Tap to unlock.
   };
 
-  using OnAuthCallback = base::Callback<void(bool auth_success)>;
-  using OnEasyUnlockIconTapped = base::RepeatingClosure;
-  using OnEasyUnlockIconHovered = base::RepeatingClosure;
-
-  // |on_auth| is executed whenever an authentication result is available, such
-  // as when the user submits a password or taps the user icon when AUTH_TAP is
-  // enabled.
-  //
-  // |on_tap| is called when the user taps the user view and AUTH_TAP is not
-  // enabled.
-  //
-  // None of the callbacks can be null.
   LoginAuthUserView(const mojom::LoginUserInfoPtr& user,
-                    const OnAuthCallback& on_auth,
-                    const LoginUserView::OnTap& on_tap,
-                    const OnEasyUnlockIconHovered& on_easy_unlock_icon_hovered,
-                    const OnEasyUnlockIconTapped& on_easy_unlock_icon_tapped);
+                    const Callbacks& callbacks);
   ~LoginAuthUserView() override;
 
   // Set the displayed set of auth methods. |auth_methods| contains or-ed
diff --git a/ash/login/ui/login_auth_user_view_unittest.cc b/ash/login/ui/login_auth_user_view_unittest.cc
index e85c624..fcfe0f4 100644
--- a/ash/login/ui/login_auth_user_view_unittest.cc
+++ b/ash/login/ui/login_auth_user_view_unittest.cc
@@ -24,8 +24,15 @@
     LoginTestBase::SetUp();
 
     user_ = CreateUser("user@domain.com");
-    view_ = new LoginAuthUserView(user_, base::DoNothing(), base::DoNothing(),
-                                  base::DoNothing(), base::DoNothing());
+
+    LoginAuthUserView::Callbacks auth_callbacks;
+    auth_callbacks.on_auth = base::DoNothing();
+    auth_callbacks.on_easy_unlock_icon_hovered = base::DoNothing();
+    auth_callbacks.on_easy_unlock_icon_tapped = base::DoNothing();
+    auth_callbacks.on_tap = base::DoNothing();
+    auth_callbacks.on_remove_warning_shown = base::DoNothing();
+    auth_callbacks.on_remove = base::DoNothing();
+    view_ = new LoginAuthUserView(user_, auth_callbacks);
 
     // We proxy |view_| inside of |container_| so we can control layout.
     container_ = new views::View();
diff --git a/ash/login/ui/login_bubble.cc b/ash/login/ui/login_bubble.cc
index 50867ed..2bfb93f 100644
--- a/ash/login/ui/login_bubble.cc
+++ b/ash/login/ui/login_bubble.cc
@@ -155,10 +155,12 @@
                     bool is_owner,
                     views::View* anchor_view,
                     bool show_remove_user,
-                    base::OnceClosure do_remove_user)
+                    base::OnceClosure on_remove_user_warning_shown,
+                    base::OnceClosure on_remove_user_requested)
       : LoginBaseBubbleView(anchor_view),
         bubble_(bubble),
-        do_remove_user_(std::move(do_remove_user)) {
+        on_remove_user_warning_shown_(std::move(on_remove_user_warning_shown)),
+        on_remove_user_requested_(std::move(on_remove_user_requested)) {
     // This view has content the user can interact with if the remove user
     // button is displayed.
     set_can_activate(show_remove_user);
@@ -308,17 +310,20 @@
       SizeToContents();
       GetWidget()->SetSize(size());
       Layout();
+      if (on_remove_user_warning_shown_)
+        std::move(on_remove_user_warning_shown_).Run();
       return;
     }
 
-    if (do_remove_user_)
-      std::move(do_remove_user_).Run();
+    if (on_remove_user_requested_)
+      std::move(on_remove_user_requested_).Run();
     bubble_->Close();
   }
 
  private:
   LoginBubble* bubble_ = nullptr;
-  base::OnceClosure do_remove_user_;
+  base::OnceClosure on_remove_user_warning_shown_;
+  base::OnceClosure on_remove_user_requested_;
   views::View* remove_user_confirm_data_ = nullptr;
   views::Label* remove_user_label_ = nullptr;
   ButtonWithContent* remove_user_button_ = nullptr;
@@ -382,19 +387,19 @@
                                views::View* anchor_view,
                                LoginButton* bubble_opener,
                                bool show_remove_user,
-                               base::OnceClosure do_remove_user) {
+                               base::OnceClosure on_remove_user_warning_shown,
+                               base::OnceClosure on_remove_user_requested) {
   if (bubble_view_)
     CloseImmediately();
 
   flags_ = kFlagsNone;
   bubble_opener_ = bubble_opener;
-  bubble_view_ =
-      new LoginUserMenuView(this, username, email, type, is_owner, anchor_view,
-                            show_remove_user, std::move(do_remove_user));
+  bubble_view_ = new LoginUserMenuView(this, username, email, type, is_owner,
+                                       anchor_view, show_remove_user,
+                                       std::move(on_remove_user_warning_shown),
+                                       std::move(on_remove_user_requested));
   bool had_focus = bubble_opener_->HasFocus();
-
   Show();
-
   if (had_focus) {
     // Try to focus the bubble view only if the tooltip was focused.
     bubble_view_->RequestFocus();
diff --git a/ash/login/ui/login_bubble.h b/ash/login/ui/login_bubble.h
index 52be89c..ff1903a 100644
--- a/ash/login/ui/login_bubble.h
+++ b/ash/login/ui/login_bubble.h
@@ -52,7 +52,8 @@
                     views::View* anchor_view,
                     LoginButton* bubble_opener,
                     bool show_remove_user,
-                    base::OnceClosure do_remove_user);
+                    base::OnceClosure on_remove_user_warning_shown,
+                    base::OnceClosure on_remove_user_requested);
 
   // Shows a tooltip.
   void ShowTooltip(const base::string16& message, views::View* anchor_view);
diff --git a/ash/login/ui/login_bubble_unittest.cc b/ash/login/ui/login_bubble_unittest.cc
index cbf07da7..1f4b83c3 100644
--- a/ash/login/ui/login_bubble_unittest.cc
+++ b/ash/login/ui/login_bubble_unittest.cc
@@ -69,12 +69,14 @@
     LoginTestBase::TearDown();
   }
 
-  void ShowUserMenu(base::OnceClosure on_remove) {
+  void ShowUserMenu(base::OnceClosure on_remove_show_warning,
+                    base::OnceClosure on_remove) {
     bool show_remove_user = !on_remove.is_null();
     bubble_->ShowUserMenu(
         base::string16() /*username*/, base::string16() /*email*/,
         user_manager::UserType::USER_TYPE_REGULAR, false /*is_owner*/,
-        container_, bubble_opener_, show_remove_user, std::move(on_remove));
+        container_, bubble_opener_, show_remove_user,
+        std::move(on_remove_show_warning), std::move(on_remove));
   }
 
   // Owned by test widget view hierarchy.
@@ -118,7 +120,7 @@
   EXPECT_FALSE(bubble_->IsVisible());
 
   // Verifies that key event on the bubble opener view won't close the bubble.
-  ShowUserMenu(base::OnceClosure());
+  ShowUserMenu(base::OnceClosure(), base::OnceClosure());
   EXPECT_TRUE(bubble_->IsVisible());
   bubble_opener_->RequestFocus();
   generator.PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
@@ -141,7 +143,7 @@
   EXPECT_FALSE(bubble_->IsVisible());
 
   // Verifies that mouse event on the bubble opener view won't close the bubble.
-  ShowUserMenu(base::OnceClosure());
+  ShowUserMenu(base::OnceClosure(), base::OnceClosure());
   EXPECT_TRUE(bubble_->IsVisible());
   generator.MoveMouseTo(bubble_opener_->GetBoundsInScreen().CenterPoint());
   generator.ClickLeftButton();
@@ -170,7 +172,7 @@
 
   // Verifies that gesture event on the bubble opener view won't close the
   // bubble.
-  ShowUserMenu(base::OnceClosure());
+  ShowUserMenu(base::OnceClosure(), base::OnceClosure());
   EXPECT_TRUE(bubble_->IsVisible());
   generator.GestureTapAt(bubble_opener_->GetBoundsInScreen().CenterPoint());
   EXPECT_TRUE(bubble_->IsVisible());
@@ -192,7 +194,7 @@
             views::InkDropHostView::InkDropMode::ON);
 
   // Show the bubble to activate the ripple effect.
-  ShowUserMenu(base::OnceClosure());
+  ShowUserMenu(base::OnceClosure(), base::OnceClosure());
   EXPECT_TRUE(bubble_->IsVisible());
   EXPECT_TRUE(ink_drop_api.HasInkDrop());
   EXPECT_EQ(ink_drop_api.GetInkDrop()->GetTargetInkDropState(),
@@ -216,9 +218,14 @@
 // callback.
 TEST_F(LoginBubbleTest, RemoveUserRequiresTwoActivations) {
   // Show the user menu.
+  bool remove_warning_called = false;
   bool remove_called = false;
-  ShowUserMenu(base::BindOnce(
-      [](bool* remove_called) { *remove_called = true; }, &remove_called));
+  ShowUserMenu(
+      base::BindOnce(
+          [](bool* remove_warning_called) { *remove_warning_called = true; },
+          &remove_warning_called),
+      base::BindOnce([](bool* remove_called) { *remove_called = true; },
+                     &remove_called));
   EXPECT_TRUE(bubble_->IsVisible());
 
   // Focus the remove user button.
@@ -228,14 +235,20 @@
   remove_user_button->RequestFocus();
   EXPECT_TRUE(remove_user_button->HasFocus());
 
-  // Click it twice. Verify only the second click fires the callback.
   auto click = [&]() {
     EXPECT_TRUE(remove_user_button->HasFocus());
     GetEventGenerator().PressKey(ui::KeyboardCode::VKEY_RETURN, 0);
   };
+
+  // First click calls remove warning.
   EXPECT_NO_FATAL_FAILURE(click());
+  EXPECT_TRUE(remove_warning_called);
   EXPECT_FALSE(remove_called);
+  remove_warning_called = false;
+
+  // Second click calls remove.
   EXPECT_NO_FATAL_FAILURE(click());
+  EXPECT_FALSE(remove_warning_called);
   EXPECT_TRUE(remove_called);
 }
 
diff --git a/ash/login/ui/login_test_base.h b/ash/login/ui/login_test_base.h
index 205acfc6..7605055 100644
--- a/ash/login/ui/login_test_base.h
+++ b/ash/login/ui/login_test_base.h
@@ -40,6 +40,7 @@
   // Changes the active number of users. Fires an event on |data_dispatcher()|.
   void SetUserCount(size_t count);
 
+  std::vector<mojom::LoginUserInfoPtr>& users() { return users_; }
   const std::vector<mojom::LoginUserInfoPtr>& users() const { return users_; }
 
   LoginDataDispatcher* data_dispatcher() { return &data_dispatcher_; }
diff --git a/ash/login/ui/login_test_utils.cc b/ash/login/ui/login_test_utils.cc
index 8e8c23c9..46a55228 100644
--- a/ash/login/ui/login_test_utils.cc
+++ b/ash/login/ui/login_test_utils.cc
@@ -11,14 +11,24 @@
   return LockContentsView::TestApi(view);
 }
 
-LoginAuthUserView::TestApi MakeLoginPrimaryAuthTestApi(LockContentsView* view) {
-  return LoginAuthUserView::TestApi(
-      MakeLockContentsViewTestApi(view).primary_auth());
+LoginAuthUserView::TestApi MakeLoginAuthTestApi(LockContentsView* view,
+                                                AuthTarget target) {
+  switch (target) {
+    case AuthTarget::kPrimary:
+      return LoginAuthUserView::TestApi(
+          MakeLockContentsViewTestApi(view).primary_auth());
+    case AuthTarget::kSecondary:
+      return LoginAuthUserView::TestApi(
+          MakeLockContentsViewTestApi(view).opt_secondary_auth());
+  }
+
+  NOTREACHED();
 }
 
-LoginPasswordView::TestApi MakeLoginPasswordTestApi(LockContentsView* view) {
+LoginPasswordView::TestApi MakeLoginPasswordTestApi(LockContentsView* view,
+                                                    AuthTarget target) {
   return LoginPasswordView::TestApi(
-      MakeLoginPrimaryAuthTestApi(view).password_view());
+      MakeLoginAuthTestApi(view, target).password_view());
 }
 
 mojom::LoginUserInfoPtr CreateUser(const std::string& email) {
diff --git a/ash/login/ui/login_test_utils.h b/ash/login/ui/login_test_utils.h
index ad02dbf0..91fc4da 100644
--- a/ash/login/ui/login_test_utils.h
+++ b/ash/login/ui/login_test_utils.h
@@ -12,10 +12,14 @@
 
 namespace ash {
 
+enum class AuthTarget { kPrimary, kSecondary };
+
 // Helpers for constructing TestApi instances.
 LockContentsView::TestApi MakeLockContentsViewTestApi(LockContentsView* view);
-LoginAuthUserView::TestApi MakeLoginPrimaryAuthTestApi(LockContentsView* view);
-LoginPasswordView::TestApi MakeLoginPasswordTestApi(LockContentsView* view);
+LoginAuthUserView::TestApi MakeLoginAuthTestApi(LockContentsView* view,
+                                                AuthTarget auth);
+LoginPasswordView::TestApi MakeLoginPasswordTestApi(LockContentsView* view,
+                                                    AuthTarget auth);
 
 // Utility method to create a new |mojom::UserInfoPtr| instance.
 mojom::LoginUserInfoPtr CreateUser(const std::string& email);
diff --git a/ash/login/ui/login_user_view.cc b/ash/login/ui/login_user_view.cc
index 7210fad..400dc67 100644
--- a/ash/login/ui/login_user_view.cc
+++ b/ash/login/ui/login_user_view.cc
@@ -294,6 +294,14 @@
   return view_->tap_button_;
 }
 
+views::View* LoginUserView::TestApi::dropdown() const {
+  return view_->user_dropdown_;
+}
+
+LoginBubble* LoginUserView::TestApi::menu() const {
+  return view_->user_menu_.get();
+}
+
 bool LoginUserView::TestApi::is_opaque() const {
   return view_->is_opaque_;
 }
@@ -313,15 +321,25 @@
   return 0;
 }
 
-LoginUserView::LoginUserView(LoginDisplayStyle style,
-                             bool show_dropdown,
-                             bool show_domain,
-                             const OnTap& on_tap)
-    : on_tap_(on_tap), display_style_(style) {
-  // show_dropdown and show_domain can only be true when the user view is
-  // rendering in large mode.
+LoginUserView::LoginUserView(
+    LoginDisplayStyle style,
+    bool show_dropdown,
+    bool show_domain,
+    const OnTap& on_tap,
+    const OnRemoveWarningShown& on_remove_warning_shown,
+    const OnRemove& on_remove)
+    : on_tap_(on_tap),
+      on_remove_warning_shown_(on_remove_warning_shown),
+      on_remove_(on_remove),
+      display_style_(style) {
+  // show_dropdown can only be true when the user view is rendering in large
+  // mode.
   DCHECK(!show_dropdown || style == LoginDisplayStyle::kLarge);
   DCHECK(!show_domain || style == LoginDisplayStyle::kLarge);
+  // |on_remove_warning_shown| and |on_remove| is only available iff
+  // |show_dropdown| is true.
+  DCHECK(show_dropdown == !!on_remove_warning_shown);
+  DCHECK(show_dropdown == !!on_remove);
 
   user_image_ = new UserImage(GetImageSize(style));
   user_label_ = new UserLabel(style);
@@ -478,7 +496,7 @@
           current_user_->basic_user_info->type, current_user_->is_device_owner,
           user_dropdown_ /*anchor_view*/, user_dropdown_ /*bubble_opener*/,
           current_user_->can_remove /*show_remove_user*/,
-          base::OnceClosure() /*do_remove_user*/);
+          on_remove_warning_shown_, on_remove_);
     } else {
       user_menu_->Close();
     }
diff --git a/ash/login/ui/login_user_view.h b/ash/login/ui/login_user_view.h
index 4d27549c..45241b62 100644
--- a/ash/login/ui/login_user_view.h
+++ b/ash/login/ui/login_user_view.h
@@ -35,6 +35,8 @@
 
     views::View* user_label() const;
     views::View* tap_button() const;
+    views::View* dropdown() const;
+    LoginBubble* menu() const;
 
     bool is_opaque() const;
 
@@ -43,6 +45,8 @@
   };
 
   using OnTap = base::RepeatingClosure;
+  using OnRemoveWarningShown = base::RepeatingClosure;
+  using OnRemove = base::RepeatingClosure;
 
   // Returns the width of this view for the given display style.
   static int WidthForLayoutStyle(LoginDisplayStyle style);
@@ -50,7 +54,9 @@
   LoginUserView(LoginDisplayStyle style,
                 bool show_dropdown,
                 bool show_domain,
-                const OnTap& on_tap);
+                const OnTap& on_tap,
+                const OnRemoveWarningShown& on_remove_warning_shown,
+                const OnRemove& on_remove);
   ~LoginUserView() override;
 
   // Update the user view to display the given user information.
@@ -91,6 +97,10 @@
 
   // Executed when the user view is pressed.
   OnTap on_tap_;
+  // Executed when the user has seen the remove user warning.
+  OnRemoveWarningShown on_remove_warning_shown_;
+  // Executed when a user-remove has been requested.
+  OnRemove on_remove_;
 
   // The user that is currently being displayed (or will be displayed when an
   // animation completes).
@@ -102,8 +112,10 @@
   LoginDisplayStyle display_style_;
   UserImage* user_image_ = nullptr;
   UserLabel* user_label_ = nullptr;
+  // TODO(jdufault): Rename user_dropdown_ to dropdown_.
   LoginButton* user_dropdown_ = nullptr;
   TapButton* tap_button_ = nullptr;
+  // TODO(jdufault): Rename user_menu_ to menu_ or popup_menu_.
   std::unique_ptr<LoginBubble> user_menu_;
 
   // Show the domain information for public account user.
diff --git a/ash/login/ui/login_user_view_unittest.cc b/ash/login/ui/login_user_view_unittest.cc
index 65406597..65faed8 100644
--- a/ash/login/ui/login_user_view_unittest.cc
+++ b/ash/login/ui/login_user_view_unittest.cc
@@ -25,10 +25,20 @@
   LoginUserView* AddUserView(LoginDisplayStyle display_style,
                              bool show_dropdown) {
     // TODO(crbug.com/809635): Add test case for show_domain.
+    LoginUserView::OnRemoveWarningShown on_remove_warning_shown;
+    LoginUserView::OnRemove on_remove;
+    if (show_dropdown) {
+      on_remove_warning_shown = base::BindRepeating(
+          &LoginUserViewUnittest::OnRemoveWarningShown, base::Unretained(this));
+      on_remove = base::BindRepeating(&LoginUserViewUnittest::OnRemove,
+                                      base::Unretained(this));
+    }
+
     auto* view =
         new LoginUserView(display_style, show_dropdown, false /*show_domain*/,
                           base::BindRepeating(&LoginUserViewUnittest::OnTapped,
-                                              base::Unretained(this)));
+                                              base::Unretained(this)),
+                          on_remove_warning_shown, on_remove);
     mojom::LoginUserInfoPtr user = CreateUser("foo@foo.com");
     view->UpdateForUser(user, false /*animate*/);
     container_->AddChildView(view);
@@ -52,11 +62,15 @@
   }
 
   int tap_count_ = 0;
+  int remove_show_warning_count_ = 0;
+  int remove_count_ = 0;
 
   views::View* container_ = nullptr;  // Owned by test widget view hierarchy.
 
  private:
   void OnTapped() { ++tap_count_; }
+  void OnRemoveWarningShown() { ++remove_show_warning_count_; }
+  void OnRemove() { ++remove_count_; }
 
   DISALLOW_COPY_AND_ASSIGN(LoginUserViewUnittest);
 };
diff --git a/ash/login/ui/scrollable_users_list_view.cc b/ash/login/ui/scrollable_users_list_view.cc
index 4ad0497d..08e87b8b 100644
--- a/ash/login/ui/scrollable_users_list_view.cc
+++ b/ash/login/ui/scrollable_users_list_view.cc
@@ -117,7 +117,7 @@
 
 ScrollableUsersListView::ScrollableUsersListView(
     const std::vector<mojom::LoginUserInfoPtr>& users,
-    const OnUserViewTap& on_user_view_tap,
+    const ActionWithUser& on_tap_user,
     LoginDisplayStyle display_style)
     : views::ScrollView() {
   layout_params_ = GetLayoutParams(display_style);
@@ -136,8 +136,8 @@
   for (std::size_t i = 1u; i < users.size(); ++i) {
     auto* view = new LoginUserView(
         layout_params_.display_style, false /*show_dropdown*/,
-        false /*show_domain*/,
-        base::BindRepeating(on_user_view_tap, i - 1) /*on_tap*/);
+        false /*show_domain*/, base::BindRepeating(on_tap_user, i - 1),
+        base::RepeatingClosure(), base::RepeatingClosure());
     user_views_.push_back(view);
     view->UpdateForUser(users[i], false /*animate*/);
     contents->AddChildView(view);
@@ -158,11 +158,6 @@
 
 ScrollableUsersListView::~ScrollableUsersListView() = default;
 
-LoginUserView* ScrollableUsersListView::GetUserViewAtIndex(int index) {
-  return static_cast<size_t>(index) < user_views_.size() ? user_views_[index]
-                                                         : nullptr;
-}
-
 void ScrollableUsersListView::Layout() {
   DCHECK(layout_);
   bool should_show_landscape =
diff --git a/ash/login/ui/scrollable_users_list_view.h b/ash/login/ui/scrollable_users_list_view.h
index 6a4e91a..99e433e 100644
--- a/ash/login/ui/scrollable_users_list_view.h
+++ b/ash/login/ui/scrollable_users_list_view.h
@@ -9,6 +9,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/login/ui/login_display_style.h"
+#include "ash/login/ui/login_user_view.h"
 #include "ash/public/interfaces/login_user_info.mojom.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/views/controls/scroll_view.h"
@@ -21,7 +22,6 @@
 namespace ash {
 
 class HoverNotifier;
-class LoginUserView;
 class ScrollBar;
 
 // Scrollable list of the users. Stores the list of login user views. Can be
@@ -41,18 +41,24 @@
     ScrollableUsersListView* const view_;
   };
 
-  using OnUserViewTap = base::RepeatingCallback<void(int)>;
+  // TODO(jdufault): Pass AccountId or LoginUserView* instead of index.
+  using ActionWithUser = base::RepeatingCallback<void(int)>;
 
   // Initializes users list with rows for all |users|. The |display_style| is
   // used to determine layout and sizings. |on_user_view_tap| callback is
   // invoked whenever user row is tapped.
   ScrollableUsersListView(const std::vector<mojom::LoginUserInfoPtr>& users,
-                          const OnUserViewTap& on_user_view_tap,
+                          const ActionWithUser& on_tap_user,
                           LoginDisplayStyle display_style);
   ~ScrollableUsersListView() override;
 
   // Returns user view at |index| if it exists or nullptr otherwise.
-  LoginUserView* GetUserViewAtIndex(int index);
+  int user_count() const { return static_cast<int>(user_views_.size()); }
+  LoginUserView* user_view_at(int index) {
+    DCHECK_GE(index, 0);
+    DCHECK_LT(index, user_count());
+    return user_views_[index];
+  }
 
   // views::View:
   void Layout() override;
@@ -104,4 +110,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_LOGIN_UI_SCROLLABLE_USERS_LIST_VIEW_H_
\ No newline at end of file
+#endif  // ASH_LOGIN_UI_SCROLLABLE_USERS_LIST_VIEW_H_
diff --git a/ash/metrics/login_metrics_recorder_unittest.cc b/ash/metrics/login_metrics_recorder_unittest.cc
index ff437773..5a8224b 100644
--- a/ash/metrics/login_metrics_recorder_unittest.cc
+++ b/ash/metrics/login_metrics_recorder_unittest.cc
@@ -169,7 +169,7 @@
   EXPECT_EQ(test_api.primary_auth()->auth_methods(),
             (auth_method | LoginAuthUserView::AUTH_TAP));
   EXPECT_CALL(*client, AttemptUnlock(primary_user));
-  generator.MoveMouseTo(MakeLoginPrimaryAuthTestApi(contents)
+  generator.MoveMouseTo(MakeLoginAuthTestApi(contents, AuthTarget::kPrimary)
                             .user_view()
                             ->GetBoundsInScreen()
                             .CenterPoint());
diff --git a/ash/public/interfaces/login_screen.mojom b/ash/public/interfaces/login_screen.mojom
index 5b50918..c614cc58 100644
--- a/ash/public/interfaces/login_screen.mojom
+++ b/ash/public/interfaces/login_screen.mojom
@@ -166,4 +166,10 @@
 
   // Show Gaia signin dialog in chrome.
   ShowGaiaSignin();
+
+  // Notification that the remove user warning was shown.
+  OnRemoveUserWarningShown();
+
+  // Try to remove |account_id|.
+  RemoveUser(signin.mojom.AccountId account_id);
 };
diff --git a/ash/system/flag_warning/flag_warning_tray.cc b/ash/system/flag_warning/flag_warning_tray.cc
index ee1114a..531b8ed 100644
--- a/ash/system/flag_warning/flag_warning_tray.cc
+++ b/ash/system/flag_warning/flag_warning_tray.cc
@@ -37,21 +37,22 @@
   DCHECK(shelf_);
   SetLayoutManager(std::make_unique<views::FillLayout>());
 
-  const bool is_mash = Shell::GetAshConfig() == Config::MASH;
-  if (is_mash) {
-    container_ = new TrayContainer(shelf);
-    AddChildView(container_);
+  // Flag warning tray is not currently used in non-MASH environments, because
+  // mus will roll out via experiment/Finch trial and showing the tray would
+  // reveal the experiment state to users.
+  DCHECK_EQ(Shell::GetAshConfig(), Config::MASH);
+  container_ = new TrayContainer(shelf);
+  AddChildView(container_);
 
-    button_ = views::MdTextButton::Create(this, base::string16(),
-                                          CONTEXT_LAUNCHER_BUTTON);
-    button_->SetProminent(true);
-    button_->SetBgColorOverride(gfx::kGoogleYellow300);
-    button_->SetEnabledTextColors(SK_ColorBLACK);
-    button_->SetTooltipText(base::ASCIIToUTF16(kTooltipText));
-    UpdateButton();
-    container_->AddChildView(button_);
-  }
-  SetVisible(is_mash);
+  button_ = views::MdTextButton::Create(this, base::string16(),
+                                        CONTEXT_LAUNCHER_BUTTON);
+  button_->SetProminent(true);
+  button_->SetBgColorOverride(gfx::kGoogleYellow300);
+  button_->SetEnabledTextColors(SK_ColorBLACK);
+  button_->SetTooltipText(base::ASCIIToUTF16(kTooltipText));
+  UpdateButton();
+  container_->AddChildView(button_);
+  SetVisible(true);
 }
 
 FlagWarningTray::~FlagWarningTray() = default;
diff --git a/ash/system/flag_warning/flag_warning_tray_unittest.cc b/ash/system/flag_warning/flag_warning_tray_unittest.cc
index 3ea51e6..bdfe6f42 100644
--- a/ash/system/flag_warning/flag_warning_tray_unittest.cc
+++ b/ash/system/flag_warning/flag_warning_tray_unittest.cc
@@ -16,18 +16,14 @@
 using FlagWarningTrayTest = AshTestBase;
 
 TEST_F(FlagWarningTrayTest, Visibility) {
-  // Tray is always created.
-  FlagWarningTray* tray = Shell::GetPrimaryRootWindowController()
-                              ->GetStatusAreaWidget()
-                              ->flag_warning_tray_for_testing();
-  ASSERT_TRUE(tray);
-
-  // Warning should be visible in ash_unittests when mash is enabled, but not in
-  // regular ash_unittests. The warning does not show for Config::MUS because
+  // Flag warning tray is not currently used in non-MASH environments, because
   // mus will roll out via experiment/Finch trial and showing the tray would
   // reveal the experiment state to users.
   const bool is_mash = Shell::GetAshConfig() == Config::MASH;
-  EXPECT_EQ(tray->visible(), is_mash);
+  FlagWarningTray* tray = Shell::GetPrimaryRootWindowController()
+                              ->GetStatusAreaWidget()
+                              ->flag_warning_tray_for_testing();
+  EXPECT_EQ(tray != nullptr, is_mash);
 }
 
 }  // namespace
diff --git a/ash/system/status_area_widget.cc b/ash/system/status_area_widget.cc
index 7b4a9d8..ee441053 100644
--- a/ash/system/status_area_widget.cc
+++ b/ash/system/status_area_widget.cc
@@ -5,6 +5,7 @@
 #include "ash/system/status_area_widget.h"
 
 #include "ash/public/cpp/ash_features.h"
+#include "ash/public/cpp/config.h"
 #include "ash/session/session_controller.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
@@ -71,8 +72,13 @@
   logout_button_tray_ = std::make_unique<LogoutButtonTray>(shelf_);
   status_area_widget_delegate_->AddChildView(logout_button_tray_.get());
 
-  flag_warning_tray_ = std::make_unique<FlagWarningTray>(shelf_);
-  status_area_widget_delegate_->AddChildView(flag_warning_tray_.get());
+  if (Shell::GetAshConfig() == ash::Config::MASH) {
+    // Flag warning tray is not currently used in non-MASH environments, because
+    // mus will roll out via experiment/Finch trial and showing the tray would
+    // reveal the experiment state to users.
+    flag_warning_tray_ = std::make_unique<FlagWarningTray>(shelf_);
+    status_area_widget_delegate_->AddChildView(flag_warning_tray_.get());
+  }
 
   // The layout depends on the number of children, so build it once after
   // adding all of them.
@@ -119,7 +125,8 @@
   ime_menu_tray_->UpdateAfterShelfAlignmentChange();
   palette_tray_->UpdateAfterShelfAlignmentChange();
   overview_button_tray_->UpdateAfterShelfAlignmentChange();
-  flag_warning_tray_->UpdateAfterShelfAlignmentChange();
+  if (flag_warning_tray_)
+    flag_warning_tray_->UpdateAfterShelfAlignmentChange();
   status_area_widget_delegate_->UpdateLayout();
 }
 
@@ -176,7 +183,8 @@
   ime_menu_tray_->SchedulePaint();
   palette_tray_->SchedulePaint();
   overview_button_tray_->SchedulePaint();
-  flag_warning_tray_->SchedulePaint();
+  if (flag_warning_tray_)
+    flag_warning_tray_->SchedulePaint();
 }
 
 const ui::NativeTheme* StatusAreaWidget::GetNativeTheme() const {
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index 006da6f..d3c1d07 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -553,9 +553,9 @@
   if (reason == ActivationReason::WINDOW_DISPOSITION_CHANGED)
     return;
 
-  // Only snap window that can be snapped but hasn't been snapped.
+  // Only snap window that hasn't been snapped.
   if (!gained_active || gained_active == left_window_ ||
-      gained_active == right_window_ || !CanSnap(gained_active)) {
+      gained_active == right_window_) {
     return;
   }
 
@@ -566,6 +566,16 @@
     return;
   }
 
+  // If it's a user positionable window but can't be snapped, end split view
+  // mode and show the cannot snap toast.
+  if (!CanSnap(gained_active)) {
+    if (wm::GetWindowState(gained_active)->IsUserPositionable()) {
+      EndSplitView();
+      ShowAppCannotSnapToast();
+    }
+    return;
+  }
+
   // Snap the window on the non-default side of the screen if split view mode
   // is active.
   if (default_snap_position_ == LEFT)
diff --git a/ash/wm/splitview/split_view_controller_unittest.cc b/ash/wm/splitview/split_view_controller_unittest.cc
index d654c08..d8722b8 100644
--- a/ash/wm/splitview/split_view_controller_unittest.cc
+++ b/ash/wm/splitview/split_view_controller_unittest.cc
@@ -441,13 +441,10 @@
   EXPECT_TRUE(split_view_divider());
   EXPECT_TRUE(split_view_divider()->divider_widget()->IsAlwaysOnTop());
 
+  // Test that activating an non-snappable window ends the split view mode.
   std::unique_ptr<aura::Window> window3(CreateNonSnappableWindow(bounds));
   wm::ActivateWindow(window3.get());
-  EXPECT_TRUE(split_view_divider());
-  EXPECT_FALSE(split_view_divider()->divider_widget()->IsAlwaysOnTop());
-
-  EndSplitView();
-  EXPECT_TRUE(!split_view_divider());
+  EXPECT_FALSE(split_view_divider());
 }
 
 // Verifys that the bounds of the two windows in splitview are as expected.
diff --git a/base/i18n/file_util_icu_unittest.cc b/base/i18n/file_util_icu_unittest.cc
index 2028997..062d29b 100644
--- a/base/i18n/file_util_icu_unittest.cc
+++ b/base/i18n/file_util_icu_unittest.cc
@@ -37,7 +37,7 @@
   {"     ", "-   -"},
 };
 
-TEST_F(FileUtilICUTest, ReplaceIllegalCharacersInPathLinuxTest) {
+TEST_F(FileUtilICUTest, ReplaceIllegalCharactersInPathLinuxTest) {
   for (size_t i = 0; i < arraysize(kLinuxIllegalCharacterCases); ++i) {
     std::string bad_name(kLinuxIllegalCharacterCases[i].bad_name);
     ReplaceIllegalCharactersInPath(&bad_name, '-');
diff --git a/base/trace_event/memory_infra_background_whitelist.cc b/base/trace_event/memory_infra_background_whitelist.cc
index d6998ac..ee0919e1 100644
--- a/base/trace_event/memory_infra_background_whitelist.cc
+++ b/base/trace_event/memory_infra_background_whitelist.cc
@@ -202,6 +202,7 @@
     "v8/isolate_0x?/heap_spaces/new_space",
     "v8/isolate_0x?/heap_spaces/old_space",
     "v8/isolate_0x?/heap_spaces/other_spaces",
+    "v8/isolate_0x?/heap_spaces/read_only_space",
     "v8/isolate_0x?/malloc",
     "v8/isolate_0x?/zapped_for_debug",
     "site_storage/blob_storage/0x?",
diff --git a/build/android/main_dex_classes.flags b/build/android/main_dex_classes.flags
index 3b72aab..a293257 100644
--- a/build/android/main_dex_classes.flags
+++ b/build/android/main_dex_classes.flags
@@ -22,8 +22,16 @@
   *;
 }
 
--keep public class * extends android.app.Service {
-  <init>();
+-keep public class * extends org.chromium.base.process_launcher.ChildProcessService {
+  *;
+}
+
+-keep public class com.android.webview.** {
+  *;
+}
+
+-keep public class org.chromium.android_webview.** {
+  *;
 }
 
 # Used by tests for secondary dex extraction.
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index 8d126be..50ced4b 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -39,6 +39,7 @@
 from py_utils import tempfile_ext
 import tombstones
 
+
 with host_paths.SysPath(
     os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party'), 0):
   import jinja2  # pylint: disable=import-error
@@ -75,8 +76,6 @@
 _EXTRA_TEST_LIST = (
     'org.chromium.base.test.BaseChromiumAndroidJUnitRunner.TestList')
 
-UI_CAPTURE_DIRS = ['chromium_tests_root', 'UiCapture']
-
 FEATURE_ANNOTATION = 'Feature'
 RENDER_TEST_FEATURE_ANNOTATION = 'RenderTest'
 
@@ -126,7 +125,6 @@
     super(LocalDeviceInstrumentationTestRun, self).__init__(
         env, test_instance)
     self._flag_changers = {}
-    self._ui_capture_dir = dict()
     self._replace_package_contextmanager = None
 
   #override
@@ -253,21 +251,8 @@
         valgrind_tools.SetChromeTimeoutScale(
             dev, self._test_instance.timeout_scale)
 
-      @trace_event.traced
-      def setup_ui_capture_dir(dev):
-        # Make sure the UI capture directory exists and is empty by deleting
-        # and recreating it.
-        # TODO (aberent) once DeviceTempDir exists use it here.
-        self._ui_capture_dir[dev] = posixpath.join(
-            dev.GetExternalStoragePath(),
-            *UI_CAPTURE_DIRS)
-
-        if dev.PathExists(self._ui_capture_dir[dev]):
-          dev.RunShellCommand(['rm', '-rf', self._ui_capture_dir[dev]])
-        dev.RunShellCommand(['mkdir', self._ui_capture_dir[dev]])
-
       steps += [set_debug_app, edit_shared_prefs, push_test_data,
-                create_flag_changer, setup_ui_capture_dir]
+                create_flag_changer]
 
       def bind_crash_handler(step, dev):
         return lambda: crash_handler.RetryOnSystemCrash(step, dev)
@@ -372,7 +357,14 @@
         device.adb, suffix='.png', dir=device.GetExternalStoragePath())
     extras[EXTRA_SCREENSHOT_FILE] = screenshot_device_file.name
 
-    extras[EXTRA_UI_CAPTURE_DIR] = self._ui_capture_dir[device]
+    # Set up the screenshot directory. This needs to be done for each test so
+    # that we only get screenshots created by that test. It has to be on
+    # external storage since the default location doesn't allow file creation
+    # from the instrumentation test app on Android L and M.
+    ui_capture_dir = device_temp_file.NamedDeviceTemporaryDirectory(
+        device.adb,
+        dir=device.GetExternalStoragePath())
+    extras[EXTRA_UI_CAPTURE_DIR] = ui_capture_dir.name
 
     if self._env.trace_output:
       trace_device_file = device_temp_file.DeviceTempFile(
@@ -450,114 +442,116 @@
         time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()),
         device.serial)
 
-    with self._env.output_manager.ArchivedTempfile(
-        stream_name, 'logcat') as logcat_file:
-      try:
-        with logcat_monitor.LogcatMonitor(
-            device.adb,
-            filter_specs=local_device_environment.LOGCAT_FILTERS,
-            output_file=logcat_file.name,
-            transform_func=self._test_instance.MaybeDeobfuscateLines) as logmon:
-          with _LogTestEndpoints(device, test_name):
-            with contextlib_ext.Optional(
-                trace_event.trace(test_name),
-                self._env.trace_output):
-              output = device.StartInstrumentation(
-                  target, raw=True, extras=extras, timeout=timeout, retries=0)
-      finally:
-        logmon.Close()
-
-    if logcat_file.Link():
-      logging.info('Logcat saved to %s', logcat_file.Link())
-
-    duration_ms = time_ms() - start_ms
-
-    with contextlib_ext.Optional(
-        trace_event.trace('ProcessResults'),
-        self._env.trace_output):
-      output = self._test_instance.MaybeDeobfuscateLines(output)
-      # TODO(jbudorick): Make instrumentation tests output a JSON so this
-      # doesn't have to parse the output.
-      result_code, result_bundle, statuses = (
-          self._test_instance.ParseAmInstrumentRawOutput(output))
-      results = self._test_instance.GenerateTestResults(
-          result_code, result_bundle, statuses, start_ms, duration_ms,
-          device.product_cpu_abi, self._test_instance.symbolizer)
-
-    if self._env.trace_output:
-      self._SaveTraceData(trace_device_file, device, test['class'])
-
-    def restore_flags():
-      if flags_to_add:
-        self._flag_changers[str(device)].Restore()
-
-    def restore_timeout_scale():
-      if test_timeout_scale:
-        valgrind_tools.SetChromeTimeoutScale(
-            device, self._test_instance.timeout_scale)
-
-    def handle_coverage_data():
-      if self._test_instance.coverage_directory:
-        device.PullFile(coverage_directory,
-            self._test_instance.coverage_directory)
-        device.RunShellCommand(
-            'rm -f %s' % posixpath.join(coverage_directory, '*'),
-            check_return=True, shell=True)
-
-    def handle_render_test_data():
-      if _IsRenderTest(test):
-        # Render tests do not cause test failure by default. So we have to check
-        # to see if any failure images were generated even if the test does not
-        # fail.
-        try:
-          self._ProcessRenderTestResults(
-              device, render_tests_device_output_dir, results)
-        finally:
-          device.RemovePath(render_tests_device_output_dir,
-                            recursive=True, force=True)
-
-    def pull_ui_screen_captures():
-      screenshots = []
-      for filename in device.ListDirectory(self._ui_capture_dir[device]):
-        if filename.endswith('.json'):
-          screenshots.append(pull_ui_screenshot(filename))
-      if screenshots:
-        json_archive_name = 'ui_capture_%s_%s.json' % (
-            test_name.replace('#', '.'),
-            time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()))
-        with self._env.output_manager.ArchivedTempfile(
-            json_archive_name, 'ui_capture', output_manager.Datatype.JSON
-            ) as json_archive:
-          json.dump(screenshots, json_archive)
-        for result in results:
-          result.SetLink('ui screenshot', json_archive.Link())
-
-    def pull_ui_screenshot(filename):
-      source_dir = self._ui_capture_dir[device]
-      json_path = posixpath.join(source_dir, filename)
-      json_data = json.loads(device.ReadFile(json_path))
-      image_file_path = posixpath.join(source_dir, json_data['location'])
+    with ui_capture_dir:
       with self._env.output_manager.ArchivedTempfile(
-          json_data['location'], 'ui_capture', output_manager.Datatype.PNG
-          ) as image_archive:
-        device.PullFile(image_file_path, image_archive.name)
-      json_data['image_link'] = image_archive.Link()
-      return json_data
+          stream_name, 'logcat') as logcat_file:
+        try:
+          with logcat_monitor.LogcatMonitor(
+              device.adb,
+              filter_specs=local_device_environment.LOGCAT_FILTERS,
+              output_file=logcat_file.name,
+              transform_func=self._test_instance.MaybeDeobfuscateLines
+              ) as logmon:
+            with _LogTestEndpoints(device, test_name):
+              with contextlib_ext.Optional(
+                  trace_event.trace(test_name),
+                  self._env.trace_output):
+                output = device.StartInstrumentation(
+                    target, raw=True, extras=extras, timeout=timeout, retries=0)
+        finally:
+          logmon.Close()
 
-    # While constructing the TestResult objects, we can parallelize several
-    # steps that involve ADB. These steps should NOT depend on any info in
-    # the results! Things such as whether the test CRASHED have not yet been
-    # determined.
-    post_test_steps = [restore_flags, restore_timeout_scale,
-                       handle_coverage_data, handle_render_test_data,
-                       pull_ui_screen_captures]
-    if self._env.concurrent_adb:
-      post_test_step_thread_group = reraiser_thread.ReraiserThreadGroup(
-          reraiser_thread.ReraiserThread(f) for f in post_test_steps)
-      post_test_step_thread_group.StartAll(will_block=True)
-    else:
-      for step in post_test_steps:
-        step()
+      if logcat_file.Link():
+        logging.info('Logcat saved to %s', logcat_file.Link())
+
+      duration_ms = time_ms() - start_ms
+
+      with contextlib_ext.Optional(
+          trace_event.trace('ProcessResults'),
+          self._env.trace_output):
+        output = self._test_instance.MaybeDeobfuscateLines(output)
+        # TODO(jbudorick): Make instrumentation tests output a JSON so this
+        # doesn't have to parse the output.
+        result_code, result_bundle, statuses = (
+            self._test_instance.ParseAmInstrumentRawOutput(output))
+        results = self._test_instance.GenerateTestResults(
+            result_code, result_bundle, statuses, start_ms, duration_ms,
+            device.product_cpu_abi, self._test_instance.symbolizer)
+
+      if self._env.trace_output:
+        self._SaveTraceData(trace_device_file, device, test['class'])
+
+      def restore_flags():
+        if flags_to_add:
+          self._flag_changers[str(device)].Restore()
+
+      def restore_timeout_scale():
+        if test_timeout_scale:
+          valgrind_tools.SetChromeTimeoutScale(
+              device, self._test_instance.timeout_scale)
+
+      def handle_coverage_data():
+        if self._test_instance.coverage_directory:
+          device.PullFile(coverage_directory,
+              self._test_instance.coverage_directory)
+          device.RunShellCommand(
+              'rm -f %s' % posixpath.join(coverage_directory, '*'),
+              check_return=True, shell=True)
+
+      def handle_render_test_data():
+        if _IsRenderTest(test):
+          # Render tests do not cause test failure by default. So we have to
+          # check to see if any failure images were generated even if the test
+          # does not fail.
+          try:
+            self._ProcessRenderTestResults(
+                device, render_tests_device_output_dir, results)
+          finally:
+            device.RemovePath(render_tests_device_output_dir,
+                              recursive=True, force=True)
+
+      def pull_ui_screen_captures():
+        screenshots = []
+        for filename in device.ListDirectory(ui_capture_dir.name):
+          if filename.endswith('.json'):
+            screenshots.append(pull_ui_screenshot(filename))
+        if screenshots:
+          json_archive_name = 'ui_capture_%s_%s.json' % (
+              test_name.replace('#', '.'),
+              time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()))
+          with self._env.output_manager.ArchivedTempfile(
+              json_archive_name, 'ui_capture', output_manager.Datatype.JSON
+              ) as json_archive:
+            json.dump(screenshots, json_archive)
+          for result in results:
+            result.SetLink('ui screenshot', json_archive.Link())
+
+      def pull_ui_screenshot(filename):
+        source_dir = ui_capture_dir.name
+        json_path = posixpath.join(source_dir, filename)
+        json_data = json.loads(device.ReadFile(json_path))
+        image_file_path = posixpath.join(source_dir, json_data['location'])
+        with self._env.output_manager.ArchivedTempfile(
+            json_data['location'], 'ui_capture', output_manager.Datatype.PNG
+            ) as image_archive:
+          device.PullFile(image_file_path, image_archive.name)
+        json_data['image_link'] = image_archive.Link()
+        return json_data
+
+      # While constructing the TestResult objects, we can parallelize several
+      # steps that involve ADB. These steps should NOT depend on any info in
+      # the results! Things such as whether the test CRASHED have not yet been
+      # determined.
+      post_test_steps = [restore_flags, restore_timeout_scale,
+                         handle_coverage_data, handle_render_test_data,
+                         pull_ui_screen_captures]
+      if self._env.concurrent_adb:
+        post_test_step_thread_group = reraiser_thread.ReraiserThreadGroup(
+            reraiser_thread.ReraiserThread(f) for f in post_test_steps)
+        post_test_step_thread_group.StartAll(will_block=True)
+      else:
+        for step in post_test_steps:
+          step()
 
     for result in results:
       if logcat_file:
diff --git a/build/android/pylib/results/presentation/test_results_presentation.py b/build/android/pylib/results/presentation/test_results_presentation.py
index 21137fe..e218dac 100755
--- a/build/android/pylib/results/presentation/test_results_presentation.py
+++ b/build/android/pylib/results/presentation/test_results_presentation.py
@@ -6,7 +6,9 @@
 
 import argparse
 import collections
+import contextlib
 import json
+import logging
 import tempfile
 import os
 import sys
@@ -361,6 +363,56 @@
         authenticated_link=True)
 
 
+def ui_screenshot_set(json_path):
+  with open(json_path) as json_file:
+    json_object = json.loads(json_file.read())
+  if not 'per_iteration_data' in json_object:
+    # This will be reported as an error by result_details, no need to duplicate.
+    return None
+  ui_screenshots = []
+  for testsuite_run in json_object['per_iteration_data']:
+    for _, test_runs in testsuite_run.iteritems():
+      for test_run in test_runs:
+        if 'ui screenshot' in test_run['links']:
+          screenshot_link = test_run['links']['ui screenshot']
+          if screenshot_link.startswith('file:'):
+            with contextlib.closing(urllib.urlopen(screenshot_link)) as f:
+              test_screenshots = json.load(f)
+          else:
+            # Assume anything that isn't a file link is a google storage link
+            screenshot_string = google_storage_helper.read_from_link(
+                screenshot_link)
+            if not screenshot_string:
+              logging.error('Bad screenshot link %s', screenshot_link)
+              continue
+            test_screenshots = json.loads(
+                screenshot_string)
+          ui_screenshots.extend(test_screenshots)
+
+  if ui_screenshots:
+    return json.dumps(ui_screenshots)
+  return None
+
+
+def upload_screenshot_set(json_path, test_name, bucket, builder_name,
+                          build_number):
+  screenshot_set = ui_screenshot_set(json_path)
+  if not screenshot_set:
+    return None
+  dest = google_storage_helper.unique_name(
+    'screenshots_%s_%s_%s' % (test_name, builder_name, build_number),
+    suffix='.json')
+  with tempfile.NamedTemporaryFile(suffix='.json') as temp_file:
+    temp_file.write(screenshot_set)
+    temp_file.flush()
+    return google_storage_helper.upload(
+        name=dest,
+        filepath=temp_file.name,
+        bucket='%s/json' % bucket,
+        content_type='application/json',
+        authenticated_link=True)
+
+
 def main():
   parser = argparse.ArgumentParser()
   parser.add_argument('--json-file', help='Path of json file.')
@@ -455,16 +507,28 @@
       'Result details link do not match. The link returned by get_url_link'
       ' should be the same as that returned by upload.')
 
+  ui_screenshot_link = upload_screenshot_set(json_file, args.test_name,
+      args.bucket, builder_name, build_number)
+
+
   if args.output_json:
     with open(json_file) as original_json_file:
       json_object = json.load(original_json_file)
       json_object['links'] = {
           'result_details (logcats, flakiness links)': result_details_link
       }
+
+      if ui_screenshot_link:
+        json_object['links']['ui screenshots'] = ui_screenshot_link
+
       with open(args.output_json, 'w') as f:
         json.dump(json_object, f)
   else:
-    print result_details_link
+    print 'Result Details: %s' % result_details_link
+
+    if ui_screenshot_link:
+      print 'UI Screenshots %s' % ui_screenshot_link
+
 
 if __name__ == '__main__':
   sys.exit(main())
diff --git a/build/android/pylib/utils/google_storage_helper.py b/build/android/pylib/utils/google_storage_helper.py
index 55e4882..3101d71 100644
--- a/build/android/pylib/utils/google_storage_helper.py
+++ b/build/android/pylib/utils/google_storage_helper.py
@@ -13,6 +13,7 @@
 import os
 import sys
 import time
+import urlparse
 
 from pylib.constants import host_paths
 from pylib.utils import decorators
@@ -62,6 +63,15 @@
   return get_url_link(name, bucket, authenticated_link)
 
 
+@decorators.NoRaiseException(default_return_value='')
+def read_from_link(link):
+  # Note that urlparse returns the path with an initial '/', so we only need to
+  # add one more after the 'gs;'
+  gs_path = 'gs:/%s' % urlparse.urlparse(link).path
+  cmd = [_GSUTIL_PATH, '-q', 'cat', gs_path]
+  return cmd_helper.GetCmdOutput(cmd)
+
+
 @decorators.NoRaiseException(default_return_value=False)
 def exists(name, bucket):
   bucket = _format_bucket_name(bucket)
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index 97c31b8..1a8bf0f 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -937,6 +937,16 @@
         results_detail_file.flush()
       logging.critical('TEST RESULTS: %s', results_detail_file.Link())
 
+      ui_screenshots = test_results_presentation.ui_screenshot_set(
+          json_file.name)
+      if ui_screenshots:
+        with out_manager.ArchivedTempfile(
+            'ui_screenshots.json',
+            'ui_capture',
+            output_manager.Datatype.JSON) as ui_screenshot_file:
+          ui_screenshot_file.write(ui_screenshots)
+        logging.critical('UI Screenshots: %s', ui_screenshot_file.Link())
+
   if args.command == 'perf' and (args.steps or args.single_step):
     return 0
 
diff --git a/cc/animation/keyframed_animation_curve_unittest.cc b/cc/animation/keyframed_animation_curve_unittest.cc
index f714b283..7a13c743 100644
--- a/cc/animation/keyframed_animation_curve_unittest.cc
+++ b/cc/animation/keyframed_animation_curve_unittest.cc
@@ -5,6 +5,7 @@
 #include "cc/animation/keyframed_animation_curve.h"
 
 #include "cc/animation/transform_operations.h"
+#include "cc/test/geometry_test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/animation/tween.h"
@@ -304,6 +305,102 @@
   ExpectTranslateX(6.f, curve->GetValue(base::TimeDelta::FromSecondsD(3.f)));
 }
 
+// Tests that a discrete transform animation (e.g. where one or more keyframes
+// is a non-invertible matrix) works as expected.
+TEST(KeyframedAnimationCurveTest, DiscreteLinearTransformAnimation) {
+  gfx::Transform non_invertible_matrix(0, 0, 0, 0, 0, 0);
+  gfx::Transform identity_matrix;
+
+  std::unique_ptr<KeyframedTransformAnimationCurve> curve(
+      KeyframedTransformAnimationCurve::Create());
+  TransformOperations operations1;
+  operations1.AppendMatrix(non_invertible_matrix);
+  TransformOperations operations2;
+  operations2.AppendMatrix(identity_matrix);
+  TransformOperations operations3;
+  operations3.AppendMatrix(non_invertible_matrix);
+
+  curve->AddKeyframe(
+      TransformKeyframe::Create(base::TimeDelta(), operations1, nullptr));
+  curve->AddKeyframe(TransformKeyframe::Create(
+      base::TimeDelta::FromSecondsD(1.0), operations2, nullptr));
+  curve->AddKeyframe(TransformKeyframe::Create(
+      base::TimeDelta::FromSecondsD(2.0), operations3, nullptr));
+
+  TransformOperations result;
+
+  // Between 0 and 0.5 seconds, the first keyframe should be returned.
+  result = curve->GetValue(base::TimeDelta::FromSecondsD(0.01f));
+  EXPECT_TRANSFORMATION_MATRIX_EQ(non_invertible_matrix, result.Apply());
+
+  result = curve->GetValue(base::TimeDelta::FromSecondsD(0.49f));
+  EXPECT_TRANSFORMATION_MATRIX_EQ(non_invertible_matrix, result.Apply());
+
+  // Between 0.5 and 1.5 seconds, the middle keyframe should be returned.
+  result = curve->GetValue(base::TimeDelta::FromSecondsD(0.5f));
+  EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix, result.Apply());
+
+  result = curve->GetValue(base::TimeDelta::FromSecondsD(1.49f));
+  EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix, result.Apply());
+
+  // Between 1.5 and 2.0 seconds, the last keyframe should be returned.
+  result = curve->GetValue(base::TimeDelta::FromSecondsD(1.5f));
+  EXPECT_TRANSFORMATION_MATRIX_EQ(non_invertible_matrix, result.Apply());
+
+  result = curve->GetValue(base::TimeDelta::FromSecondsD(2.0f));
+  EXPECT_TRANSFORMATION_MATRIX_EQ(non_invertible_matrix, result.Apply());
+}
+
+TEST(KeyframedAnimationCurveTest, DiscreteCubicBezierTransformAnimation) {
+  gfx::Transform non_invertible_matrix(0, 0, 0, 0, 0, 0);
+  gfx::Transform identity_matrix;
+
+  std::unique_ptr<KeyframedTransformAnimationCurve> curve(
+      KeyframedTransformAnimationCurve::Create());
+  TransformOperations operations1;
+  operations1.AppendMatrix(non_invertible_matrix);
+  TransformOperations operations2;
+  operations2.AppendMatrix(identity_matrix);
+  TransformOperations operations3;
+  operations3.AppendMatrix(non_invertible_matrix);
+
+  // The cubic-bezier here is a nice fairly strong ease-in curve, where 50%
+  // progression is at approximately 85% of the time.
+  curve->AddKeyframe(TransformKeyframe::Create(
+      base::TimeDelta(), operations1,
+      CubicBezierTimingFunction::Create(0.75f, 0.25f, 0.9f, 0.4f)));
+  curve->AddKeyframe(TransformKeyframe::Create(
+      base::TimeDelta::FromSecondsD(1.0), operations2,
+      CubicBezierTimingFunction::Create(0.75f, 0.25f, 0.9f, 0.4f)));
+  curve->AddKeyframe(TransformKeyframe::Create(
+      base::TimeDelta::FromSecondsD(2.0), operations3,
+      CubicBezierTimingFunction::Create(0.75f, 0.25f, 0.9f, 0.4f)));
+
+  TransformOperations result;
+
+  // Due to the cubic-bezier, the first keyframe is returned almost all the way
+  // to 1 second.
+  result = curve->GetValue(base::TimeDelta::FromSecondsD(0.01f));
+  EXPECT_TRANSFORMATION_MATRIX_EQ(non_invertible_matrix, result.Apply());
+
+  result = curve->GetValue(base::TimeDelta::FromSecondsD(0.8f));
+  EXPECT_TRANSFORMATION_MATRIX_EQ(non_invertible_matrix, result.Apply());
+
+  // Between ~0.85 and ~1.85 seconds, the middle keyframe should be returned.
+  result = curve->GetValue(base::TimeDelta::FromSecondsD(0.85f));
+  EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix, result.Apply());
+
+  result = curve->GetValue(base::TimeDelta::FromSecondsD(1.8f));
+  EXPECT_TRANSFORMATION_MATRIX_EQ(identity_matrix, result.Apply());
+
+  // Finally the last keyframe only takes effect after ~1.85 seconds.
+  result = curve->GetValue(base::TimeDelta::FromSecondsD(1.85f));
+  EXPECT_TRANSFORMATION_MATRIX_EQ(non_invertible_matrix, result.Apply());
+
+  result = curve->GetValue(base::TimeDelta::FromSecondsD(2.0f));
+  EXPECT_TRANSFORMATION_MATRIX_EQ(non_invertible_matrix, result.Apply());
+}
+
 // Tests that a filter animation with one keyframe works as expected.
 TEST(KeyframedAnimationCurveTest, OneFilterKeyframe) {
   std::unique_ptr<KeyframedFilterAnimationCurve> curve(
diff --git a/cc/animation/transform_operations.cc b/cc/animation/transform_operations.cc
index 5e4e807..9b45618 100644
--- a/cc/animation/transform_operations.cc
+++ b/cc/animation/transform_operations.cc
@@ -53,7 +53,11 @@
 TransformOperations TransformOperations::Blend(const TransformOperations& from,
                                                SkMScalar progress) const {
   TransformOperations to_return;
-  BlendInternal(from, progress, &to_return);
+  if (!BlendInternal(from, progress, &to_return)) {
+    // If the matrices cannot be blended, fallback to discrete animation logic.
+    // See https://drafts.csswg.org/css-transforms/#matrix-interpolation
+    to_return = progress < 0.5 ? from : *this;
+  }
   return to_return;
 }
 
diff --git a/cc/animation/transform_operations.h b/cc/animation/transform_operations.h
index b74ddac..081da59 100644
--- a/cc/animation/transform_operations.h
+++ b/cc/animation/transform_operations.h
@@ -48,6 +48,9 @@
   // transforms are baked to matrices (using apply), and the matrices are
   // then decomposed and interpolated. For more information, see
   // http://www.w3.org/TR/2011/WD-css3-2d-transforms-20111215/#matrix-decomposition.
+  //
+  // If either of the matrices are non-decomposable for the blend, Blend applies
+  // discrete interpolation between them based on the progress value.
   TransformOperations Blend(const TransformOperations& from,
                             SkMScalar progress) const;
 
diff --git a/cc/animation/transform_operations_unittest.cc b/cc/animation/transform_operations_unittest.cc
index ca73a5a..ed65e9b 100644
--- a/cc/animation/transform_operations_unittest.cc
+++ b/cc/animation/transform_operations_unittest.cc
@@ -857,6 +857,32 @@
                                   operations1.Blend(operations2, -0.5).Apply());
 }
 
+TEST(TransformOperationTest, NonDecomposableBlend) {
+  TransformOperations non_decomposible_transform;
+  gfx::Transform non_decomposible_matrix(0, 0, 0, 0, 0, 0);
+  non_decomposible_transform.AppendMatrix(non_decomposible_matrix);
+
+  TransformOperations identity_transform;
+  gfx::Transform identity_matrix;
+  identity_transform.AppendMatrix(identity_matrix);
+
+  // Before the half-way point, we should return the 'from' matrix.
+  EXPECT_TRANSFORMATION_MATRIX_EQ(
+      non_decomposible_matrix,
+      identity_transform.Blend(non_decomposible_transform, 0.0f).Apply());
+  EXPECT_TRANSFORMATION_MATRIX_EQ(
+      non_decomposible_matrix,
+      identity_transform.Blend(non_decomposible_transform, 0.49f).Apply());
+
+  // After the half-way point, we should return the 'to' matrix.
+  EXPECT_TRANSFORMATION_MATRIX_EQ(
+      identity_matrix,
+      identity_transform.Blend(non_decomposible_transform, 0.5f).Apply());
+  EXPECT_TRANSFORMATION_MATRIX_EQ(
+      identity_matrix,
+      identity_transform.Blend(non_decomposible_transform, 1.0f).Apply());
+}
+
 TEST(TransformOperationTest, BlendedBoundsWhenTypesDoNotMatch) {
   TransformOperations operations_from;
   operations_from.AppendScale(2.0, 4.0, 8.0);
diff --git a/cc/base/rtree.h b/cc/base/rtree.h
index e71f615..d6a1e8d 100644
--- a/cc/base/rtree.h
+++ b/cc/base/rtree.h
@@ -71,6 +71,10 @@
   // Returns the total bounds of all items in this rtree.
   gfx::Rect GetBounds() const;
 
+  // Returns respective bounds of all items in this rtree in the order of items.
+  // Production code except tracing should not use this method.
+  std::vector<gfx::Rect> GetAllBoundsForTracing() const;
+
   void Reset();
 
  private:
@@ -118,6 +122,9 @@
   Branch<T> BuildRecursive(std::vector<Branch<T>>* branches, int level);
   Node<T>* AllocateNodeAtLevel(int level);
 
+  void GetAllBoundsRecursive(Node<T>* root,
+                             std::vector<gfx::Rect>* results) const;
+
   // This is the count of data elements (rather than total nodes in the tree)
   size_t num_data_elements_ = 0u;
   Branch<T> root_;
@@ -330,6 +337,25 @@
 }
 
 template <typename T>
+std::vector<gfx::Rect> RTree<T>::GetAllBoundsForTracing() const {
+  std::vector<gfx::Rect> results;
+  if (num_data_elements_ > 0)
+    GetAllBoundsRecursive(root_.subtree, &results);
+  return results;
+}
+
+template <typename T>
+void RTree<T>::GetAllBoundsRecursive(Node<T>* node,
+                                     std::vector<gfx::Rect>* results) const {
+  for (uint16_t i = 0; i < node->num_children; ++i) {
+    if (node->level == 0)
+      results->push_back(node->children[i].bounds);
+    else
+      GetAllBoundsRecursive(node->children[i].subtree, results);
+  }
+}
+
+template <typename T>
 void RTree<T>::Reset() {
   num_data_elements_ = 0;
   nodes_.clear();
diff --git a/cc/base/rtree_unittest.cc b/cc/base/rtree_unittest.cc
index 683bbb3..d5242a1 100644
--- a/cc/base/rtree_unittest.cc
+++ b/cc/base/rtree_unittest.cc
@@ -110,6 +110,7 @@
 TEST(RTreeTest, GetBoundsEmpty) {
   RTree<size_t> rtree;
   EXPECT_EQ(gfx::Rect(), rtree.GetBounds());
+  EXPECT_TRUE(rtree.GetAllBoundsForTracing().empty());
 }
 
 TEST(RTreeTest, GetBoundsNonOverlapping) {
@@ -121,6 +122,7 @@
   rtree.Build(rects);
 
   EXPECT_EQ(gfx::Rect(5, 6, 19, 20), rtree.GetBounds());
+  EXPECT_EQ(rects, rtree.GetAllBoundsForTracing());
 }
 
 TEST(RTreeTest, GetBoundsOverlapping) {
@@ -132,6 +134,7 @@
   rtree.Build(rects);
 
   EXPECT_EQ(gfx::Rect(0, 0, 10, 10), rtree.GetBounds());
+  EXPECT_EQ(rects, rtree.GetAllBoundsForTracing());
 }
 
 TEST(RTreeTest, BuildAfterReset) {
@@ -147,10 +150,12 @@
   // Resetting should give the same as an empty rtree.
   rtree.Reset();
   EXPECT_EQ(gfx::Rect(), rtree.GetBounds());
+  EXPECT_TRUE(rtree.GetAllBoundsForTracing().empty());
 
   // Should be able to rebuild from a reset rtree.
   rtree.Build(rects);
   EXPECT_EQ(gfx::Rect(0, 0, 10, 10), rtree.GetBounds());
+  EXPECT_EQ(rects, rtree.GetAllBoundsForTracing());
 }
 
 TEST(RTreeTest, Payload) {
diff --git a/cc/input/scroll_snap_data.h b/cc/input/scroll_snap_data.h
index a1dd237..871ad242 100644
--- a/cc/input/scroll_snap_data.h
+++ b/cc/input/scroll_snap_data.h
@@ -62,24 +62,26 @@
 
 struct ScrollSnapAlign {
   ScrollSnapAlign()
-      : alignmentX(SnapAlignment::kNone), alignmentY(SnapAlignment::kNone) {}
+      : alignment_inline(SnapAlignment::kNone),
+        alignment_block(SnapAlignment::kNone) {}
 
   explicit ScrollSnapAlign(SnapAlignment alignment)
-      : alignmentX(alignment), alignmentY(alignment) {}
+      : alignment_inline(alignment), alignment_block(alignment) {}
 
-  ScrollSnapAlign(SnapAlignment x, SnapAlignment y)
-      : alignmentX(x), alignmentY(y) {}
+  ScrollSnapAlign(SnapAlignment i, SnapAlignment b)
+      : alignment_inline(i), alignment_block(b) {}
 
   bool operator==(const ScrollSnapAlign& other) const {
-    return alignmentX == other.alignmentX && alignmentY == other.alignmentY;
+    return alignment_inline == other.alignment_inline &&
+           alignment_block == other.alignment_block;
   }
 
   bool operator!=(const ScrollSnapAlign& other) const {
     return !(*this == other);
   }
 
-  SnapAlignment alignmentX;
-  SnapAlignment alignmentY;
+  SnapAlignment alignment_inline;
+  SnapAlignment alignment_block;
 };
 
 // Snap area is a bounding box that could be snapped to when a scroll happens in
diff --git a/cc/paint/display_item_list.cc b/cc/paint/display_item_list.cc
index efe2e20..5757588 100644
--- a/cc/paint/display_item_list.cc
+++ b/cc/paint/display_item_list.cc
@@ -115,8 +115,12 @@
     state->BeginArray("items");
 
     PlaybackParams params(nullptr, SkMatrix::I());
+    const auto& bounds = rtree_.GetAllBoundsForTracing();
+    size_t i = 0;
     for (const PaintOp* op : PaintOpBuffer::Iterator(&paint_op_buffer_)) {
       state->BeginDictionary();
+      state->SetString("name", PaintOpTypeToString(op->GetType()));
+      MathUtil::AddToTracedValue("visual_rect", bounds[i++], state.get());
 
       SkPictureRecorder recorder;
       SkCanvas* canvas =
@@ -124,9 +128,11 @@
       op->Raster(canvas, params);
       sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
 
-      std::string b64_picture;
-      PictureDebugUtil::SerializeAsBase64(picture.get(), &b64_picture);
-      state->SetString("skp64", b64_picture);
+      if (picture->approximateOpCount()) {
+        std::string b64_picture;
+        PictureDebugUtil::SerializeAsBase64(picture.get(), &b64_picture);
+        state->SetString("skp64", b64_picture);
+      }
 
       state->EndDictionary();
     }
diff --git a/cc/paint/display_item_list_unittest.cc b/cc/paint/display_item_list_unittest.cc
index 43215b4a..ee69227 100644
--- a/cc/paint/display_item_list_unittest.cc
+++ b/cc/paint/display_item_list_unittest.cc
@@ -64,6 +64,20 @@
 
 }  // namespace
 
+#define EXPECT_TRACED_RECT(x, y, width, height, rect_list) \
+  do {                                                     \
+    ASSERT_EQ(4u, rect_list->GetSize());                   \
+    double d;                                              \
+    EXPECT_TRUE((rect_list)->GetDouble(0, &d));            \
+    EXPECT_EQ(x, d);                                       \
+    EXPECT_TRUE((rect_list)->GetDouble(1, &d));            \
+    EXPECT_EQ(y, d);                                       \
+    EXPECT_TRUE((rect_list)->GetDouble(2, &d));            \
+    EXPECT_EQ(width, d);                                   \
+    EXPECT_TRUE((rect_list)->GetDouble(3, &d));            \
+    EXPECT_EQ(height, d);                                  \
+  } while (false)
+
 TEST(DisplayItemListTest, SingleUnpairedRange) {
   gfx::Rect layer_rect(100, 100);
   PaintFlags blue_flags;
@@ -399,15 +413,10 @@
     // The real contents of the traced value is in here.
     {
       const base::ListValue* list;
-      double d;
 
       // The layer_rect field is present by empty.
       ASSERT_TRUE(params_dict->GetList("layer_rect", &list));
-      ASSERT_EQ(4u, list->GetSize());
-      EXPECT_TRUE(list->GetDouble(0, &d) && d == 0) << d;
-      EXPECT_TRUE(list->GetDouble(1, &d) && d == 0) << d;
-      EXPECT_TRUE(list->GetDouble(2, &d) && d == 0) << d;
-      EXPECT_TRUE(list->GetDouble(3, &d) && d == 0) << d;
+      EXPECT_TRACED_RECT(0, 0, 0, 0, list);
 
       // The items list is there but empty.
       ASSERT_TRUE(params_dict->GetList("items", &list));
@@ -426,15 +435,10 @@
     // The real contents of the traced value is in here.
     {
       const base::ListValue* list;
-      double d;
 
       // The layer_rect field is present by empty.
       ASSERT_TRUE(params_dict->GetList("layer_rect", &list));
-      ASSERT_EQ(4u, list->GetSize());
-      EXPECT_TRUE(list->GetDouble(0, &d) && d == 0) << d;
-      EXPECT_TRUE(list->GetDouble(1, &d) && d == 0) << d;
-      EXPECT_TRUE(list->GetDouble(2, &d) && d == 0) << d;
-      EXPECT_TRUE(list->GetDouble(3, &d) && d == 0) << d;
+      EXPECT_TRACED_RECT(0, 0, 0, 0, list);
 
       // The items list is not there since we asked for no ops.
       ASSERT_FALSE(params_dict->GetList("items", &list));
@@ -463,10 +467,11 @@
     PaintFlags red_paint;
     red_paint.setColor(SK_ColorRED);
 
-    list->push<SaveOp>();
+    list->push<SaveLayerOp>(nullptr, &red_paint);
     list->push<TranslateOp>(static_cast<float>(offset.x()),
                             static_cast<float>(offset.y()));
     list->push<DrawRectOp>(SkRect::MakeWH(4, 4), red_paint);
+    list->push<RestoreOp>();
 
     list->EndPaintOfUnpaired(bounds);
   }
@@ -490,28 +495,35 @@
 
     // The real contents of the traced value is in here.
     {
-      const base::ListValue* list;
-      double d;
-
+      const base::ListValue* layer_rect;
       // The layer_rect field is present and has the bounds of the rtree.
-      ASSERT_TRUE(params_dict->GetList("layer_rect", &list));
-      ASSERT_EQ(4u, list->GetSize());
-      EXPECT_TRUE(list->GetDouble(0, &d) && d == 2) << d;
-      EXPECT_TRUE(list->GetDouble(1, &d) && d == 3) << d;
-      EXPECT_TRUE(list->GetDouble(2, &d) && d == 8) << d;
-      EXPECT_TRUE(list->GetDouble(3, &d) && d == 9) << d;
+      ASSERT_TRUE(params_dict->GetList("layer_rect", &layer_rect));
+      EXPECT_TRACED_RECT(2, 3, 8, 9, layer_rect);
 
       // The items list has 3 things in it since we built 3 visual rects.
-      ASSERT_TRUE(params_dict->GetList("items", &list));
-      EXPECT_EQ(6u, list->GetSize());
+      const base::ListValue* items;
+      ASSERT_TRUE(params_dict->GetList("items", &items));
+      ASSERT_EQ(7u, items->GetSize());
 
-      for (int i = 0; i < 6; ++i) {
+      const char* expected_names[] = {"Save",      "Concat",   "SaveLayer",
+                                      "Translate", "DrawRect", "Restore",
+                                      "Restore"};
+      bool expected_has_skp[] = {false, true, true, true, true, false, false};
+
+      for (int i = 0; i < 7; ++i) {
         const base::DictionaryValue* item_dict;
+        ASSERT_TRUE(items->GetDictionary(i, &item_dict));
 
-        ASSERT_TRUE(list->GetDictionary(i, &item_dict));
+        const base::ListValue* visual_rect;
+        ASSERT_TRUE(item_dict->GetList("visual_rect", &visual_rect));
+        EXPECT_TRACED_RECT(2, 3, 8, 9, visual_rect);
 
-        // The SkPicture for each item exists.
-        EXPECT_TRUE(
+        std::string name;
+        EXPECT_TRUE(item_dict->GetString("name", &name));
+        EXPECT_EQ(expected_names[i], name);
+
+        EXPECT_EQ(
+            expected_has_skp[i],
             item_dict->GetString("skp64", static_cast<std::string*>(nullptr)));
       }
     }
@@ -528,15 +540,9 @@
     // The real contents of the traced value is in here.
     {
       const base::ListValue* list;
-      double d;
-
       // The layer_rect field is present and has the bounds of the rtree.
       ASSERT_TRUE(params_dict->GetList("layer_rect", &list));
-      ASSERT_EQ(4u, list->GetSize());
-      EXPECT_TRUE(list->GetDouble(0, &d) && d == 2) << d;
-      EXPECT_TRUE(list->GetDouble(1, &d) && d == 3) << d;
-      EXPECT_TRUE(list->GetDouble(2, &d) && d == 8) << d;
-      EXPECT_TRUE(list->GetDouble(3, &d) && d == 9) << d;
+      EXPECT_TRACED_RECT(2, 3, 8, 9, list);
 
       // The items list is not present since we asked for no ops.
       ASSERT_FALSE(params_dict->GetList("items", &list));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
index d11be9d..4d3f344e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
@@ -150,6 +150,8 @@
     @VisibleForTesting
     static final String PARALLEL_REQUEST_REFERRER_KEY =
             "android.support.customtabs.PARALLEL_REQUEST_REFERRER";
+    static final String PARALLEL_REQUEST_REFERRER_POLICY_KEY =
+            "android.support.customtabs.PARALLEL_REQUEST_REFERRER_POLICY";
     @VisibleForTesting
     static final String PARALLEL_REQUEST_URL_KEY =
             "android.support.customtabs.PARALLEL_REQUEST_URL";
@@ -930,8 +932,11 @@
         if (!mClientManager.getAllowParallelRequestForSession(session)) return;
         Uri referrer = intent.getParcelableExtra(PARALLEL_REQUEST_REFERRER_KEY);
         Uri url = intent.getParcelableExtra(PARALLEL_REQUEST_URL_KEY);
+        int policy =
+                intent.getIntExtra(PARALLEL_REQUEST_REFERRER_POLICY_KEY, WebReferrerPolicy.DEFAULT);
         if (referrer == null || url == null) return;
-        startParallelRequest(session, url, referrer);
+        if (policy < 0 || policy > WebReferrerPolicy.LAST) policy = WebReferrerPolicy.DEFAULT;
+        startParallelRequest(session, url, referrer, policy);
     }
 
     /** @return Whether {@code session} can create a parallel request for a given
@@ -957,12 +962,14 @@
      * @param session Calling context session.
      * @param url URL to send the request to.
      * @param referrer Referrer (and first party for cookies) to use.
+     * @param referrerPolicy Referrer policy for the parallel request.
      * @return Whether the request started. False if the session is not authorized to use the
      *         provided origin, if Chrome hasn't been initialized, or the feature is disabled.
      *         Also fails if the URL is neither HTTPS not HTTP.
      */
     @VisibleForTesting
-    boolean startParallelRequest(CustomTabsSessionToken session, Uri url, Uri referrer) {
+    boolean startParallelRequest(CustomTabsSessionToken session, Uri url, Uri referrer,
+            @WebReferrerPolicy int referrerPolicy) {
         ThreadUtils.assertOnUiThread();
         if (url.toString().equals("") || !isValid(url)
                 || !canDoParallelRequest(session, referrer)) {
@@ -970,7 +977,7 @@
         }
 
         nativeCreateAndStartDetachedResourceRequest(
-                Profile.getLastUsedProfile(), url.toString(), referrer.toString());
+                Profile.getLastUsedProfile(), url.toString(), referrer.toString(), referrerPolicy);
         return true;
     }
 
@@ -1540,5 +1547,5 @@
     }
 
     private static native void nativeCreateAndStartDetachedResourceRequest(
-            Profile profile, String url, String origin);
+            Profile profile, String url, String origin, @WebReferrerPolicy int referrerPolicy);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFactory.java
index 3e3384f..97ebb3b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFactory.java
@@ -26,7 +26,7 @@
 public class AndroidPaymentAppFactory implements PaymentAppFactoryAddition {
     @Override
     public void create(WebContents webContents, Map<String, PaymentMethodData> methodData,
-            PaymentAppCreatedCallback callback) {
+            boolean mayCrawlUnused, PaymentAppCreatedCallback callback) {
         AndroidPaymentAppFinder.find(webContents, methodData.keySet(),
                 new PaymentManifestWebDataService(), new PaymentManifestDownloader(),
                 new PaymentManifestParser(), new PackageManagerDelegate(), callback);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java
index 8002afb..06d181d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java
@@ -53,10 +53,11 @@
          * @param webContents The web contents that invoked PaymentRequest.
          * @param methodData  The methods that the merchant supports, along with the method specific
          *                    data.
+         * @param mayCrawl    Whether crawling for installable payment apps is allowed.
          * @param callback    The callback to invoke when apps are created.
          */
         void create(WebContents webContents, Map<String, PaymentMethodData> methodData,
-                PaymentAppCreatedCallback callback);
+                boolean mayCrawl, PaymentAppCreatedCallback callback);
     }
 
     private PaymentAppFactory() {
@@ -95,10 +96,11 @@
      * @param webContents The web contents where PaymentRequest was invoked.
      * @param methodData  The methods that the merchant supports, along with the method specific
      *                    data.
+     * @param mayCrawl    Whether crawling for installable payment apps is allowed.
      * @param callback    The callback to invoke when apps are created.
      */
     public void create(WebContents webContents, Map<String, PaymentMethodData> methodData,
-            final PaymentAppCreatedCallback callback) {
+            boolean mayCrawl, final PaymentAppCreatedCallback callback) {
         callback.onPaymentAppCreated(new AutofillPaymentApp(webContents));
 
         if (mAdditionalFactories.isEmpty()) {
@@ -122,7 +124,7 @@
                     if (mPendingTasks.isEmpty()) callback.onAllPaymentAppsCreated();
                 }
             };
-            additionalFactory.create(webContents, methodData, cb);
+            additionalFactory.create(webContents, methodData, mayCrawl, cb);
         }
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index d0672fe..d28e8a9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -331,6 +331,7 @@
     private Callback<PaymentInformation> mPaymentInformationCallback;
     private boolean mPaymentAppRunning;
     private boolean mMerchantSupportsAutofillPaymentInstruments;
+    private boolean mUserCanAddCreditCard;
     private boolean mHideServerAutofillInstruments;
     private ContactEditor mContactEditor;
     private boolean mHasRecordedAbortReason;
@@ -479,8 +480,11 @@
         // Checks whether the merchant supports autofill payment instrument before show is called.
         mMerchantSupportsAutofillPaymentInstruments =
                 AutofillPaymentApp.merchantSupportsAutofillPaymentInstruments(mMethodData);
-        PaymentAppFactory.getInstance().create(
-                mWebContents, Collections.unmodifiableMap(mMethodData), this /* callback */);
+        mUserCanAddCreditCard = mMerchantSupportsAutofillPaymentInstruments
+                && !ChromeFeatureList.isEnabled(ChromeFeatureList.NO_CREDIT_CARD_ABORT);
+        PaymentAppFactory.getInstance().create(mWebContents,
+                Collections.unmodifiableMap(mMethodData), !mUserCanAddCreditCard /* mayCrawl */,
+                this /* callback */);
 
         // Log the various types of payment methods that were requested by the merchant.
         boolean requestedMethodGoogle = false;
@@ -1525,7 +1529,10 @@
         mPaymentMethodsSection.addAndSelectOrUpdateItem(updatedAutofillPaymentInstruments);
 
         updateInstrumentModifiedTotals();
-        mUI.updateSection(PaymentRequestUI.TYPE_PAYMENT_METHODS, mPaymentMethodsSection);
+
+        if (mUI != null) {
+            mUI.updateSection(PaymentRequestUI.TYPE_PAYMENT_METHODS, mPaymentMethodsSection);
+        }
     }
 
     @Override
@@ -1536,7 +1543,10 @@
         mPaymentMethodsSection.removeAndUnselectItem(guid);
 
         updateInstrumentModifiedTotals();
-        mUI.updateSection(PaymentRequestUI.TYPE_PAYMENT_METHODS, mPaymentMethodsSection);
+
+        if (mUI != null) {
+            mUI.updateSection(PaymentRequestUI.TYPE_PAYMENT_METHODS, mPaymentMethodsSection);
+        }
     }
 
     /**
@@ -1752,10 +1762,8 @@
 
         boolean foundPaymentMethods =
                 mPaymentMethodsSection != null && !mPaymentMethodsSection.isEmpty();
-        boolean userCanAddCreditCard = mMerchantSupportsAutofillPaymentInstruments
-                && !ChromeFeatureList.isEnabled(ChromeFeatureList.NO_CREDIT_CARD_ABORT);
 
-        if (!mArePaymentMethodsSupported || (!foundPaymentMethods && !userCanAddCreditCard)) {
+        if (!mArePaymentMethodsSupported || (!foundPaymentMethods && !mUserCanAddCreditCard)) {
             // All payment apps have responded, but none of them have instruments. It's possible to
             // add credit cards, but the merchant does not support them either. The payment request
             // must be rejected.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
index 54cef34..cfb9138 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
@@ -71,11 +71,12 @@
 
     @Override
     public void create(WebContents webContents, Map<String, PaymentMethodData> methodData,
-            PaymentAppFactory.PaymentAppCreatedCallback callback) {
+            boolean mayCrawl, PaymentAppFactory.PaymentAppCreatedCallback callback) {
         ThreadUtils.assertOnUiThread();
 
         nativeGetAllPaymentApps(webContents,
-                methodData.values().toArray(new PaymentMethodData[methodData.size()]), callback);
+                methodData.values().toArray(new PaymentMethodData[methodData.size()]), mayCrawl,
+                callback);
     }
 
     /**
@@ -202,7 +203,7 @@
      * @param modifiers        Payment method specific modifiers to the payment items and the total.
      * @param callback         Called after the payment app is finished running.
      * @param appName          The installable app name.
-     * @param appIcon          The installable app icon.
+     * @param icon             The installable app icon.
      * @param swUri            The URI to get the app's service worker js script.
      * @param scope            The scope of the service worker that should be registered.
      * @param useCache         Whether to use cache when registering the service worker.
@@ -409,7 +410,8 @@
     }
 
     private static native void nativeGetAllPaymentApps(WebContents webContents,
-            PaymentMethodData[] methodData, PaymentAppFactory.PaymentAppCreatedCallback callback);
+            PaymentMethodData[] methodData, boolean mayCrawlForInstallablePaymentApps,
+            PaymentAppFactory.PaymentAppCreatedCallback callback);
 
     private static native void nativeHasServiceWorkerPaymentApps(
             HasServiceWorkerPaymentAppsCallback callback);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderService.java b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderService.java
index e27b502f..c07bbf04 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderService.java
@@ -16,6 +16,7 @@
 import org.chromium.base.Log;
 import org.chromium.base.PathUtils;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.annotations.MainDex;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
@@ -27,6 +28,7 @@
 /**
  * A service to accept requests to take image file contents and decode them.
  */
+@MainDex
 public class DecoderService extends Service {
     // The keys for the bundle when passing data to and from this service.
     static final String KEY_FILE_DESCRIPTOR = "file_descriptor";
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/DetachedResourceRequestTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/DetachedResourceRequestTest.java
index 399944f8..59ddf79 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/DetachedResourceRequestTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/DetachedResourceRequestTest.java
@@ -25,6 +25,7 @@
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.blink_public.web.WebReferrerPolicy;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.browserservices.Origin;
 import org.chromium.chrome.browser.browserservices.OriginVerifier;
@@ -96,15 +97,17 @@
         CustomTabsSessionToken session = prepareSession();
         ThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertFalse("Should not allow android-app:// scheme",
-                    mConnection.startParallelRequest(
-                            session, Uri.parse("android-app://this.is.an.android.app"), ORIGIN));
+                    mConnection.startParallelRequest(session,
+                            Uri.parse("android-app://this.is.an.android.app"), ORIGIN,
+                            WebReferrerPolicy.DEFAULT));
             Assert.assertFalse("Should not allow an empty URL",
-                    mConnection.startParallelRequest(session, Uri.parse(""), ORIGIN));
-            Assert.assertFalse("Should not allow an arbitrary origin",
                     mConnection.startParallelRequest(
-                            session, Uri.parse("HTTPS://foo.bar"), Uri.parse("wrong://origin")));
-            Assert.assertTrue(
-                    mConnection.startParallelRequest(session, Uri.parse("HTTP://foo.bar"), ORIGIN));
+                            session, Uri.parse(""), ORIGIN, WebReferrerPolicy.DEFAULT));
+            Assert.assertFalse("Should not allow an arbitrary origin",
+                    mConnection.startParallelRequest(session, Uri.parse("HTTPS://foo.bar"),
+                            Uri.parse("wrong://origin"), WebReferrerPolicy.DEFAULT));
+            Assert.assertTrue(mConnection.startParallelRequest(
+                    session, Uri.parse("HTTP://foo.bar"), ORIGIN, WebReferrerPolicy.DEFAULT));
         });
     }
 
@@ -125,8 +128,10 @@
         mServer.start();
 
         Uri url = Uri.parse(mServer.getURL("/echotitle"));
-        ThreadUtils.runOnUiThread(
-                () -> Assert.assertTrue(mConnection.startParallelRequest(session, url, ORIGIN)));
+        ThreadUtils.runOnUiThread(() -> {
+            Assert.assertTrue(mConnection.startParallelRequest(
+                    session, url, ORIGIN, WebReferrerPolicy.DEFAULT));
+        });
         cb.waitForCallback(0, 1);
     }
 
@@ -137,8 +142,10 @@
         CustomTabsSessionToken session = prepareSession();
         mServer = EmbeddedTestServer.createAndStartServer(mContext);
         final Uri url = Uri.parse(mServer.getURL("/set-cookie?acookie"));
-        ThreadUtils.runOnUiThreadBlocking(
-                () -> Assert.assertTrue(mConnection.startParallelRequest(session, url, ORIGIN)));
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue(mConnection.startParallelRequest(
+                    session, url, ORIGIN, WebReferrerPolicy.DEFAULT));
+        });
 
         String echoUrl = mServer.getURL("/echoheader?Cookie");
         Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(mContext, echoUrl);
@@ -162,8 +169,10 @@
             prefs.setBlockThirdPartyCookiesEnabled(true);
         });
         final Uri url = Uri.parse(mServer.getURL("/set-cookie?acookie"));
-        ThreadUtils.runOnUiThreadBlocking(
-                () -> Assert.assertTrue(mConnection.startParallelRequest(session, url, ORIGIN)));
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue(mConnection.startParallelRequest(
+                    session, url, ORIGIN, WebReferrerPolicy.DEFAULT));
+        });
 
         String echoUrl = mServer.getURL("/echoheader?Cookie");
         Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(mContext, echoUrl);
@@ -190,8 +199,10 @@
         final Uri origin = Uri.parse(new Origin(url).toString());
         CustomTabsSessionToken session = prepareSession(url);
 
-        ThreadUtils.runOnUiThreadBlocking(
-                () -> Assert.assertTrue(mConnection.startParallelRequest(session, url, origin)));
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            Assert.assertTrue(mConnection.startParallelRequest(
+                    session, url, origin, WebReferrerPolicy.DEFAULT));
+        });
 
         String echoUrl = mServer.getURL("/echoheader?Cookie");
         Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(mContext, echoUrl);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppAndBasicCardWithModifiersTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppAndBasicCardWithModifiersTest.java
index 1e24705..b5e9db5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppAndBasicCardWithModifiersTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppAndBasicCardWithModifiersTest.java
@@ -324,7 +324,7 @@
                 new ServiceWorkerPaymentApp.Capabilities(alicepayNetworks, alicepayTypes)};
 
         PaymentAppFactory.getInstance().addAdditionalFactory((webContents, methodNames,
-                                                                     callback) -> {
+                                                                     mayCrawlUnused, callback) -> {
             ChromeActivity activity = ChromeActivity.fromWebContents(webContents);
             BitmapDrawable icon = new BitmapDrawable(activity.getResources(),
                     Bitmap.createBitmap(new int[] {Color.RED}, 1 /* width */, 1 /* height */,
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppTest.java
index 7e2953d..13c4142 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppTest.java
@@ -89,7 +89,7 @@
             throws InterruptedException, ExecutionException, TimeoutException {
         final TestPay app = new TestPay("https://bobpay.com", HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
         PaymentAppFactory.getInstance().addAdditionalFactory(
-                (webContents, methodNames, callback) -> {
+                (webContents, methodNames, mayCrawlUnused, callback) -> {
                     callback.onPaymentAppCreated(app);
                     callback.onAllPaymentAppsCreated();
                 });
@@ -111,7 +111,7 @@
             throws InterruptedException, ExecutionException, TimeoutException {
         final TestPay app = new TestPay("https://bobpay.com", NO_INSTRUMENTS, IMMEDIATE_RESPONSE);
         PaymentAppFactory.getInstance().addAdditionalFactory(
-                (webContents, methodNames, callback) -> {
+                (webContents, methodNames, mayCrawlUnused, callback) -> {
                     callback.onPaymentAppCreated(app);
                     callback.onAllPaymentAppsCreated();
                 });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppsSortingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppsSortingTest.java
index 43f18a82..8f97b5d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppsSortingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppsSortingTest.java
@@ -65,7 +65,7 @@
         final TestPay appC =
                 new TestPay("https://charliepay.com", HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
         PaymentAppFactory.getInstance().addAdditionalFactory(
-                (webContents, methodNames, callback) -> {
+                (webContents, methodNames, mayCrawlUnused, callback) -> {
                     callback.onPaymentAppCreated(appA);
                     callback.onPaymentAppCreated(appB);
                     callback.onPaymentAppCreated(appC);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java
index 751eb2b7..9c41d4a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java
@@ -52,7 +52,7 @@
             final ServiceWorkerPaymentApp.Capabilities[] capabilities, final boolean withName,
             final boolean withIcon) {
         PaymentAppFactory.getInstance().addAdditionalFactory(
-                (webContents, methodNames, callback) -> {
+                (webContents, methodNames, mayCrawlUnused, callback) -> {
                     ChromeActivity activity = ChromeActivity.fromWebContents(webContents);
                     BitmapDrawable icon = withIcon
                             ? new BitmapDrawable(activity.getResources(),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java
index 7e52ee25..16d682f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java
@@ -980,7 +980,7 @@
             final String appMethodName, final int instrumentPresence,
             final int responseSpeed, final int creationSpeed) {
         PaymentAppFactory.getInstance().addAdditionalFactory(
-                (webContents, methodNames, callback) -> {
+                (webContents, methodNames, mayCrawlUnusued, callback) -> {
                     final TestPay app = new TestPay(appMethodName, instrumentPresence,
                             responseSpeed);
                     if (creationSpeed == IMMEDIATE_CREATION) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/test/ScreenShooter.java b/chrome/android/javatests/src/org/chromium/chrome/browser/test/ScreenShooter.java
index 4382b7c..878de8b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/test/ScreenShooter.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/test/ScreenShooter.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.annotation.SuppressLint;
 import android.app.Instrumentation;
 import android.content.res.Configuration;
 import android.graphics.Point;
@@ -93,6 +94,7 @@
  * }
  * </pre>
  */
+@SuppressLint("SetWorldReadable")
 public class ScreenShooter extends TestWatcher {
     private static final String SCREENSHOT_DIR =
             "org.chromium.base.test.util.Screenshooter.ScreenshotDir";
@@ -204,6 +206,9 @@
             File shotFile = File.createTempFile(shotName, IMAGE_SUFFIX, new File(mBaseDir));
             assertTrue("Screenshot " + shotName, mDevice.takeScreenshot(shotFile));
             writeImageDescription(shotFile, filters, tags, metadata);
+            // Set as world readable so that the test runner can read it from /data/local/tmp
+            // without having to run as root
+            shotFile.setReadable(true, false);
         } catch (IOException e) {
             fail("Cannot create shot files " + e.toString());
         }
@@ -231,8 +236,12 @@
         String jsonFileName =
                 shotFileName.substring(0, shotFileName.length() - IMAGE_SUFFIX.length())
                 + JSON_SUFFIX;
-        try (FileWriter fileWriter = new FileWriter(new File(mBaseDir, jsonFileName));) {
+        File descriptionFile = new File(mBaseDir, jsonFileName);
+        try (FileWriter fileWriter = new FileWriter(descriptionFile)) {
             fileWriter.write(imageDescription.toString());
         }
+        // Set as world readable so that the test runner can read it from /data/local/tmp without
+        // having to run as root
+        descriptionFile.setReadable(true, false);
     }
 }
diff --git a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/AutofillTest.java b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/AutofillTest.java
index b58b57c9..9988077 100644
--- a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/AutofillTest.java
+++ b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/AutofillTest.java
@@ -90,7 +90,7 @@
     @LargeTest
     @Feature({"Sync"})
     public void testDownloadAutofill() throws Exception {
-        addServerAutofillProfile(STREET, CITY, STATE, ZIP);
+        addServerAutofillProfile(getServerAutofillProfile(STREET, CITY, STATE, ZIP));
         SyncTestUtil.triggerSync();
         waitForClientAutofillProfileCount(1);
 
@@ -111,14 +111,14 @@
     @Feature({"Sync"})
     public void testDownloadAutofillModification() throws Exception {
         // Add the entity to test modifying.
-        EntitySpecifics specifics = addServerAutofillProfile(STREET, CITY, STATE, ZIP);
+        addServerAutofillProfile(getServerAutofillProfile(STREET, CITY, STATE, ZIP));
         SyncTestUtil.triggerSync();
         waitForClientAutofillProfileCount(1);
 
         // Modify on server, sync, and verify modification locally.
         Autofill autofill = getClientAutofillProfiles().get(0);
-        specifics.autofillProfile.addressHomeCity = MODIFIED_CITY;
-        mSyncTestRule.getFakeServerHelper().modifyEntitySpecifics(autofill.id, specifics);
+        mSyncTestRule.getFakeServerHelper().modifyEntitySpecifics(
+                autofill.id, getServerAutofillProfile(STREET, MODIFIED_CITY, STATE, ZIP));
         SyncTestUtil.triggerSync();
         mSyncTestRule.pollInstrumentationThread(new ClientAutofillCriteria() {
             @Override
@@ -135,7 +135,7 @@
     @Feature({"Sync"})
     public void testDownloadDeletedAutofill() throws Exception {
         // Add the entity to test deleting.
-        addServerAutofillProfile(STREET, CITY, STATE, ZIP);
+        addServerAutofillProfile(getServerAutofillProfile(STREET, CITY, STATE, ZIP));
         SyncTestUtil.triggerSync();
         waitForClientAutofillProfileCount(1);
 
@@ -153,24 +153,27 @@
     public void testDisabledNoDownloadAutofill() throws Exception {
         // The AUTOFILL type here controls both AUTOFILL and AUTOFILL_PROFILE.
         mSyncTestRule.disableDataType(ModelType.AUTOFILL);
-        addServerAutofillProfile(STREET, CITY, STATE, ZIP);
+        addServerAutofillProfile(getServerAutofillProfile(STREET, CITY, STATE, ZIP));
         SyncTestUtil.triggerSyncAndWaitForCompletion();
         assertClientAutofillProfileCount(0);
     }
 
-    private EntitySpecifics addServerAutofillProfile(
+    private EntitySpecifics getServerAutofillProfile(
             String street, String city, String state, String zip) {
-        EntitySpecifics specifics = new EntitySpecifics();
-        AutofillProfileSpecifics profile = new AutofillProfileSpecifics();
-        profile.guid = GUID;
-        profile.origin = ORIGIN;
-        profile.addressHomeLine1 = street;
-        profile.addressHomeCity = city;
-        profile.addressHomeState = state;
-        profile.addressHomeZip = zip;
-        specifics.autofillProfile = profile;
-        mSyncTestRule.getFakeServerHelper().injectUniqueClientEntity(street /* name */, specifics);
-        return specifics;
+        AutofillProfileSpecifics profile = AutofillProfileSpecifics.newBuilder()
+                                                   .setGuid(GUID)
+                                                   .setOrigin(ORIGIN)
+                                                   .setAddressHomeLine1(street)
+                                                   .setAddressHomeCity(city)
+                                                   .setAddressHomeState(state)
+                                                   .setAddressHomeZip(zip)
+                                                   .build();
+        return EntitySpecifics.newBuilder().setAutofillProfile(profile).build();
+    }
+
+    private void addServerAutofillProfile(EntitySpecifics specifics) {
+        mSyncTestRule.getFakeServerHelper().injectUniqueClientEntity(
+                specifics.getAutofillProfile().getAddressHomeLine1() /* name */, specifics);
     }
 
     private List<Autofill> getClientAutofillProfiles() throws JSONException {
diff --git a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/BookmarksTest.java b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/BookmarksTest.java
index ee3d740..625d4ae 100644
--- a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/BookmarksTest.java
+++ b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/BookmarksTest.java
@@ -492,10 +492,10 @@
                 mSyncTestRule.getFakeServerHelper().getSyncEntitiesByModelType(ModelType.BOOKMARKS);
         List<Bookmark> bookmarks = new ArrayList<Bookmark>(entities.size());
         for (SyncEntity entity : entities) {
-            String id = entity.idString;
-            String parentId = entity.parentIdString;
-            BookmarkSpecifics specifics = entity.specifics.bookmark;
-            bookmarks.add(new Bookmark(id, specifics.title, specifics.url, parentId));
+            String id = entity.getIdString();
+            String parentId = entity.getParentIdString();
+            BookmarkSpecifics specifics = entity.getSpecifics().getBookmark();
+            bookmarks.add(new Bookmark(id, specifics.getTitle(), specifics.getUrl(), parentId));
         }
         return bookmarks;
     }
diff --git a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/FakeServerHelper.java b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/FakeServerHelper.java
index 4f011f7..4d57c37 100644
--- a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/FakeServerHelper.java
+++ b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/FakeServerHelper.java
@@ -6,8 +6,7 @@
 
 import android.content.Context;
 
-import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
-import com.google.protobuf.nano.MessageNano;
+import com.google.protobuf.InvalidProtocolBufferException;
 
 import org.chromium.base.ThreadUtils;
 import org.chromium.components.sync.protocol.EntitySpecifics;
@@ -190,14 +189,12 @@
         checkFakeServerInitialized("useFakeServer must be called before getting sync entities.");
         return ThreadUtils.runOnUiThreadBlocking(new Callable<List<SyncEntity>>() {
             @Override
-            public List<SyncEntity> call() throws InvalidProtocolBufferNanoException {
+            public List<SyncEntity> call() throws InvalidProtocolBufferException {
                 byte[][] serializedEntities = nativeGetSyncEntitiesByModelType(
                         mNativeFakeServerHelperAndroid, sNativeFakeServer, modelType);
                 List<SyncEntity> entities = new ArrayList<SyncEntity>(serializedEntities.length);
                 for (int i = 0; i < serializedEntities.length; i++) {
-                    SyncEntity entity = new SyncEntity();
-                    MessageNano.mergeFrom(entity, serializedEntities[i]);
-                    entities.add(entity);
+                    entities.add(SyncEntity.parseFrom(serializedEntities[i]));
                 }
                 return entities;
             }
@@ -220,7 +217,7 @@
                 // The protocol buffer is serialized as a byte array because it can be easily
                 // deserialized from this format in native code.
                 nativeInjectUniqueClientEntity(mNativeFakeServerHelperAndroid, sNativeFakeServer,
-                        name, MessageNano.toByteArray(entitySpecifics));
+                        name, entitySpecifics.toByteArray());
                 return null;
             }
         });
@@ -240,7 +237,7 @@
                 // The protocol buffer is serialized as a byte array because it can be easily
                 // deserialized from this format in native code.
                 nativeModifyEntitySpecifics(mNativeFakeServerHelperAndroid, sNativeFakeServer, id,
-                        MessageNano.toByteArray(entitySpecifics));
+                        entitySpecifics.toByteArray());
                 return null;
             }
         });
diff --git a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/OpenTabsTest.java b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/OpenTabsTest.java
index 5dab9258..33c2fa4 100644
--- a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/OpenTabsTest.java
+++ b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/OpenTabsTest.java
@@ -242,37 +242,42 @@
         }
     }
 
-    private EntitySpecifics makeSessionEntity(String tag, String clientName, int numTabs) {
-        EntitySpecifics specifics = new EntitySpecifics();
-        specifics.session = new SessionSpecifics();
-        specifics.session.sessionTag = tag;
-        specifics.session.header = new SessionHeader();
-        specifics.session.header.clientName = clientName;
-        specifics.session.header.deviceType = SyncEnums.TYPE_PHONE;
-        SessionWindow window = new SessionWindow();
-        window.windowId = 0;
-        window.selectedTabIndex = 0;
-        window.tab = new int[numTabs];
+    private SessionWindow makeSessionWindow(int numTabs) {
+        SessionWindow.Builder windowBuilder =
+                SessionWindow.newBuilder().setWindowId(0).setSelectedTabIndex(0);
         for (int i = 0; i < numTabs; i++) {
-            window.tab[i] = i;
+            windowBuilder.addTab(i); // Updates |windowBuilder| internal state.
         }
-        specifics.session.header.window = new SessionWindow[] { window };
-        return specifics;
+        return windowBuilder.build();
+    }
+
+    private EntitySpecifics makeSessionEntity(String tag, String clientName, int numTabs) {
+        SessionSpecifics session =
+                SessionSpecifics.newBuilder()
+                        .setSessionTag(tag)
+                        .setHeader(SessionHeader.newBuilder()
+                                           .setClientName(clientName)
+                                           .setDeviceType(SyncEnums.DeviceType.TYPE_PHONE)
+                                           .addWindow(makeSessionWindow(numTabs))
+                                           .build())
+                        .build();
+        return EntitySpecifics.newBuilder().setSession(session).build();
     }
 
     private EntitySpecifics makeTabEntity(String tag, String url, int id) {
-        EntitySpecifics specifics = new EntitySpecifics();
-        specifics.session = new SessionSpecifics();
-        specifics.session.sessionTag = tag;
-        specifics.session.tabNodeId = id;
-        SessionTab tab = new SessionTab();
-        tab.tabId = id;
-        tab.currentNavigationIndex = 0;
-        TabNavigation nav = new TabNavigation();
-        nav.virtualUrl = url;
-        tab.navigation = new TabNavigation[] { nav };
-        specifics.session.tab = tab;
-        return specifics;
+        SessionSpecifics session =
+                SessionSpecifics.newBuilder()
+                        .setSessionTag(tag)
+                        .setTabNodeId(id)
+                        .setTab(SessionTab.newBuilder()
+                                        .setTabId(id)
+                                        .setCurrentNavigationIndex(0)
+                                        .addNavigation(TabNavigation.newBuilder()
+                                                               .setVirtualUrl(url)
+                                                               .build())
+                                        .build())
+                        .build();
+        return EntitySpecifics.newBuilder().setSession(session).build();
     }
 
     private void deleteServerTabsForClient(String clientName) throws JSONException {
diff --git a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/TypedUrlsTest.java b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/TypedUrlsTest.java
index da1e562..913be39 100644
--- a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/TypedUrlsTest.java
+++ b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/TypedUrlsTest.java
@@ -132,12 +132,16 @@
     }
 
     private void addServerTypedUrl(String url) throws InterruptedException {
-        EntitySpecifics specifics = new EntitySpecifics();
-        specifics.typedUrl = new TypedUrlSpecifics();
-        specifics.typedUrl.url = url;
-        specifics.typedUrl.title = url;
-        specifics.typedUrl.visits = new long[] {getCurrentTimeInMicroseconds()};
-        specifics.typedUrl.visitTransitions = new int[]{SyncEnums.TYPED};
+        EntitySpecifics specifics =
+                EntitySpecifics.newBuilder()
+                        .setTypedUrl(TypedUrlSpecifics.newBuilder()
+                                             .setUrl(url)
+                                             .setTitle(url)
+                                             .addVisits(getCurrentTimeInMicroseconds())
+                                             .addVisitTransitions(
+                                                     SyncEnums.PageTransition.TYPED.getNumber())
+                                             .build())
+                        .build();
         mSyncTestRule.getFakeServerHelper().injectUniqueClientEntity(url /* name */, specifics);
     }
 
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 06f568b4..ac9b2ae8 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -10933,6 +10933,12 @@
       <message name="IDS_VR_REPOSITION_LABEL" desc="This text appears over a window to explain to the user how to manipulate the window.">
         Move the controller to reposition. Click to finish.
       </message>
+      <message name="IDS_VR_MENU_NEW_INCOGNITO_TAB" desc="Menu item for opening a new incognito tab that facilitates pseudononymous browsing. [CHAR-LIMIT=27]">
+        New incognito tab
+      </message>
+      <message name="IDS_VR_MENU_CLOSE_INCOGNITO_TABS" desc="Menu item for closing all open incognito tabs. [CHAR-LIMIT=27]">
+        Close incognito tabs
+     </message>
     </if>
 
     <message name="IDS_CONFIRM_FILE_UPLOAD_TITLE" desc="Title of dialog for confirming that many files are about to be uploaded / given to the site. [ICU Syntax]">
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index d846c71..077e7ba2 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -2455,6 +2455,18 @@
   <message name="IDS_SETTINGS_SITE_SETTINGS_FLASH" desc="Label for the Flash site settings.">
     Flash
   </message>
+  <message name="IDS_SETTINGS_SITE_SETTINGS_PAYMENT_HANDLER" desc="Label for the payment handler site settings.">
+    Payment Handlers
+  </message>
+  <message name="IDS_SETTINGS_SITE_SETTINGS_PAYMENT_HANDLER_ALLOW" desc="The allow label for payment handler installation in site settings.">
+    Allow sites to install payment handlers
+  </message>
+  <message name="IDS_SETTINGS_SITE_SETTINGS_PAYMENT_HANDLER_ALLOW_RECOMMENDED" desc="The allow label for payment handler installation in site settings (with the 'recommended' suffix).">
+    Allow sites to install payment handlers (recommended)
+  </message>
+  <message name="IDS_SETTINGS_SITE_SETTINGS_PAYMENT_HANDLER_BLOCK" desc="The block label for payment handler installation access in site settings.">
+    Do not allow any site to install payment handlers
+  </message>
   <message name="IDS_SETTINGS_SITE_SETTINGS_PDF_DOCUMENTS" desc="Label for the PDF documents site settings.">
     PDF documents
   </message>
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index b284e69ae..a063c72 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1605,8 +1605,6 @@
     "//components/captive_portal",
     "//components/certificate_reporting",
     "//components/certificate_transparency",
-    "//components/chrome_cleaner/public/constants",
-    "//components/chrome_cleaner/public/interfaces",
     "//components/cloud_devices/common",
     "//components/component_updater",
     "//components/component_updater:crl_set_remover",
@@ -2911,6 +2909,8 @@
       "//components/browser_watcher",
       "//components/browser_watcher:browser_watcher_client",
       "//components/browser_watcher:stability_client",
+      "//components/chrome_cleaner/public/constants",
+      "//components/chrome_cleaner/public/interfaces",
       "//third_party/crashpad/crashpad/client:client",
       "//third_party/iaccessible2",
       "//third_party/isimpledom",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index d6ea445..695cc5b8 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3124,10 +3124,6 @@
      MULTI_VALUE_TYPE(kForceColorProfileChoices)},
 
 #if defined(OS_CHROMEOS)
-    {"use-monitor-color-space", flag_descriptions::kUseMonitorColorSpaceName,
-     flag_descriptions::kUseMonitorColorSpaceDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(features::kUseMonitorColorSpace)},
-
     {"quick-unlock-pin-signin", flag_descriptions::kQuickUnlockPinSignin,
      flag_descriptions::kQuickUnlockPinSigninDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(features::kQuickUnlockPinSignin)},
diff --git a/chrome/browser/android/customtabs/detached_resource_request.cc b/chrome/browser/android/customtabs/detached_resource_request.cc
index 0f8a481..45dcb14 100644
--- a/chrome/browser/android/customtabs/detached_resource_request.cc
+++ b/chrome/browser/android/customtabs/detached_resource_request.cc
@@ -14,6 +14,7 @@
 #include "content/public/common/referrer.h"
 #include "content/public/common/resource_type.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
+#include "net/url_request/url_request_job.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/simple_url_loader.h"
 #include "url/gurl.h"
@@ -32,10 +33,12 @@
     content::BrowserContext* browser_context,
     const GURL& url,
     const GURL& site_for_cookies,
+    const net::URLRequest::ReferrerPolicy referrer_policy,
     DetachedResourceRequest::OnResultCallback cb) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   std::unique_ptr<DetachedResourceRequest> detached_request(
-      new DetachedResourceRequest(url, site_for_cookies, std::move(cb)));
+      new DetachedResourceRequest(url, site_for_cookies, referrer_policy,
+                                  std::move(cb)));
   Start(std::move(detached_request), browser_context);
 }
 
@@ -44,6 +47,7 @@
 DetachedResourceRequest::DetachedResourceRequest(
     const GURL& url,
     const GURL& site_for_cookies,
+    net::URLRequest::ReferrerPolicy referrer_policy,
     DetachedResourceRequest::OnResultCallback cb)
     : url_(url), site_for_cookies_(site_for_cookies), cb_(std::move(cb)) {
   net::NetworkTrafficAnnotationTag traffic_annotation =
@@ -70,9 +74,10 @@
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->method = "GET";
   resource_request->url = url_;
-  resource_request->referrer = site_for_cookies_;
-  resource_request->referrer_policy =
-      content::Referrer::GetDefaultReferrerPolicy();
+  // The referrer is stripped if it's not set properly initially.
+  resource_request->referrer = net::URLRequestJob::ComputeReferrerForPolicy(
+      referrer_policy, site_for_cookies_, url_);
+  resource_request->referrer_policy = referrer_policy;
   resource_request->site_for_cookies = site_for_cookies_;
   resource_request->request_initiator = url::Origin::Create(site_for_cookies_);
   resource_request->resource_type = content::RESOURCE_TYPE_SUB_RESOURCE;
diff --git a/chrome/browser/android/customtabs/detached_resource_request.h b/chrome/browser/android/customtabs/detached_resource_request.h
index 863bd5c..469f1148 100644
--- a/chrome/browser/android/customtabs/detached_resource_request.h
+++ b/chrome/browser/android/customtabs/detached_resource_request.h
@@ -11,6 +11,7 @@
 #include "base/bind_helpers.h"
 #include "base/callback.h"
 #include "base/time/time.h"
+#include "net/url_request/url_request.h"
 #include "url/gurl.h"
 
 namespace network {
@@ -42,11 +43,13 @@
   static void CreateAndStart(content::BrowserContext* browser_context,
                              const GURL& url,
                              const GURL& first_party_for_cookies,
+                             net::URLRequest::ReferrerPolicy referer_policy,
                              OnResultCallback cb = base::DoNothing());
 
  private:
   DetachedResourceRequest(const GURL& url,
                           const GURL& site_for_cookies,
+                          net::URLRequest::ReferrerPolicy referer_policy,
                           OnResultCallback cb);
 
   static void Start(std::unique_ptr<DetachedResourceRequest> request,
diff --git a/chrome/browser/android/customtabs/detached_resource_request_android.cc b/chrome/browser/android/customtabs/detached_resource_request_android.cc
index 33283554..1504dadc 100644
--- a/chrome/browser/android/customtabs/detached_resource_request_android.cc
+++ b/chrome/browser/android/customtabs/detached_resource_request_android.cc
@@ -8,7 +8,9 @@
 #include "chrome/browser/android/customtabs/detached_resource_request.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_android.h"
+#include "content/public/common/referrer.h"
 #include "jni/CustomTabsConnection_jni.h"
+#include "third_party/WebKit/public/platform/WebReferrerPolicy.h"
 #include "url/gurl.h"
 
 namespace customtabs {
@@ -18,7 +20,8 @@
     const base::android::JavaParamRef<jclass>& jcaller,
     const base::android::JavaParamRef<jobject>& profile,
     const base::android::JavaParamRef<jstring>& url,
-    const base::android::JavaParamRef<jstring>& origin) {
+    const base::android::JavaParamRef<jstring>& origin,
+    jint referrer_policy) {
   DCHECK(profile && url && origin);
 
   Profile* native_profile = ProfileAndroid::FromProfileAndroid(profile);
@@ -29,8 +32,12 @@
   DCHECK(native_url.is_valid());
   DCHECK(native_origin.is_valid());
 
-  DetachedResourceRequest::CreateAndStart(native_profile, native_url,
-                                          native_origin);
+  // Java only knows about the blink referrer policy.
+  net::URLRequest::ReferrerPolicy url_request_referrer_policy =
+      content::Referrer::ReferrerPolicyForUrlRequest(
+          static_cast<blink::WebReferrerPolicy>(referrer_policy));
+  DetachedResourceRequest::CreateAndStart(
+      native_profile, native_url, native_origin, url_request_referrer_policy);
 }
 
 }  // namespace customtabs
diff --git a/chrome/browser/android/customtabs/detached_resource_request_unittest.cc b/chrome/browser/android/customtabs/detached_resource_request_unittest.cc
index 6ab7867..4a7594a 100644
--- a/chrome/browser/android/customtabs/detached_resource_request_unittest.cc
+++ b/chrome/browser/android/customtabs/detached_resource_request_unittest.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/android/customtabs/detached_resource_request.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/common/referrer.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
@@ -152,8 +153,9 @@
     std::string cookie = content::GetCookies(browser_context(), url);
     ASSERT_EQ("", cookie);
 
-    DetachedResourceRequest::CreateAndStart(browser_context(), url,
-                                            site_for_cookies);
+    DetachedResourceRequest::CreateAndStart(
+        browser_context(), url, site_for_cookies,
+        content::Referrer::GetDefaultReferrerPolicy());
     first_request_waiter.Run();
     second_request_waiter.Run();
 
@@ -161,6 +163,33 @@
     ASSERT_EQ("acookie", cookie);
   }
 
+  void SetAndCheckReferrer(const std::string& initial_referrer,
+                           const std::string& expected_referrer,
+                           net::URLRequest::ReferrerPolicy policy) {
+    base::RunLoop request_completion_waiter;
+    base::RunLoop server_request_waiter;
+    HttpRequest::HeaderMap headers;
+
+    embedded_test_server()->RegisterRequestMonitor(
+        base::BindRepeating(&WatchPathAndReportHeaders, kEchoTitle, nullptr,
+                            &headers, server_request_waiter.QuitClosure()));
+    ASSERT_TRUE(embedded_test_server()->Start());
+    GURL url(embedded_test_server()->GetURL(kEchoTitle));
+    GURL site_for_cookies(initial_referrer);
+
+    DetachedResourceRequest::CreateAndStart(
+        browser_context(), url, site_for_cookies, policy,
+        base::BindOnce(
+            [](base::OnceClosure closure, bool success) {
+              EXPECT_TRUE(success);
+              std::move(closure).Run();
+            },
+            request_completion_waiter.QuitClosure()));
+    server_request_waiter.Run();
+    EXPECT_EQ(expected_referrer, headers["referer"]);
+    request_completion_waiter.Run();
+  }
+
  private:
   std::unique_ptr<TestingProfile> profile_;
   content::TestBrowserThreadBundle thread_bundle_;
@@ -182,6 +211,7 @@
 
   DetachedResourceRequest::CreateAndStart(
       browser_context(), url, site_for_cookies,
+      content::Referrer::GetDefaultReferrerPolicy(),
       base::BindOnce(
           [](base::OnceClosure closure, bool success) {
             EXPECT_TRUE(success);
@@ -204,6 +234,7 @@
 
   DetachedResourceRequest::CreateAndStart(
       browser_context(), url, site_for_cookies,
+      content::Referrer::GetDefaultReferrerPolicy(),
       base::BindOnce(
           [](base::OnceClosure closure, bool success) {
             EXPECT_FALSE(success);
@@ -230,8 +261,9 @@
 
   // No request coalescing, and no cache hit for a no-cache resource.
   for (int i = 0; i < 2; ++i) {
-    DetachedResourceRequest::CreateAndStart(browser_context(), url,
-                                            site_for_cookies);
+    DetachedResourceRequest::CreateAndStart(
+        browser_context(), url, site_for_cookies,
+        content::Referrer::GetDefaultReferrerPolicy());
   }
   request_waiter.Run();
   EXPECT_EQ(site_for_cookies.spec(), headers["referer"]);
@@ -250,8 +282,9 @@
   // Downgrade, as the server is over HTTP.
   GURL site_for_cookies("https://cats.google.com");
 
-  DetachedResourceRequest::CreateAndStart(browser_context(), url,
-                                          site_for_cookies);
+  DetachedResourceRequest::CreateAndStart(
+      browser_context(), url, site_for_cookies,
+      content::Referrer::GetDefaultReferrerPolicy());
   request_waiter.Run();
   EXPECT_EQ("", headers["referer"]);
 }
@@ -276,8 +309,9 @@
                                           redirected_url.spec()));
   GURL site_for_cookies(embedded_test_server()->base_url());
 
-  DetachedResourceRequest::CreateAndStart(browser_context(), url,
-                                          site_for_cookies);
+  DetachedResourceRequest::CreateAndStart(
+      browser_context(), url, site_for_cookies,
+      content::Referrer::GetDefaultReferrerPolicy());
   first_request_waiter.Run();
   second_request_waiter.Run();
 }
@@ -304,6 +338,7 @@
 
   DetachedResourceRequest::CreateAndStart(
       browser_context(), url, site_for_cookies,
+      content::Referrer::GetDefaultReferrerPolicy(),
       base::BindOnce(
           [](base::OnceClosure closure, bool success) {
             EXPECT_TRUE(success);
@@ -316,4 +351,23 @@
   ASSERT_EQ("acookie", cookie);
 }
 
+TEST_F(DetachedResourceRequestTest, DefaultReferrerPolicy) {
+  // No Referrer on downgrade.
+  SetAndCheckReferrer("https://cats.google.com", "",
+                      content::Referrer::GetDefaultReferrerPolicy());
+}
+
+TEST_F(DetachedResourceRequestTest, OriginReferrerPolicy) {
+  // Only the origin, even for downgrades.
+  SetAndCheckReferrer("https://cats.google.com/cute-cats",
+                      "https://cats.google.com/",
+                      net::URLRequest::ReferrerPolicy::ORIGIN);
+}
+
+TEST_F(DetachedResourceRequestTest, NeverClearReferrerPolicy) {
+  SetAndCheckReferrer("https://cats.google.com/cute-cats",
+                      "https://cats.google.com/cute-cats",
+                      net::URLRequest::ReferrerPolicy::NEVER_CLEAR_REFERRER);
+}
+
 }  // namespace customtabs
diff --git a/chrome/browser/android/ntp/OWNERS b/chrome/browser/android/ntp/OWNERS
index 29fd4353..df11064 100644
--- a/chrome/browser/android/ntp/OWNERS
+++ b/chrome/browser/android/ntp/OWNERS
@@ -1,4 +1,6 @@
 file://chrome/android/java/src/org/chromium/chrome/browser/ntp/OWNERS
 bauerb@chromium.org
+fgorski@chromium.org
 markusheintz@chromium.org
+pnoland@chromium.org
 treib@chromium.org
diff --git a/chrome/browser/android/payments/service_worker_payment_app_bridge.cc b/chrome/browser/android/payments/service_worker_payment_app_bridge.cc
index bd1ae15..63ae37d7 100644
--- a/chrome/browser/android/payments/service_worker_payment_app_bridge.cc
+++ b/chrome/browser/android/payments/service_worker_payment_app_bridge.cc
@@ -311,6 +311,7 @@
     const JavaParamRef<jclass>& jcaller,
     const JavaParamRef<jobject>& jweb_contents,
     const JavaParamRef<jobjectArray>& jmethod_data,
+    jboolean jmay_crawl_for_installable_payment_apps,
     const JavaParamRef<jobject>& jcallback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -323,6 +324,7 @@
           Profile::FromBrowserContext(web_contents->GetBrowserContext()),
           ServiceAccessType::EXPLICIT_ACCESS),
       ConvertPaymentMethodDataFromJavaToNative(env, jmethod_data),
+      jmay_crawl_for_installable_payment_apps,
       base::BindOnce(&OnGotAllPaymentApps,
                      ScopedJavaGlobalRef<jobject>(env, jweb_contents),
                      ScopedJavaGlobalRef<jobject>(env, jcallback)),
diff --git a/chrome/browser/chromeos/fileapi/external_file_url_util.cc b/chrome/browser/chromeos/fileapi/external_file_url_util.cc
index 542e9d5..9fa61d9a7 100644
--- a/chrome/browser/chromeos/fileapi/external_file_url_util.cc
+++ b/chrome/browser/chromeos/fileapi/external_file_url_util.cc
@@ -44,8 +44,10 @@
 base::FilePath ExternalFileURLToVirtualPath(const GURL& url) {
   if (!url.is_valid() || url.scheme() != content::kExternalFileScheme)
     return base::FilePath();
+  const std::string url_string =
+      url.GetContent() + (url.has_ref() ? "#" + url.ref() : "");
   const std::string path_string =
-      net::UnescapeURLComponent(url.GetContent(), net::UnescapeRule::NORMAL);
+      net::UnescapeURLComponent(url_string, net::UnescapeRule::NORMAL);
   return base::FilePath::FromUTF8Unsafe(path_string);
 }
 
diff --git a/chrome/browser/client_hints/client_hints_browsertest.cc b/chrome/browser/client_hints/client_hints_browsertest.cc
index 5bd3394..0e2c8ce 100644
--- a/chrome/browser/client_hints/client_hints_browsertest.cc
+++ b/chrome/browser/client_hints/client_hints_browsertest.cc
@@ -10,6 +10,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/content_settings/tab_specific_content_settings.h"
 #include "chrome/browser/metrics/subprocess_metrics_provider.h"
 #include "chrome/browser/net/url_request_mock_util.h"
 #include "chrome/browser/profiles/profile.h"
@@ -170,6 +171,18 @@
     expect_client_hints_on_subresources_ = expect_client_hints;
   }
 
+  // Verify that the user is not notified that cookies or JavaScript were
+  // blocked on the webpage due to the checks done by client hints.
+  void VerifyContentSettingsNotNotified() const {
+    content::WebContents* web_contents =
+        browser()->tab_strip_model()->GetActiveWebContents();
+    EXPECT_FALSE(TabSpecificContentSettings::FromWebContents(web_contents)
+                     ->IsContentBlocked(CONTENT_SETTINGS_TYPE_COOKIES));
+
+    EXPECT_FALSE(TabSpecificContentSettings::FromWebContents(web_contents)
+                     ->IsContentBlocked(CONTENT_SETTINGS_TYPE_JAVASCRIPT));
+  }
+
   const GURL& accept_ch_with_lifetime_http_local_url() const {
     return accept_ch_with_lifetime_http_local_url_;
   }
@@ -594,6 +607,7 @@
       ->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_CLIENT_HINTS, std::string(),
                               &host_settings);
   EXPECT_EQ(0u, host_settings.size());
+  VerifyContentSettingsNotNotified();
 
   // Allow cookies.
   cookie_settings_->SetCookieSetting(accept_ch_without_lifetime_url(),
@@ -648,6 +662,7 @@
   ui_test_utils::NavigateToURL(browser(),
                                without_accept_ch_without_lifetime_url());
   EXPECT_EQ(0u, count_client_hints_headers_seen());
+  VerifyContentSettingsNotNotified();
 
   // Allow the cookies: Client hints should now be attached.
   HostContentSettingsMapFactory::GetForProfile(browser()->profile())
@@ -694,6 +709,7 @@
       ->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_CLIENT_HINTS, std::string(),
                               &host_settings);
   EXPECT_EQ(0u, host_settings.size());
+  VerifyContentSettingsNotNotified();
 
   // Allow the JavaScript: Client hint preferences should be persisted.
   HostContentSettingsMapFactory::GetForProfile(browser()->profile())
@@ -748,6 +764,7 @@
   ui_test_utils::NavigateToURL(browser(),
                                without_accept_ch_without_lifetime_url());
   EXPECT_EQ(0u, count_client_hints_headers_seen());
+  VerifyContentSettingsNotNotified();
 
   // Allow the Javascript: Client hints should now be attached.
   HostContentSettingsMapFactory::GetForProfile(browser()->profile())
@@ -813,6 +830,7 @@
   EXPECT_EQ(3u, count_client_hints_headers_seen());
   EXPECT_EQ(2u, third_party_request_count_seen());
   EXPECT_EQ(0u, third_party_client_hints_count_seen());
+  VerifyContentSettingsNotNotified();
 
   // Clear settings.
   HostContentSettingsMapFactory::GetForProfile(browser()->profile())
@@ -859,6 +877,7 @@
   // Client hints are not attached to third party subresources even though
   // cookies are allowed only for the first party origin.
   EXPECT_EQ(0u, third_party_client_hints_count_seen());
+  VerifyContentSettingsNotNotified();
 
   // Allow cookies.
   cookie_settings_->SetCookieSetting(accept_ch_without_lifetime_img_localhost(),
diff --git a/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc b/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc
index c5e7e37..f535ebb 100644
--- a/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc
+++ b/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "chrome/test/views/scoped_macviews_browser_mode.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_types.h"
 #include "extensions/browser/extension_registry.h"
@@ -423,6 +424,17 @@
   EXPECT_FALSE(browser_action_test_util->HasPopup());
 }
 
+class BrowserActionInteractiveViewsTest : public BrowserActionInteractiveTest {
+ public:
+  BrowserActionInteractiveViewsTest() = default;
+  ~BrowserActionInteractiveViewsTest() override = default;
+
+ private:
+  test::ScopedMacViewsBrowserMode views_mode_{true};
+
+  DISALLOW_COPY_AND_ASSIGN(BrowserActionInteractiveViewsTest);
+};
+
 // BrowserActionTestUtil::InspectPopup() is not implemented for a Cocoa browser.
 #if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
 #define MAYBE_CloseBrowserWithDevTools CloseBrowserWithDevTools
@@ -430,7 +442,7 @@
 #define MAYBE_CloseBrowserWithDevTools DISABLED_CloseBrowserWithDevTools
 #endif
 // Test closing the browser while inspecting an extension popup with dev tools.
-IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest,
+IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveViewsTest,
                        MAYBE_CloseBrowserWithDevTools) {
   if (!ShouldRunPopupTest())
     return;
diff --git a/chrome/browser/extensions/api/identity/gaia_web_auth_flow.cc b/chrome/browser/extensions/api/identity/gaia_web_auth_flow.cc
index 03882c28ed..7bd31f7b 100644
--- a/chrome/browser/extensions/api/identity/gaia_web_auth_flow.cc
+++ b/chrome/browser/extensions/api/identity/gaia_web_auth_flow.cc
@@ -38,7 +38,7 @@
                            "account_id",
                            token_key->account_id);
 
-  const char kOAuth2RedirectPathFormat[] = "/%s#";
+  const char kOAuth2RedirectPathFormat[] = "/%s";
   const char kOAuth2AuthorizeFormat[] =
       "?response_type=token&approval_prompt=force&authuser=0&"
       "client_id=%s&"
@@ -175,17 +175,14 @@
 
   // The format of the target URL is:
   //     reversed.oauth.client.id:/extensionid#access_token=TOKEN
-  //
-  // Because there is no double slash, everything after the scheme is
-  // interpreted as a path, including the fragment.
 
   if (url.scheme() == redirect_scheme_ && !url.has_host() && !url.has_port() &&
       base::StartsWith(url.GetContent(), redirect_path_prefix_,
-                       base::CompareCase::SENSITIVE)) {
+                       base::CompareCase::SENSITIVE) &&
+      url.has_ref()) {
     web_flow_.release()->DetachDelegateAndDelete();
 
-    std::string fragment = url.GetContent().substr(
-        redirect_path_prefix_.length(), std::string::npos);
+    std::string fragment = url.ref();
     base::StringPairs pairs;
     base::SplitStringIntoKeyValuePairs(fragment, '=', '&', &pairs);
     std::string access_token;
diff --git a/chrome/browser/net/proxy_browsertest.cc b/chrome/browser/net/proxy_browsertest.cc
index 78084c1..8d55d0e 100644
--- a/chrome/browser/net/proxy_browsertest.cc
+++ b/chrome/browser/net/proxy_browsertest.cc
@@ -109,8 +109,10 @@
   DISALLOW_COPY_AND_ASSIGN(ProxyBrowserTest);
 };
 
-#if defined(OS_CHROMEOS)
 // We bypass manually installed proxy for localhost on chromeos.
+// TODO(crbug.com/822614): Flaky on Windows Debug and ASAN bots.
+#if (defined(OS_CHROMEOS) || defined(ADDRESS_SANITIZER) || \
+     (defined(OS_WIN) && !defined(NDEBUG)))
 #define MAYBE_BasicAuthWSConnect DISABLED_BasicAuthWSConnect
 #else
 #define MAYBE_BasicAuthWSConnect BasicAuthWSConnect
diff --git a/chrome/browser/notifications/notification_platform_bridge_linux.cc b/chrome/browser/notifications/notification_platform_bridge_linux.cc
index 875b42e..49cbbd9f 100644
--- a/chrome/browser/notifications/notification_platform_bridge_linux.cc
+++ b/chrome/browser/notifications/notification_platform_bridge_linux.cc
@@ -268,8 +268,10 @@
         gfx::Image(*ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
                        IDR_PRODUCT_LOGO_256))
             .As1xPNGBytes();
-    PostTaskToTaskRunnerThread(base::BindOnce(
-        &NotificationPlatformBridgeLinuxImpl::InitOnTaskRunner, this));
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&NotificationPlatformBridgeLinuxImpl::InitOnTaskRunner,
+                       this));
   }
 
   void Display(
@@ -285,16 +287,19 @@
         notification, body_images_supported_.value(),
         /*include_small_image=*/false, /*include_icon_images=*/false);
 
-    PostTaskToTaskRunnerThread(base::BindOnce(
-        &NotificationPlatformBridgeLinuxImpl::DisplayOnTaskRunner, this,
-        notification_type, profile_id, is_incognito,
-        std::move(notification_copy)));
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &NotificationPlatformBridgeLinuxImpl::DisplayOnTaskRunner, this,
+            notification_type, profile_id, is_incognito,
+            std::move(notification_copy)));
   }
 
   void Close(const std::string& profile_id,
              const std::string& notification_id) override {
     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-    PostTaskToTaskRunnerThread(
+    task_runner_->PostTask(
+        FROM_HERE,
         base::BindOnce(&NotificationPlatformBridgeLinuxImpl::CloseOnTaskRunner,
                        this, profile_id, notification_id));
   }
@@ -304,9 +309,11 @@
       bool incognito,
       const GetDisplayedNotificationsCallback& callback) const override {
     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-    PostTaskToTaskRunnerThread(base::BindOnce(
-        &NotificationPlatformBridgeLinuxImpl::GetDisplayedOnTaskRunner, this,
-        profile_id, incognito, callback));
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &NotificationPlatformBridgeLinuxImpl::GetDisplayedOnTaskRunner,
+            this, profile_id, incognito, callback));
   }
 
   void SetReadyCallback(NotificationBridgeReadyCallback callback) override {
@@ -320,8 +327,10 @@
 
   void CleanUp() {
     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-    PostTaskToTaskRunnerThread(base::BindOnce(
-        &NotificationPlatformBridgeLinuxImpl::CleanUpOnTaskRunner, this));
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &NotificationPlatformBridgeLinuxImpl::CleanUpOnTaskRunner, this));
   }
 
  private:
@@ -385,20 +394,6 @@
     body_images_supported_ = body_images_supported;
   }
 
-  void PostTaskToUiThread(base::OnceClosure closure) const {
-    DCHECK(task_runner_->RunsTasksInCurrentSequence());
-    bool success = content::BrowserThread::PostTask(
-        content::BrowserThread::UI, FROM_HERE, std::move(closure));
-    DCHECK(success);
-  }
-
-  void PostTaskToTaskRunnerThread(base::OnceClosure closure) const {
-    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-    DCHECK(task_runner_);
-    bool success = task_runner_->PostTask(FROM_HERE, std::move(closure));
-    DCHECK(success);
-  }
-
   // Sets up the D-Bus connection.
   void InitOnTaskRunner() {
     DCHECK(task_runner_->RunsTasksInCurrentSequence());
@@ -440,9 +435,11 @@
           ConnectionInitializationStatusCode::MISSING_REQUIRED_CAPABILITIES);
       return;
     }
-    PostTaskToUiThread(base::BindOnce(
-        &NotificationPlatformBridgeLinuxImpl::SetBodyImagesSupported, this,
-        base::ContainsKey(capabilities_, kCapabilityBodyImages)));
+    content::BrowserThread::PostTask(
+        content::BrowserThread::UI, FROM_HERE,
+        base::BindOnce(
+            &NotificationPlatformBridgeLinuxImpl::SetBodyImagesSupported, this,
+            base::ContainsKey(capabilities_, kCapabilityBodyImages)));
 
     dbus::MethodCall get_server_information_call(kFreedesktopNotificationsName,
                                                  "GetServerInformation");
@@ -586,7 +583,7 @@
             ResizeImageToFdoMaxSize(notification->image()).As1xPNGBytes());
         if (image_file) {
           body << "<img src=\""
-               << net::EscapePath(image_file->file_path().value())
+               << "file://" + net::EscapePath(image_file->file_path().value())
                << "\" alt=\"\"/>\n";
           data->resource_files.push_back(std::move(image_file));
         }
@@ -731,7 +728,9 @@
       if (data->profile_id == profile_id && data->is_incognito == incognito)
         displayed->insert(data->notification_id);
     }
-    PostTaskToUiThread(base::BindOnce(callback, std::move(displayed), true));
+    content::BrowserThread::PostTask(
+        content::BrowserThread::UI, FROM_HERE,
+        base::BindOnce(callback, std::move(displayed), true));
   }
 
   NotificationData* FindNotificationData(const std::string& notification_id,
@@ -762,15 +761,18 @@
     return nullptr;
   }
 
-  void ForwardNotificationOperation(NotificationData* data,
+  void ForwardNotificationOperation(const base::Location& location,
+                                    NotificationData* data,
                                     NotificationCommon::Operation operation,
                                     const base::Optional<int>& action_index,
                                     const base::Optional<bool>& by_user) {
     DCHECK(task_runner_->RunsTasksInCurrentSequence());
-    PostTaskToUiThread(base::BindOnce(
-        ForwardNotificationOperationOnUiThread, operation,
-        data->notification_type, data->origin_url, data->notification_id,
-        action_index, by_user, data->profile_id, data->is_incognito));
+    content::BrowserThread::PostTask(
+        content::BrowserThread::UI, location,
+        base::BindOnce(ForwardNotificationOperationOnUiThread, operation,
+                       data->notification_type, data->origin_url,
+                       data->notification_id, action_index, by_user,
+                       data->profile_id, data->is_incognito));
   }
 
   void OnActionInvoked(dbus::Signal* signal) {
@@ -788,13 +790,13 @@
       return;
 
     if (action == kDefaultButtonId) {
-      ForwardNotificationOperation(data, NotificationCommon::CLICK,
+      ForwardNotificationOperation(FROM_HERE, data, NotificationCommon::CLICK,
                                    base::nullopt /* action_index */,
                                    base::nullopt /* by_user */);
     } else if (action == kSettingsButtonId) {
-      ForwardNotificationOperation(data, NotificationCommon::SETTINGS,
-                                   base::nullopt /* action_index */,
-                                   base::nullopt /* by_user */);
+      ForwardNotificationOperation(
+          FROM_HERE, data, NotificationCommon::SETTINGS,
+          base::nullopt /* action_index */, base::nullopt /* by_user */);
     } else if (action == kCloseButtonId) {
       CloseOnTaskRunner(data->profile_id, data->notification_id);
     } else {
@@ -805,7 +807,7 @@
       size_t id_zero_based = id - data->action_start;
       if (id_zero_based >= n_buttons)
         return;
-      ForwardNotificationOperation(data, NotificationCommon::CLICK,
+      ForwardNotificationOperation(FROM_HERE, data, NotificationCommon::CLICK,
                                    id_zero_based, base::nullopt /* by_user */);
     }
   }
@@ -822,7 +824,7 @@
       return;
 
     // TODO(peter): Can we support |by_user| appropriately here?
-    ForwardNotificationOperation(data, NotificationCommon::CLOSE,
+    ForwardNotificationOperation(FROM_HERE, data, NotificationCommon::CLOSE,
                                  base::nullopt /* action_index */,
                                  true /* by_user */);
     notifications_.erase(data);
@@ -847,10 +849,12 @@
         "Notifications.Linux.BridgeInitializationStatus",
         static_cast<int>(status),
         static_cast<int>(ConnectionInitializationStatusCode::NUM_ITEMS));
-    PostTaskToUiThread(base::BindOnce(
-        &NotificationPlatformBridgeLinuxImpl::
-            OnConnectionInitializationFinishedOnUiThread,
-        this, status == ConnectionInitializationStatusCode::SUCCESS));
+    content::BrowserThread::PostTask(
+        content::BrowserThread::UI, FROM_HERE,
+        base::BindOnce(&NotificationPlatformBridgeLinuxImpl::
+                           OnConnectionInitializationFinishedOnUiThread,
+                       this,
+                       status == ConnectionInitializationStatusCode::SUCCESS));
   }
 
   void OnSignalConnected(const std::string& interface_name,
diff --git a/chrome/browser/notifications/notification_platform_bridge_linux_unittest.cc b/chrome/browser/notifications/notification_platform_bridge_linux_unittest.cc
index a3ef6c6..0a208b80 100644
--- a/chrome/browser/notifications/notification_platform_bridge_linux_unittest.cc
+++ b/chrome/browser/notifications/notification_platform_bridge_linux_unittest.cc
@@ -477,7 +477,8 @@
           [=](const NotificationRequest& request) {
             std::string file_name;
             EXPECT_TRUE(RE2::FullMatch(
-                request.body, "\\<img src=\\\"(.+)\\\" alt=\\\".*\\\"/\\>",
+                request.body,
+                "\\<img src=\\\"file://(.+)\\\" alt=\\\".*\\\"/\\>",
                 &file_name));
             std::string file_contents;
             EXPECT_TRUE(base::ReadFileToString(base::FilePath(file_name),
diff --git a/chrome/browser/notifications/notification_platform_bridge_win.cc b/chrome/browser/notifications/notification_platform_bridge_win.cc
index dc0265893..43148db 100644
--- a/chrome/browser/notifications/notification_platform_bridge_win.cc
+++ b/chrome/browser/notifications/notification_platform_bridge_win.cc
@@ -18,7 +18,9 @@
 #include "base/compiler_specific.h"
 #include "base/feature_list.h"
 #include "base/hash.h"
+#include "base/location.h"
 #include "base/logging.h"
+#include "base/sequenced_task_runner.h"
 #include "base/strings/string16.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
@@ -707,7 +709,8 @@
       notification, /*include_body_image=*/true, /*include_small_image=*/true,
       /*include_icon_images=*/true);
 
-  PostTaskToTaskRunnerThread(
+  task_runner_->PostTask(
+      FROM_HERE,
       base::BindOnce(&NotificationPlatformBridgeWinImpl::Display, impl_,
                      notification_type, profile_id, is_incognito,
                      std::move(notification_copy), std::move(metadata)));
@@ -716,16 +719,17 @@
 void NotificationPlatformBridgeWin::Close(const std::string& profile_id,
                                           const std::string& notification_id) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  PostTaskToTaskRunnerThread(
-      base::BindOnce(&NotificationPlatformBridgeWinImpl::Close, impl_,
-                     notification_id, profile_id));
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&NotificationPlatformBridgeWinImpl::Close,
+                                impl_, notification_id, profile_id));
 }
 
 void NotificationPlatformBridgeWin::GetDisplayed(
     const std::string& profile_id,
     bool incognito,
     const GetDisplayedNotificationsCallback& callback) const {
-  PostTaskToTaskRunnerThread(
+  task_runner_->PostTask(
+      FROM_HERE,
       base::BindOnce(&NotificationPlatformBridgeWinImpl::GetDisplayed, impl_,
                      profile_id, incognito, callback));
 }
@@ -733,7 +737,8 @@
 void NotificationPlatformBridgeWin::SetReadyCallback(
     NotificationBridgeReadyCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  PostTaskToTaskRunnerThread(
+  task_runner_->PostTask(
+      FROM_HERE,
       base::BindOnce(&NotificationPlatformBridgeWinImpl::SetReadyCallback,
                      impl_, std::move(callback)));
 }
@@ -779,22 +784,18 @@
          base::FeatureList::IsEnabled(features::kNativeNotifications);
 }
 
-void NotificationPlatformBridgeWin::PostTaskToTaskRunnerThread(
-    base::OnceClosure closure) const {
-  bool success = task_runner_->PostTask(FROM_HERE, std::move(closure));
-  DCHECK(success);
-}
-
 void NotificationPlatformBridgeWin::ForwardHandleEventForTesting(
     NotificationCommon::Operation operation,
     winui::Notifications::IToastNotification* notification,
     winui::Notifications::IToastActivatedEventArgs* args,
     const base::Optional<bool>& by_user) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  PostTaskToTaskRunnerThread(base::BindOnce(
-      &NotificationPlatformBridgeWinImpl::ForwardHandleEventForTesting, impl_,
-      operation, base::Unretained(notification), base::Unretained(args),
-      by_user));
+  task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &NotificationPlatformBridgeWinImpl::ForwardHandleEventForTesting,
+          impl_, operation, base::Unretained(notification),
+          base::Unretained(args), by_user));
 }
 
 void NotificationPlatformBridgeWin::SetDisplayedNotificationsForTesting(
diff --git a/chrome/browser/notifications/notification_platform_bridge_win.h b/chrome/browser/notifications/notification_platform_bridge_win.h
index 876f491..11da138c 100644
--- a/chrome/browser/notifications/notification_platform_bridge_win.h
+++ b/chrome/browser/notifications/notification_platform_bridge_win.h
@@ -6,16 +6,16 @@
 #define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_PLATFORM_BRIDGE_WIN_H_
 
 #include <windows.ui.notifications.h>
+
 #include <string>
 
-#include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/optional.h"
-#include "base/sequenced_task_runner.h"
 #include "chrome/browser/notifications/notification_platform_bridge.h"
 
 namespace base {
 class CommandLine;
+class SequencedTaskRunner;
 }
 
 class NotificationPlatformBridgeWinImpl;
@@ -62,8 +62,6 @@
   FRIEND_TEST_ALL_PREFIXES(NotificationPlatformBridgeWinUITest, HandleEvent);
   FRIEND_TEST_ALL_PREFIXES(NotificationPlatformBridgeWinUITest, HandleSettings);
 
-  void PostTaskToTaskRunnerThread(base::OnceClosure closure) const;
-
   // Simulates a click/dismiss event. Only for use in testing.
   // Note: Ownership of |notification| and |args| is retained by the caller.
   void ForwardHandleEventForTesting(
diff --git a/chrome/browser/ntp_snippets/download_suggestions_provider.cc b/chrome/browser/ntp_snippets/download_suggestions_provider.cc
index bb24ac7..3d7bd5a5 100644
--- a/chrome/browser/ntp_snippets/download_suggestions_provider.cc
+++ b/chrome/browser/ntp_snippets/download_suggestions_provider.cc
@@ -719,7 +719,6 @@
     bool notify,
     const std::vector<offline_pages::OfflinePageItem>&
         all_download_offline_pages) {
-
   std::set<std::string> old_dismissed_ids =
       ReadOfflinePageDismissedIDsFromPrefs();
   std::set<std::string> retained_dismissed_ids;
diff --git a/chrome/browser/payments/service_worker_payment_app_factory_browsertest.cc b/chrome/browser/payments/service_worker_payment_app_factory_browsertest.cc
index d49d36c..d1895f4 100644
--- a/chrome/browser/payments/service_worker_payment_app_factory_browsertest.cc
+++ b/chrome/browser/payments/service_worker_payment_app_factory_browsertest.cc
@@ -133,6 +133,7 @@
             Profile::FromBrowserContext(context),
             ServiceAccessType::EXPLICIT_ACCESS),
         method_data,
+        /*may_crawl_for_installable_payment_apps=*/true,
         base::BindOnce(
             &ServiceWorkerPaymentAppFactoryBrowserTest::OnGotAllPaymentApps,
             base::Unretained(this)),
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 bdb071be..d92651f9 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
@@ -436,10 +436,6 @@
         evt.target.state[StateType.EDITABLE])
       return;
 
-    // Skip richly editables.
-    if (evt.target.state[StateType.RICHLY_EDITABLE])
-      return;
-
     // Delegate to the edit text handler if this is an editable.
     if (evt.target.state[StateType.EDITABLE]) {
       this.onEditableChanged_(evt);
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js
index 69aa131..61bbd3f 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js
@@ -44,17 +44,7 @@
   this.node_ = node;
 
   chrome.automation.getDesktop(function(desktop) {
-    // A rich text field is one where selection gets placed on a DOM descendant
-    // to a root text field. This is one of:
-    // - content editables (detected via richly editable state)
-    // - role textbox without native text field support (i.e. not an input or
-    // textarea)
-    //
-    // For any of these, it must not be something from the desktop tree where we
-    // currently do not have rich text fields.
-    var useRichText = node.state[StateType.RICHLY_EDITABLE] ||
-        (node.htmlTag != 'input' && node.htmlTag != 'textarea' && node.root &&
-         node.root.role != RoleType.DESKTOP);
+    var useRichText = node.state[StateType.RICHLY_EDITABLE];
 
     /** @private {!AutomationEditableText} */
     this.editableText_ = useRichText ? new AutomationRichEditableText(node) :
diff --git a/chrome/browser/resources/chromeos/chromevox/testing/common.js b/chrome/browser/resources/chromeos/chromevox/testing/common.js
index 1a654c9..d5d7f5f1 100644
--- a/chrome/browser/resources/chromeos/chromevox/testing/common.js
+++ b/chrome/browser/resources/chromeos/chromevox/testing/common.js
@@ -50,9 +50,9 @@
  */
 TestUtils.createUrlForDoc = function(doc) {
   var docString = TestUtils.extractHtmlFromCommentEncodedString(doc);
-  return TestUtils.collapseWhitespace(
-      'data:text/html,<!doctype html>' +
-      docString.replace(/[\n\r]/g, '').trim());
+  return 'data:text/html,<!doctype html>' +
+      encodeURIComponent(TestUtils.collapseWhitespace(
+          docString.replace(/[\n\r]/g, '').trim()));
 };
 
 /**
diff --git a/chrome/browser/resources/chromeos/select_to_speak/compiled_resources2.gyp b/chrome/browser/resources/chromeos/select_to_speak/compiled_resources2.gyp
index 2591e84..b7a0583 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/compiled_resources2.gyp
+++ b/chrome/browser/resources/chromeos/select_to_speak/compiled_resources2.gyp
@@ -29,7 +29,6 @@
         '<(EXTERNS_GYP):accessibility_private',
         '<(EXTERNS_GYP):automation',
         '<(EXTERNS_GYP):chrome_extensions',
-        '<(EXTERNS_GYP):command_line_private',
         '<(EXTERNS_GYP):metrics_private',
        ],
       'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
diff --git a/chrome/browser/resources/chromeos/select_to_speak/options.html b/chrome/browser/resources/chromeos/select_to_speak/options.html
index 7945d08..1c71ed8d 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/options.html
+++ b/chrome/browser/resources/chromeos/select_to_speak/options.html
@@ -99,17 +99,6 @@
           </div>
         </div>
       </div>
-
-      <div id="behavior">
-        <h2 class="i18n" msgid="options_behavior"></h2>
-        <div class="option">
-          <input id="readAfterClose" type="checkbox" class="checkbox pref"
-                 name="readAfterClose" aria-labeledby="readAfterCloseLabel">
-          <label id="readAfterCloseLabel" class="i18n"
-                 msgid="options_read_after_close">
-          </label>
-        </div>
-      </div>
     </div>
 
     <script src="select_to_speak_options.js"></script>
diff --git a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
index a1250cc..5029e68 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
+++ b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
@@ -116,7 +116,7 @@
   this.highlightColor_ = '#5e9bff';
 
   /** @private {boolean} */
-  this.readAfterClose_ = false;
+  this.readAfterClose_ = true;
 
   /** @private {?NodeGroupItem} */
   this.currentNode_ = null;
@@ -882,10 +882,7 @@
     var updatePrefs =
         (function() {
           chrome.storage.sync.get(
-              [
-                'voice', 'rate', 'pitch', 'wordHighlight', 'highlightColor',
-                'readAfterClose'
-              ],
+              ['voice', 'rate', 'pitch', 'wordHighlight', 'highlightColor'],
               (function(prefs) {
                 if (prefs['voice']) {
                   this.voiceNameFromPrefs_ = prefs['voice'];
@@ -912,12 +909,6 @@
                   chrome.storage.sync.set(
                       {'highlightColor': this.highlightColor_});
                 }
-                if (prefs['readAfterClose'] !== undefined) {
-                  this.readAfterClose_ = prefs['readAfterClose'];
-                } else {
-                  chrome.storage.sync.set(
-                      {'readAfterClose': this.readAfterClose_});
-                }
               }).bind(this));
         }).bind(this);
 
@@ -944,9 +935,15 @@
           if (voices.length == 0)
             return;
 
-          voices.forEach((function(voice) {
-                           this.validVoiceNames_.add(voice.voiceName);
-                         }).bind(this));
+          voices.forEach((voice) => {
+            if (!voice.eventTypes.includes('start') ||
+                !voice.eventTypes.includes('end') ||
+                !voice.eventTypes.includes('word') ||
+                !voice.eventTypes.includes('cancelled')) {
+              return;
+            }
+            this.validVoiceNames_.add(voice.voiceName);
+          });
 
           voices.sort(function(a, b) {
             function score(voice) {
@@ -983,8 +980,8 @@
   updateFromNodeState_: function(nodeGroupItem, inForeground) {
     switch (getNodeState(nodeGroupItem.node)) {
       case NodeState.NODE_STATE_INVALID:
-        // If the node is invalid, stop speaking entirely if the user setting
-        // is not to continue reading.
+        // If the node is invalid, continue speech unless readAfterClose_
+        // is set to true. See https://crbug.com/818835 for more.
         if (this.readAfterClose_) {
           this.clearFocusRing_();
           this.visible_ = false;
diff --git a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak_options.js b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak_options.js
index 99df1f7..54276f5 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak_options.js
+++ b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak_options.js
@@ -37,13 +37,6 @@
             select.disabled = true;
           }
         });
-    this.syncCheckboxControlToPref_('readAfterClose', 'readAfterClose');
-    chrome.commandLinePrivate.hasSwitch(
-        'enable-experimental-accessibility-features',
-        (experimentalFeaturesEnabled) => {
-          let behaviorSection = document.getElementById('behavior');
-          behaviorSection.hidden = !experimentalFeaturesEnabled;
-        });
     this.setUpHighlightListener_();
     chrome.metricsPrivate.recordUserAction(
         'Accessibility.CrosSelectToSpeak.LoadSettings');
@@ -96,6 +89,13 @@
       voices.forEach(function(voice) {
         if (!voice.voiceName)
           return;
+        if (!voice.eventTypes.includes('start') ||
+            !voice.eventTypes.includes('end') ||
+            !voice.eventTypes.includes('word') ||
+            !voice.eventTypes.includes('cancelled')) {
+          // Required event types for Select-to-Speak.
+          return;
+        }
         var option = document.createElement('option');
         option.voiceName = voice.voiceName;
         option.innerText = option.voiceName;
diff --git a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings.grd b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings.grd
index f543b8474..f9f53964 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings.grd
+++ b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings.grd
@@ -192,12 +192,6 @@
       <message desc="Label for highest synthesized speech pitch in the Select-to-speak options dialog." name="IDS_SELECT_TO_SPEAK_OPTIONS_PITCH_HIGHEST">
         Highest
       </message>
-      <message desc="Group of options for controlling behavior" name="IDS_SELECT_TO_SPEAK_OPTIONS_BEHAVIOR">
-        Behavior
-      </message>
-      <message desc="Label for option to continue reading after text goes away, or stop when text goes away" name="IDS_SELECT_TO_SPEAK_OPTIONS_READ_AFTER_CLOSE">
-        Continue reading if text being spoken goes away
-      </message>
     </messages>
   </release>
 </grit>
diff --git a/chrome/browser/resources/settings/appearance_page/appearance_page.js b/chrome/browser/resources/settings/appearance_page/appearance_page.js
index 950dcd54..b16b270e 100644
--- a/chrome/browser/resources/settings/appearance_page/appearance_page.js
+++ b/chrome/browser/resources/settings/appearance_page/appearance_page.js
@@ -274,7 +274,7 @@
    * @private
    */
   themeChanged_: function(themeId, useSystemTheme) {
-    if (themeId) {
+    if (themeId.length > 0) {
       assert(!useSystemTheme);
 
       this.browserProxy_.getThemeInfo(themeId).then(info => {
diff --git a/chrome/browser/resources/settings/icons.html b/chrome/browser/resources/settings/icons.html
index bf9a000..d8a2c38 100644
--- a/chrome/browser/resources/settings/icons.html
+++ b/chrome/browser/resources/settings/icons.html
@@ -96,6 +96,7 @@
       <g id="notifications"><path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"></path></g>
       <g id="pdf"><path d="M7 11.5h1v-1H7v1zM19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-9.5 8.5c0 .83-.67 1.5-1.5 1.5H7v2H5.5V9H8c.83 0 1.5.67 1.5 1.5v1zm10-1H17v1h1.5V13H17v2h-1.5V9h4v1.5zm-5 3c0 .83-.67 1.5-1.5 1.5h-2.5V9H13c.83 0 1.5.67 1.5 1.5v3zm-2.5 0h1v-3h-1v3z"></path><path fill="none" d="M0 0h24v24H0z"></path></g>
       <g id="palette"><path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path></g>
+      <g id="payment-handler"><path d="M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z"></path></g>
       <g id="photo"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"></path></g>
       <g id="power-settings-new"><path d="M13 3h-2v10h2V3zm4.83 2.17l-1.42 1.42C17.99 7.86 19 9.81 19 12c0 3.87-3.13 7-7 7s-7-3.13-7-7c0-2.19 1.01-4.14 2.58-5.42L6.17 5.17C4.23 6.82 3 9.26 3 12c0 4.97 4.03 9 9 9s9-4.03 9-9c0-2.74-1.23-5.18-3.17-6.83z"></path></g>
       <g id="protocol-handler"><path d="M21.72 11.33l-6.644-7.035a.97.97 0 0 0-1.38-.01l-1.67 1.72-1.617-1.712a.97.97 0 0 0-1.38-.01l-6.737 6.935c-.187.191-.29.447-.292.719-.002.272.099.529.28.722l6.644 7.034a.949.949 0 0 0 1.38.011l1.671-1.718 1.615 1.71a.949.949 0 0 0 1.381.01l6.74-6.935a1.054 1.054 0 0 0 .01-1.44zM6.947 12.464l3.657 3.785-.974.98-5.273-5.456 5.349-5.378.929.962-3.677 3.7a.998.998 0 0 0-.292.702 1 1 0 0 0 .28.705zm7.35 4.768l-.931-.963 3.68-3.7a1.012 1.012 0 0 0 .007-1.407l-3.656-3.784.974-.98 5.273 5.456-5.348 5.378z"></path></g>
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html
index 6adaa19..a4b853a 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.html
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html
@@ -529,8 +529,7 @@
 </if>
         </settings-subpage>
       </template>
-      <template is="dom-if" if="[[enableClipboardContentSetting_]]"
-          no-search>
+      <template is="dom-if" if="[[enableClipboardContentSetting_]]">
         <template is="dom-if" route-path="/content/clipboard" no-search>
           <settings-subpage page-title="$i18n{siteSettingsClipboard}">
             <category-default-setting
@@ -545,6 +544,21 @@
           </settings-subpage>
        </template>
       </template>
+      <template is="dom-if" if="[[enablePaymentHandlerContentSetting_]]">
+        <template is="dom-if" route-path="/content/paymentHandler" no-search>
+          <settings-subpage page-title="$i18n{siteSettingsPaymentHandler}">
+            <category-default-setting
+                toggle-off-label="$i18n{siteSettingsPaymentHandlerBlock}"
+                toggle-on-label="$i18n{siteSettingsPaymentHandlerAllowRecommended}"
+                category="[[ContentSettingsTypes.PAYMENT_HANDLER]]">
+            </category-default-setting>
+            <category-setting-exceptions
+                category="[[ContentSettingsTypes.PAYMENT_HANDLER]]"
+                block-header="$i18n{siteSettingsBlocked}">
+            </category-setting-exceptions>
+          </settings-subpage>
+       </template>
+      </template>
     </settings-animated-pages>
   </template>
   <script src="privacy_page.js"></script>
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.js b/chrome/browser/resources/settings/privacy_page/privacy_page.js
index abb969d0d..c8a41ea 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.js
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.js
@@ -121,6 +121,14 @@
     },
 
     /** @private */
+    enablePaymentHandlerContentSetting_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.getBoolean('enablePaymentHandlerContentSetting');
+      }
+    },
+
+    /** @private */
     enableSensorsContentSetting_: {
       type: Boolean,
       readOnly: true,
diff --git a/chrome/browser/resources/settings/route.js b/chrome/browser/resources/settings/route.js
index 68183214..e799ba3 100644
--- a/chrome/browser/resources/settings/route.js
+++ b/chrome/browser/resources/settings/route.js
@@ -80,6 +80,7 @@
  *   SITE_SETTINGS_MICROPHONE: (undefined|!settings.Route),
  *   SITE_SETTINGS_MIDI_DEVICES: (undefined|!settings.Route),
  *   SITE_SETTINGS_NOTIFICATIONS: (undefined|!settings.Route),
+ *   SITE_SETTINGS_PAYMENT_HANDLER: (undefined|!settings.Route),
  *   SITE_SETTINGS_PDF_DOCUMENTS: (undefined|!settings.Route),
  *   SITE_SETTINGS_POPUPS: (undefined|!settings.Route),
  *   SITE_SETTINGS_PROTECTED_CONTENT: (undefined|!settings.Route),
@@ -335,6 +336,10 @@
           r.SITE_SETTINGS.createChild('pdfDocuments');
       r.SITE_SETTINGS_PROTECTED_CONTENT =
           r.SITE_SETTINGS.createChild('protectedContent');
+      if (loadTimeData.getBoolean('enablePaymentHandlerContentSetting')) {
+        r.SITE_SETTINGS_PAYMENT_HANDLER =
+            r.SITE_SETTINGS.createChild('paymentHandler');
+      }
 
       // <if expr="chromeos">
       if (pageVisibility.dateTime !== false) {
diff --git a/chrome/browser/resources/settings/site_settings/category_default_setting.js b/chrome/browser/resources/settings/site_settings/category_default_setting.js
index 9933cca..e596b8f 100644
--- a/chrome/browser/resources/settings/site_settings/category_default_setting.js
+++ b/chrome/browser/resources/settings/site_settings/category_default_setting.js
@@ -98,6 +98,7 @@
       case settings.ContentSettingsTypes.JAVASCRIPT:
       case settings.ContentSettingsTypes.SOUND:
       case settings.ContentSettingsTypes.SENSORS:
+      case settings.ContentSettingsTypes.PAYMENT_HANDLER:
       case settings.ContentSettingsTypes.POPUPS:
       case settings.ContentSettingsTypes.PROTOCOL_HANDLERS:
 
diff --git a/chrome/browser/resources/settings/site_settings/constants.js b/chrome/browser/resources/settings/site_settings/constants.js
index f878c56..15e8539 100644
--- a/chrome/browser/resources/settings/site_settings/constants.js
+++ b/chrome/browser/resources/settings/site_settings/constants.js
@@ -34,6 +34,7 @@
   ADS: 'ads',
   CLIPBOARD: 'clipboard',
   SENSORS: 'sensors',
+  PAYMENT_HANDLER: 'payment-handler',
 };
 
 /**
diff --git a/chrome/browser/resources/settings/site_settings/site_details.html b/chrome/browser/resources/settings/site_settings/site_details.html
index 455695a..b177b18 100644
--- a/chrome/browser/resources/settings/site_settings/site_details.html
+++ b/chrome/browser/resources/settings/site_settings/site_details.html
@@ -157,6 +157,12 @@
           label="$i18n{siteSettingsClipboard}"
           hidden$="[[!enableClipboardContentSetting_]]">
       </site-details-permission>
+      <site-details-permission
+          category="{{ContentSettingsTypes.PAYMENT_HANDLER}}"
+          icon="settings:payment-handler" id="paymentHandler"
+          label="$i18n{siteSettingsPaymentHandler}"
+          hidden$="[[!enablePaymentHandlerContentSetting_]]">
+      </site-details-permission>
     </div>
 
     <div id="clearAndReset" class="settings-box"
diff --git a/chrome/browser/resources/settings/site_settings/site_details.js b/chrome/browser/resources/settings/site_settings/site_details.js
index 73bd087..5e2f924 100644
--- a/chrome/browser/resources/settings/site_settings/site_details.js
+++ b/chrome/browser/resources/settings/site_settings/site_details.js
@@ -83,6 +83,14 @@
       },
     },
 
+    /** @private */
+    enablePaymentHandlerContentSetting_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.getBoolean('enablePaymentHandlerContentSetting');
+      },
+    },
+
     /**
      * The type of storage for the origin.
      * @private
diff --git a/chrome/browser/resources/settings/site_settings_page/site_settings_page.html b/chrome/browser/resources/settings/site_settings_page/site_settings_page.html
index aff9578a..60ec3f3 100644
--- a/chrome/browser/resources/settings/site_settings_page/site_settings_page.html
+++ b/chrome/browser/resources/settings/site_settings_page/site_settings_page.html
@@ -395,9 +395,9 @@
     </div>
     <template is="dom-if" if="[[enableClipboardContentSetting_]]">
       <div id="clipboard" class="settings-box two-line"
-           category$="[[ContentSettingsTypes.CLIPBOARD]]"
-           data-route="SITE_SETTINGS_CLIPBOARD" on-click="onTapNavigate_"
-           actionable>
+          category$="[[ContentSettingsTypes.CLIPBOARD]]"
+          data-route="SITE_SETTINGS_CLIPBOARD" on-click="onTapNavigate_"
+          actionable>
         <iron-icon icon="settings:clipboard"></iron-icon>
         <div class="middle">
           $i18n{siteSettingsClipboard}
@@ -414,6 +414,27 @@
         </paper-icon-button-light>
       </div>
     </template>
+    <template is="dom-if" if="[[enablePaymentHandlerContentSetting_]]">
+      <div id="paymentHandler" class="settings-box two-line"
+           category$="[[ContentSettingsTypes.PAYMENT_HANDLER]]"
+           data-route="SITE_SETTINGS_PAYMENT_HANDLER" on-click="onTapNavigate_"
+           actionable>
+        <iron-icon icon="settings:payment-handler"></iron-icon>
+        <div class="middle">
+          $i18n{siteSettingsPaymentHandler}
+          <div class="secondary" id="paymentHandlerSecondary">
+            [[defaultSettingLabel_(
+                default_.paymentHandler,
+                '$i18nPolymer{siteSettingsPaymentHandlerAllow}',
+                '$i18nPolymer{siteSettingsPaymentHandlerBlock}')]]
+          </div>
+        </div>
+        <paper-icon-button-light class="subpage-arrow">
+          <button aria-label="$i18n{siteSettingsPaymentHandler}"
+              aria-describedby="paymentHandlerSecondary"></button>
+        </paper-icon-button-light>
+      </div>
+    </template>
   </template>
   <script src="site_settings_page.js"></script>
 </dom-module>
diff --git a/chrome/browser/resources/settings/site_settings_page/site_settings_page.js b/chrome/browser/resources/settings/site_settings_page/site_settings_page.js
index 5a3b98d..fb521d2 100644
--- a/chrome/browser/resources/settings/site_settings_page/site_settings_page.js
+++ b/chrome/browser/resources/settings/site_settings_page/site_settings_page.js
@@ -76,6 +76,14 @@
       }
     },
 
+    /** @private */
+    enablePaymentHandlerContentSetting_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.getBoolean('enablePaymentHandlerContentSetting');
+      }
+    },
+
     /** @type {!Map<string, string>} */
     focusConfig: {
       type: Object,
@@ -97,25 +105,36 @@
     // element, with additional entries that correspond to subpage trigger
     // elements residing in this element's Shadow DOM.
     const R = settings.routes;
-    [[R.SITE_SETTINGS_COOKIES, 'cookies'],
-     [R.SITE_SETTINGS_LOCATION, 'location'], [R.SITE_SETTINGS_CAMERA, 'camera'],
-     [R.SITE_SETTINGS_MICROPHONE, 'microphone'],
-     [R.SITE_SETTINGS_NOTIFICATIONS, 'notifications'],
-     [R.SITE_SETTINGS_JAVASCRIPT, 'javascript'],
-     [R.SITE_SETTINGS_SOUND, 'sound'], [R.SITE_SETTINGS_FLASH, 'flash'],
-     [R.SITE_SETTINGS_IMAGES, 'images'], [R.SITE_SETTINGS_POPUPS, 'popups'],
-     [R.SITE_SETTINGS_BACKGROUND_SYNC, 'background-sync'],
-     [R.SITE_SETTINGS_AUTOMATIC_DOWNLOADS, 'automatic-downloads'],
-     [R.SITE_SETTINGS_UNSANDBOXED_PLUGINS, 'unsandboxed-plugins'],
-     [R.SITE_SETTINGS_HANDLERS, 'protocol-handlers'],
-     [R.SITE_SETTINGS_MIDI_DEVICES, 'midi-devices'],
-     [R.SITE_SETTINGS_ADS, 'ads'], [R.SITE_SETTINGS_ZOOM_LEVELS, 'zoom-levels'],
-     [R.SITE_SETTINGS_USB_DEVICES, 'usb-devices'],
-     [R.SITE_SETTINGS_PDF_DOCUMENTS, 'pdf-documents'],
-     [R.SITE_SETTINGS_PROTECTED_CONTENT, 'protected-content'],
-     [R.SITE_SETTINGS_CLIPBOARD, 'clipboard'],
-     [R.SITE_SETTINGS_SENSORS, 'sensors'],
-    ].forEach(pair => {
+    const pairs = [
+      [R.SITE_SETTINGS_COOKIES, 'cookies'],
+      [R.SITE_SETTINGS_LOCATION, 'location'],
+      [R.SITE_SETTINGS_CAMERA, 'camera'],
+      [R.SITE_SETTINGS_MICROPHONE, 'microphone'],
+      [R.SITE_SETTINGS_NOTIFICATIONS, 'notifications'],
+      [R.SITE_SETTINGS_JAVASCRIPT, 'javascript'],
+      [R.SITE_SETTINGS_SOUND, 'sound'],
+      [R.SITE_SETTINGS_FLASH, 'flash'],
+      [R.SITE_SETTINGS_IMAGES, 'images'],
+      [R.SITE_SETTINGS_POPUPS, 'popups'],
+      [R.SITE_SETTINGS_BACKGROUND_SYNC, 'background-sync'],
+      [R.SITE_SETTINGS_AUTOMATIC_DOWNLOADS, 'automatic-downloads'],
+      [R.SITE_SETTINGS_UNSANDBOXED_PLUGINS, 'unsandboxed-plugins'],
+      [R.SITE_SETTINGS_HANDLERS, 'protocol-handlers'],
+      [R.SITE_SETTINGS_MIDI_DEVICES, 'midi-devices'],
+      [R.SITE_SETTINGS_ADS, 'ads'],
+      [R.SITE_SETTINGS_ZOOM_LEVELS, 'zoom-levels'],
+      [R.SITE_SETTINGS_USB_DEVICES, 'usb-devices'],
+      [R.SITE_SETTINGS_PDF_DOCUMENTS, 'pdf-documents'],
+      [R.SITE_SETTINGS_PROTECTED_CONTENT, 'protected-content'],
+      [R.SITE_SETTINGS_CLIPBOARD, 'clipboard'],
+      [R.SITE_SETTINGS_SENSORS, 'sensors'],
+    ];
+
+    if (this.enablePaymentHandlerContentSetting_) {
+      pairs.push([R.SITE_SETTINGS_PAYMENT_HANDLER, 'payment-handler']);
+    }
+
+    pairs.forEach(pair => {
       const route = pair[0];
       const id = pair[1];
       this.focusConfig.set(route.path, '* /deep/ #' + id + ' .subpage-arrow');
diff --git a/chrome/browser/resources/vr/assets/VERSION b/chrome/browser/resources/vr/assets/VERSION
index 41288ef..269703f6 100644
--- a/chrome/browser/resources/vr/assets/VERSION
+++ b/chrome/browser/resources/vr/assets/VERSION
@@ -1,2 +1,2 @@
 MAJOR=2
-MINOR=1
\ No newline at end of file
+MINOR=2
\ No newline at end of file
diff --git a/chrome/browser/resources/vr/assets/chromium/inactive_button_click.wav b/chrome/browser/resources/vr/assets/chromium/inactive_button_click.wav
new file mode 100644
index 0000000..a6bd2c4
--- /dev/null
+++ b/chrome/browser/resources/vr/assets/chromium/inactive_button_click.wav
Binary files differ
diff --git a/chrome/browser/resources/vr/assets/google_chrome/inactive_button_click.wav.sha1 b/chrome/browser/resources/vr/assets/google_chrome/inactive_button_click.wav.sha1
new file mode 100644
index 0000000..c5bb4ae
--- /dev/null
+++ b/chrome/browser/resources/vr/assets/google_chrome/inactive_button_click.wav.sha1
@@ -0,0 +1 @@
+e75d3a135c48f42fea2272174ecdf2a562f18864
\ No newline at end of file
diff --git a/chrome/browser/resources/vr/assets/vr_assets_component_files.json b/chrome/browser/resources/vr/assets/vr_assets_component_files.json
index 5daecb2..e6dfab0f 100644
--- a/chrome/browser/resources/vr/assets/vr_assets_component_files.json
+++ b/chrome/browser/resources/vr/assets/vr_assets_component_files.json
@@ -5,5 +5,6 @@
   "google_chrome/normal_gradient.png",
   "google_chrome/button_click.wav",
   "google_chrome/button_hover.wav",
-  "google_chrome/back_button_click.wav"
+  "google_chrome/back_button_click.wav",
+  "google_chrome/inactive_button_click.wav"
 ]
\ No newline at end of file
diff --git a/chrome/browser/search/suggestions/image_fetcher_impl_browsertest.cc b/chrome/browser/search/suggestions/image_fetcher_impl_browsertest.cc
index badc4e0..4e6f88a 100644
--- a/chrome/browser/search/suggestions/image_fetcher_impl_browsertest.cc
+++ b/chrome/browser/search/suggestions/image_fetcher_impl_browsertest.cc
@@ -15,7 +15,6 @@
 #include "chrome/browser/search/suggestions/image_decoder_impl.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/in_process_browser_test.h"
-#include "components/image_fetcher/core/image_fetcher_delegate.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -24,7 +23,6 @@
 
 using image_fetcher::ImageFetcher;
 using image_fetcher::ImageFetcherImpl;
-using image_fetcher::ImageFetcherDelegate;
 
 namespace suggestions {
 
@@ -36,37 +34,15 @@
 
 const base::FilePath::CharType kDocRoot[] =
     FILE_PATH_LITERAL("chrome/test/data");
-
-class TestImageFetcherDelegate : public ImageFetcherDelegate {
- public:
-  TestImageFetcherDelegate()
-    : num_delegate_valid_called_(0),
-      num_delegate_null_called_(0) {}
-  ~TestImageFetcherDelegate() override {}
-
-  // Perform additional tasks when an image has been fetched.
-  void OnImageFetched(const std::string& id, const gfx::Image& image) override {
-    if (!image.IsEmpty()) {
-      num_delegate_valid_called_++;
-    } else {
-      num_delegate_null_called_++;
-    }
-  };
-
-  int num_delegate_valid_called() { return num_delegate_valid_called_; }
-  int num_delegate_null_called() { return num_delegate_null_called_; }
-
- private:
-  int num_delegate_valid_called_;
-  int num_delegate_null_called_;
-};
-
 }  // namespace
 
 class ImageFetcherImplBrowserTest : public InProcessBrowserTest {
  protected:
   ImageFetcherImplBrowserTest()
-      : num_callback_valid_called_(0), num_callback_null_called_(0) {
+      : num_callback_valid_called_(0),
+        num_callback_null_called_(0),
+        num_data_callback_valid_called_(0),
+        num_data_callback_null_called_(0) {
     test_server_.ServeFilesFromSourceDirectory(base::FilePath(kDocRoot));
   }
 
@@ -78,7 +54,6 @@
     ImageFetcherImpl* fetcher =
         new ImageFetcherImpl(std::make_unique<suggestions::ImageDecoderImpl>(),
                              browser()->profile()->GetRequestContext());
-    fetcher->SetImageFetcherDelegate(&delegate_);
     return fetcher;
   }
 
@@ -94,12 +69,23 @@
     loop->Quit();
   }
 
-  void StartOrQueueNetworkRequestHelper(const GURL& image_url) {
+  void OnImageDataAvailable(const std::string& image_data,
+                            const image_fetcher::RequestMetadata& metadata) {
+    if (!image_data.empty()) {
+      num_data_callback_valid_called_++;
+    } else {
+      num_data_callback_null_called_++;
+    }
+  }
+
+  void FetchImageAndDataHelper(const GURL& image_url) {
     std::unique_ptr<ImageFetcherImpl> image_fetcher_(CreateImageFetcher());
 
     base::RunLoop run_loop;
-    image_fetcher_->StartOrQueueNetworkRequest(
+    image_fetcher_->FetchImageAndData(
         kTestUrl, image_url,
+        base::BindOnce(&ImageFetcherImplBrowserTest::OnImageDataAvailable,
+                       base::Unretained(this)),
         base::Bind(&ImageFetcherImplBrowserTest::OnImageAvailable,
                    base::Unretained(this), &run_loop),
         TRAFFIC_ANNOTATION_FOR_TESTS);
@@ -109,8 +95,10 @@
   int num_callback_valid_called_;
   int num_callback_null_called_;
 
+  int num_data_callback_valid_called_;
+  int num_data_callback_null_called_;
+
   net::EmbeddedTestServer test_server_;
-  TestImageFetcherDelegate delegate_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ImageFetcherImplBrowserTest);
@@ -118,35 +106,35 @@
 
 IN_PROC_BROWSER_TEST_F(ImageFetcherImplBrowserTest, NormalFetch) {
   GURL image_url(test_server_.GetURL(kTestImagePath).spec());
-  StartOrQueueNetworkRequestHelper(image_url);
+  FetchImageAndDataHelper(image_url);
 
   EXPECT_EQ(1, num_callback_valid_called_);
   EXPECT_EQ(0, num_callback_null_called_);
-  EXPECT_EQ(1, delegate_.num_delegate_valid_called());
-  EXPECT_EQ(0, delegate_.num_delegate_null_called());
+  EXPECT_EQ(1, num_data_callback_valid_called_);
+  EXPECT_EQ(0, num_data_callback_null_called_);
 }
 
 IN_PROC_BROWSER_TEST_F(ImageFetcherImplBrowserTest, MultipleFetch) {
   GURL image_url(test_server_.GetURL(kTestImagePath).spec());
 
   for (int i = 0; i < 5; i++) {
-    StartOrQueueNetworkRequestHelper(image_url);
+    FetchImageAndDataHelper(image_url);
   }
 
   EXPECT_EQ(5, num_callback_valid_called_);
   EXPECT_EQ(0, num_callback_null_called_);
-  EXPECT_EQ(5, delegate_.num_delegate_valid_called());
-  EXPECT_EQ(0, delegate_.num_delegate_null_called());
+  EXPECT_EQ(5, num_data_callback_valid_called_);
+  EXPECT_EQ(0, num_data_callback_null_called_);
 }
 
 IN_PROC_BROWSER_TEST_F(ImageFetcherImplBrowserTest, InvalidFetch) {
   GURL invalid_image_url(test_server_.GetURL(kInvalidImagePath).spec());
-  StartOrQueueNetworkRequestHelper(invalid_image_url);
+  FetchImageAndDataHelper(invalid_image_url);
 
   EXPECT_EQ(0, num_callback_valid_called_);
   EXPECT_EQ(1, num_callback_null_called_);
-  EXPECT_EQ(0, delegate_.num_delegate_valid_called());
-  EXPECT_EQ(1, delegate_.num_delegate_null_called());
+  EXPECT_EQ(0, num_data_callback_valid_called_);
+  EXPECT_EQ(1, num_data_callback_null_called_);
 }
 
 }  // namespace suggestions
diff --git a/chrome/browser/task_manager/sampling/task_group.cc b/chrome/browser/task_manager/sampling/task_group.cc
index 3e7e18a..3c3dccd 100644
--- a/chrome/browser/task_manager/sampling/task_group.cc
+++ b/chrome/browser/task_manager/sampling/task_group.cc
@@ -28,7 +28,7 @@
 
 // A mask for the refresh types that are done in the background thread.
 const int kBackgroundRefreshTypesMask =
-    REFRESH_TYPE_CPU | REFRESH_TYPE_MEMORY_DETAILS | REFRESH_TYPE_IDLE_WAKEUPS |
+    REFRESH_TYPE_CPU | REFRESH_TYPE_SWAPPED_MEM | REFRESH_TYPE_IDLE_WAKEUPS |
 #if defined(OS_WIN)
     REFRESH_TYPE_START_TIME | REFRESH_TYPE_CPU_TIME |
 #endif  // defined(OS_WIN)
@@ -93,6 +93,7 @@
       expected_on_bg_done_flags_(kBackgroundRefreshTypesMask),
       current_on_bg_done_flags_(0),
       platform_independent_cpu_usage_(0.0),
+      swapped_mem_bytes_(-1),
       memory_footprint_(-1),
       gpu_memory_(-1),
       memory_state_(base::MemoryState::UNKNOWN),
@@ -120,7 +121,7 @@
         base::Process::Open(process_id_), blocking_pool_runner,
         base::Bind(&TaskGroup::OnCpuRefreshDone,
                    weak_ptr_factory_.GetWeakPtr()),
-        base::Bind(&TaskGroup::OnMemoryUsageRefreshDone,
+        base::Bind(&TaskGroup::OnSwappedMemRefreshDone,
                    weak_ptr_factory_.GetWeakPtr()),
         base::Bind(&TaskGroup::OnIdleWakeupsRefreshDone,
                    weak_ptr_factory_.GetWeakPtr()),
@@ -303,11 +304,11 @@
   OnBackgroundRefreshTypeFinished(REFRESH_TYPE_CPU);
 }
 
-void TaskGroup::OnMemoryUsageRefreshDone(MemoryUsageStats memory_usage) {
+void TaskGroup::OnSwappedMemRefreshDone(int64_t swapped_mem_bytes) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  memory_usage_ = memory_usage;
-  OnBackgroundRefreshTypeFinished(REFRESH_TYPE_MEMORY_DETAILS);
+  swapped_mem_bytes_ = swapped_mem_bytes;
+  OnBackgroundRefreshTypeFinished(REFRESH_TYPE_SWAPPED_MEM);
 }
 
 void TaskGroup::OnProcessPriorityDone(bool is_backgrounded) {
diff --git a/chrome/browser/task_manager/sampling/task_group.h b/chrome/browser/task_manager/sampling/task_group.h
index 0ed31d50..7778af1 100644
--- a/chrome/browser/task_manager/sampling/task_group.h
+++ b/chrome/browser/task_manager/sampling/task_group.h
@@ -78,7 +78,7 @@
   void set_footprint_bytes(int64_t footprint) { memory_footprint_ = footprint; }
   int64_t footprint_bytes() const { return memory_footprint_; }
 #if defined(OS_CHROMEOS)
-  int64_t swapped_bytes() const { return memory_usage_.swapped_bytes; }
+  int64_t swapped_bytes() const { return swapped_mem_bytes_; }
 #endif
   int64_t gpu_memory() const { return gpu_memory_; }
   bool gpu_memory_has_duplicates() const { return gpu_memory_has_duplicates_; }
@@ -124,7 +124,7 @@
 #endif  // defined(OS_LINUX)
 
   void OnCpuRefreshDone(double cpu_usage);
-  void OnMemoryUsageRefreshDone(MemoryUsageStats memory_usage);
+  void OnSwappedMemRefreshDone(int64_t swapped_mem_bytes);
   void OnProcessPriorityDone(bool is_backgrounded);
   void OnIdleWakeupsRefreshDone(int idle_wakeups_per_second);
 
@@ -158,7 +158,7 @@
   double platform_independent_cpu_usage_;
   base::Time start_time_;     // Only calculated On Windows now.
   base::TimeDelta cpu_time_;  // Only calculated On Windows now.
-  MemoryUsageStats memory_usage_;
+  int64_t swapped_mem_bytes_;
   int64_t memory_footprint_;
   int64_t gpu_memory_;
   base::MemoryState memory_state_;
diff --git a/chrome/browser/task_manager/sampling/task_group_sampler.cc b/chrome/browser/task_manager/sampling/task_group_sampler.cc
index 0fa7b1e7..d8b72bd 100644
--- a/chrome/browser/task_manager/sampling/task_group_sampler.cc
+++ b/chrome/browser/task_manager/sampling/task_group_sampler.cc
@@ -40,7 +40,7 @@
     base::Process process,
     const scoped_refptr<base::SequencedTaskRunner>& blocking_pool_runner,
     const OnCpuRefreshCallback& on_cpu_refresh,
-    const OnMemoryRefreshCallback& on_memory_refresh,
+    const OnSwappedMemRefreshCallback& on_swapped_mem_refresh,
     const OnIdleWakeupsCallback& on_idle_wakeups,
 #if defined(OS_LINUX)
     const OnOpenFdCountCallback& on_open_fd_count,
@@ -50,7 +50,7 @@
       process_metrics_(CreateProcessMetrics(process_.Handle())),
       blocking_pool_runner_(blocking_pool_runner),
       on_cpu_refresh_callback_(on_cpu_refresh),
-      on_memory_refresh_callback_(on_memory_refresh),
+      on_swapped_mem_refresh_callback_(on_swapped_mem_refresh),
       on_idle_wakeups_callback_(on_idle_wakeups),
 #if defined(OS_LINUX)
       on_open_fd_count_callback_(on_open_fd_count),
@@ -77,13 +77,12 @@
         on_cpu_refresh_callback_);
   }
 
-  if (TaskManagerObserver::IsResourceRefreshEnabled(REFRESH_TYPE_MEMORY_DETAILS,
+  if (TaskManagerObserver::IsResourceRefreshEnabled(REFRESH_TYPE_SWAPPED_MEM,
                                                     refresh_flags)) {
     base::PostTaskAndReplyWithResult(
-        blocking_pool_runner_.get(),
-        FROM_HERE,
-        base::Bind(&TaskGroupSampler::RefreshMemoryUsage, this),
-        on_memory_refresh_callback_);
+        blocking_pool_runner_.get(), FROM_HERE,
+        base::Bind(&TaskGroupSampler::RefreshSwappedMem, this),
+        on_swapped_mem_refresh_callback_);
   }
 
 #if defined(OS_MACOSX) || defined(OS_LINUX)
@@ -127,19 +126,17 @@
   return process_metrics_->GetPlatformIndependentCPUUsage();
 }
 
-MemoryUsageStats TaskGroupSampler::RefreshMemoryUsage() {
+int64_t TaskGroupSampler::RefreshSwappedMem() {
   DCHECK(worker_pool_sequenced_checker_.CalledOnValidSequence());
 
-  MemoryUsageStats memory_usage;
-
 #if defined(OS_CHROMEOS)
   base::WorkingSetKBytes ws_usage;
   if (process_metrics_->GetWorkingSetKBytes(&ws_usage)) {
-    memory_usage.swapped_bytes = ws_usage.swapped * 1024;
+    return ws_usage.swapped * 1024;
   }
 #endif  // defined(OS_CHROMEOS)
 
-  return memory_usage;
+  return 0;
 }
 
 int TaskGroupSampler::RefreshIdleWakeupsPerSecond() {
diff --git a/chrome/browser/task_manager/sampling/task_group_sampler.h b/chrome/browser/task_manager/sampling/task_group_sampler.h
index 40cfb648..c0c6241 100644
--- a/chrome/browser/task_manager/sampling/task_group_sampler.h
+++ b/chrome/browser/task_manager/sampling/task_group_sampler.h
@@ -21,16 +21,6 @@
 
 namespace task_manager {
 
-// Wraps the memory usage stats values together so that it can be sent between
-// the UI and the worker threads.
-struct MemoryUsageStats {
-#if defined(OS_CHROMEOS)
-  int64_t swapped_bytes = -1;
-#endif
-
-  MemoryUsageStats() {}
-};
-
 // Defines the expensive process' stats sampler that will calculate these
 // resources on the worker thread. Objects of this class are created by the
 // TaskGroups on the UI thread, however it will be used mainly on a blocking
@@ -40,7 +30,7 @@
   // Below are the types of callbacks that are invoked on the UI thread upon
   // completion of corresponding refresh tasks on the worker thread.
   using OnCpuRefreshCallback = base::Callback<void(double)>;
-  using OnMemoryRefreshCallback = base::Callback<void(MemoryUsageStats)>;
+  using OnSwappedMemRefreshCallback = base::Callback<void(int64_t)>;
   using OnIdleWakeupsCallback = base::Callback<void(int)>;
 #if defined(OS_LINUX)
   using OnOpenFdCountCallback = base::Callback<void(int)>;
@@ -51,7 +41,7 @@
       base::Process process,
       const scoped_refptr<base::SequencedTaskRunner>& blocking_pool_runner,
       const OnCpuRefreshCallback& on_cpu_refresh,
-      const OnMemoryRefreshCallback& on_memory_refresh,
+      const OnSwappedMemRefreshCallback& on_memory_refresh,
       const OnIdleWakeupsCallback& on_idle_wakeups,
 #if defined(OS_LINUX)
       const OnOpenFdCountCallback& on_open_fd_count,
@@ -68,7 +58,7 @@
 
   // The refresh calls that will be done on the worker thread.
   double RefreshCpuUsage();
-  MemoryUsageStats RefreshMemoryUsage();
+  int64_t RefreshSwappedMem();
   int RefreshIdleWakeupsPerSecond();
 #if defined(OS_LINUX)
   int RefreshOpenFdCount();
@@ -88,7 +78,7 @@
   // The UI-thread callbacks in TaskGroup to be called when their corresponding
   // refreshes on the worker thread are done.
   const OnCpuRefreshCallback on_cpu_refresh_callback_;
-  const OnMemoryRefreshCallback on_memory_refresh_callback_;
+  const OnSwappedMemRefreshCallback on_swapped_mem_refresh_callback_;
   const OnIdleWakeupsCallback on_idle_wakeups_callback_;
 #if defined(OS_LINUX)
   const OnOpenFdCountCallback on_open_fd_count_callback_;
diff --git a/chrome/browser/task_manager/task_manager_observer.h b/chrome/browser/task_manager/task_manager_observer.h
index 1d700f6..ad9913e3 100644
--- a/chrome/browser/task_manager/task_manager_observer.h
+++ b/chrome/browser/task_manager/task_manager_observer.h
@@ -26,8 +26,8 @@
   REFRESH_TYPE_NONE = 0,
   REFRESH_TYPE_CPU = 1,
 
-  // Memory details currently only includes "swapped memory" on CrOS.
-  REFRESH_TYPE_MEMORY_DETAILS = 1 << 2,
+  // Only available on CrOS.
+  REFRESH_TYPE_SWAPPED_MEM = 1 << 2,
   REFRESH_TYPE_GPU_MEMORY = 1 << 3,
   REFRESH_TYPE_V8_MEMORY = 1 << 4,
   REFRESH_TYPE_SQLITE_MEMORY = 1 << 5,
diff --git a/chrome/browser/themes/theme_service.cc b/chrome/browser/themes/theme_service.cc
index 95c7eeb2..b015f90 100644
--- a/chrome/browser/themes/theme_service.cc
+++ b/chrome/browser/themes/theme_service.cc
@@ -566,7 +566,10 @@
                             weak_ptr_factory_.GetWeakPtr(), true));
 }
 
+void ThemeService::FixInconsistentPreferencesIfNeeded() {}
+
 void ThemeService::LoadThemePrefs() {
+  FixInconsistentPreferencesIfNeeded();
   PrefService* prefs = profile_->GetPrefs();
 
   std::string current_id = GetThemeID();
diff --git a/chrome/browser/themes/theme_service.h b/chrome/browser/themes/theme_service.h
index 86ceda0..1e90506e 100644
--- a/chrome/browser/themes/theme_service.h
+++ b/chrome/browser/themes/theme_service.h
@@ -171,6 +171,10 @@
   virtual bool ShouldUseNativeFrame() const;
   bool HasCustomImage(int id) const;
 
+  // If there is an inconsistency in preferences, change preferences to a
+  // consistent state.
+  virtual void FixInconsistentPreferencesIfNeeded();
+
   Profile* profile() const { return profile_; }
 
   void set_ready() { ready_ = true; }
diff --git a/chrome/browser/themes/theme_service_aurax11.cc b/chrome/browser/themes/theme_service_aurax11.cc
index 00adcd14..4903e41 100644
--- a/chrome/browser/themes/theme_service_aurax11.cc
+++ b/chrome/browser/themes/theme_service_aurax11.cc
@@ -101,3 +101,14 @@
   return theme_supplier &&
          theme_supplier->get_theme_type() == CustomThemeSupplier::NATIVE_X11;
 }
+
+void ThemeServiceAuraX11::FixInconsistentPreferencesIfNeeded() {
+  PrefService* prefs = profile()->GetPrefs();
+
+  // When using the system theme, the theme ID should match the default. Give
+  // precedence to the non-default theme specified.
+  if (GetThemeID() != ThemeService::kDefaultThemeID &&
+      prefs->GetBoolean(prefs::kUsesSystemTheme)) {
+    prefs->SetBoolean(prefs::kUsesSystemTheme, false);
+  }
+}
diff --git a/chrome/browser/themes/theme_service_aurax11.h b/chrome/browser/themes/theme_service_aurax11.h
index 56564bc..0286525b 100644
--- a/chrome/browser/themes/theme_service_aurax11.h
+++ b/chrome/browser/themes/theme_service_aurax11.h
@@ -21,6 +21,7 @@
   bool IsSystemThemeDistinctFromDefaultTheme() const override;
   bool UsingDefaultTheme() const override;
   bool UsingSystemTheme() const override;
+  void FixInconsistentPreferencesIfNeeded() override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ThemeServiceAuraX11);
diff --git a/chrome/browser/themes/theme_service_unittest.cc b/chrome/browser/themes/theme_service_unittest.cc
index c640575..909ae16 100644
--- a/chrome/browser/themes/theme_service_unittest.cc
+++ b/chrome/browser/themes/theme_service_unittest.cc
@@ -400,6 +400,26 @@
   EXPECT_EQ(get_theme_supplier(theme_service)->get_theme_type(),
             CustomThemeSupplier::SUPERVISED_USER_THEME);
 }
+
+TEST_F(ThemeServiceTest, UserThemeTakesPrecedenceOverSystemTheme) {
+  ThemeService* theme_service =
+      ThemeServiceFactory::GetForProfile(profile_.get());
+
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  const std::string& extension_id = LoadUnpackedThemeAt(temp_dir.GetPath());
+  ASSERT_EQ(extension_id, theme_service->GetThemeID());
+
+  // Set preference |prefs::kUsesSystemTheme| to true which conflicts with
+  // having a user theme selected.
+  profile_->GetPrefs()->SetBoolean(prefs::kUsesSystemTheme, true);
+  EXPECT_TRUE(profile_->GetPrefs()->GetBoolean(prefs::kUsesSystemTheme));
+
+  // Initialization should fix the preference inconsistency.
+  theme_service->Init(profile_.get());
+  ASSERT_EQ(extension_id, theme_service->GetThemeID());
+  EXPECT_FALSE(profile_->GetPrefs()->GetBoolean(prefs::kUsesSystemTheme));
+}
 #endif // defined(OS_LINUX) && !defined(OS_CHROMEOS)
 #endif // BUILDFLAG(ENABLE_SUPERVISED_USERS)
 
diff --git a/chrome/browser/ui/ash/login_screen_client.cc b/chrome/browser/ui/ash/login_screen_client.cc
index ce63aa3..341bfa0 100644
--- a/chrome/browser/ui/ash/login_screen_client.cc
+++ b/chrome/browser/ui/ash/login_screen_client.cc
@@ -11,7 +11,9 @@
 #include "chrome/browser/chromeos/login/reauth_stats.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/chromeos/login/ui/user_adding_screen.h"
+#include "chrome/browser/profiles/profile_metrics.h"
 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
+#include "components/user_manager/remove_user_delegate.h"
 #include "content/public/common/service_manager_connection.h"
 #include "services/service_manager/public/cpp/connector.h"
 
@@ -117,6 +119,18 @@
   }
 }
 
+void LoginScreenClient::OnRemoveUserWarningShown() {
+  ProfileMetrics::LogProfileDeleteUser(
+      ProfileMetrics::DELETE_PROFILE_USER_MANAGER_SHOW_WARNING);
+}
+
+void LoginScreenClient::RemoveUser(const AccountId& account_id) {
+  ProfileMetrics::LogProfileDeleteUser(
+      ProfileMetrics::DELETE_PROFILE_USER_MANAGER);
+  user_manager::UserManager::Get()->RemoveUser(account_id,
+                                               nullptr /*delegate*/);
+}
+
 void LoginScreenClient::LoadWallpaper(const AccountId& account_id) {
   WallpaperControllerClient::Get()->ShowUserWallpaper(account_id);
 }
diff --git a/chrome/browser/ui/ash/login_screen_client.h b/chrome/browser/ui/ash/login_screen_client.h
index 720e923..7c90857 100644
--- a/chrome/browser/ui/ash/login_screen_client.h
+++ b/chrome/browser/ui/ash/login_screen_client.h
@@ -69,6 +69,8 @@
   void OnMaxIncorrectPasswordAttempted(const AccountId& account_id) override;
   void FocusLockScreenApps(bool reverse) override;
   void ShowGaiaSignin() override;
+  void OnRemoveUserWarningShown() override;
+  void RemoveUser(const AccountId& account_id) override;
 
   // Wrappers around the mojom::LockScreen interface.
   void ShowLockScreen(ash::mojom::LoginScreen::ShowLockScreenCallback on_shown);
diff --git a/chrome/browser/ui/cocoa/tabs/alert_indicator_button_cocoa_unittest.mm b/chrome/browser/ui/cocoa/tabs/alert_indicator_button_cocoa_unittest.mm
index b882ffa..4f9030f 100644
--- a/chrome/browser/ui/cocoa/tabs/alert_indicator_button_cocoa_unittest.mm
+++ b/chrome/browser/ui/cocoa/tabs/alert_indicator_button_cocoa_unittest.mm
@@ -32,9 +32,9 @@
 
 namespace {
 
-class AlertIndicatorButtonTest : public CocoaTest {
+class AlertIndicatorButtonTestCocoa : public CocoaTest {
  public:
-  AlertIndicatorButtonTest()
+  AlertIndicatorButtonTestCocoa()
       : scoped_task_environment_(
             base::test::ScopedTaskEnvironment::MainThreadType::UI) {
     base::CommandLine::ForCurrentProcess()->AppendSwitch(
@@ -89,6 +89,6 @@
   base::test::ScopedTaskEnvironment scoped_task_environment_;
 };
 
-TEST_VIEW(AlertIndicatorButtonTest, button_)
+TEST_VIEW(AlertIndicatorButtonTestCocoa, button_)
 
 }  // namespace
diff --git a/chrome/browser/ui/task_manager/task_manager_table_model.cc b/chrome/browser/ui/task_manager/task_manager_table_model.cc
index 1424dcf..70042bcd 100644
--- a/chrome/browser/ui/task_manager/task_manager_table_model.cc
+++ b/chrome/browser/ui/task_manager/task_manager_table_model.cc
@@ -728,7 +728,7 @@
       break;
 
     case IDS_TASK_MANAGER_SWAPPED_MEM_COLUMN:
-      type = REFRESH_TYPE_MEMORY_DETAILS;
+      type = REFRESH_TYPE_SWAPPED_MEM;
       if (table_view_delegate_->IsColumnVisible(
               IDS_TASK_MANAGER_SWAPPED_MEM_COLUMN)) {
         needs_refresh = true;
diff --git a/chrome/browser/ui/views/constrained_window_views_browsertest.cc b/chrome/browser/ui/views/constrained_window_views_browsertest.cc
index 5db03ad..ad45018 100644
--- a/chrome/browser/ui/views/constrained_window_views_browsertest.cc
+++ b/chrome/browser/ui/views/constrained_window_views_browsertest.cc
@@ -13,6 +13,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "chrome/test/views/scoped_macviews_browser_mode.h"
 #include "components/constrained_window/constrained_window_views.h"
 #include "components/web_modal/web_contents_modal_dialog_host.h"
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
@@ -52,9 +53,18 @@
   return dialog;
 }
 
-} // namespace
+class ConstrainedWindowViewTest : public InProcessBrowserTest {
+ public:
+  ConstrainedWindowViewTest() = default;
+  ~ConstrainedWindowViewTest() override = default;
 
-typedef InProcessBrowserTest ConstrainedWindowViewTest;
+ private:
+  test::ScopedMacViewsBrowserMode views_mode_{true};
+
+  DISALLOW_COPY_AND_ASSIGN(ConstrainedWindowViewTest);
+};
+
+}  // namespace
 
 // Tests the intial focus of tab-modal dialogs, the restoration of focus to the
 // browser when they close, and that queued dialogs don't register themselves as
diff --git a/chrome/browser/ui/views/device_chooser_content_view.cc b/chrome/browser/ui/views/device_chooser_content_view.cc
index 5824887..8015d62 100644
--- a/chrome/browser/ui/views/device_chooser_content_view.cc
+++ b/chrome/browser/ui/views/device_chooser_content_view.cc
@@ -51,9 +51,6 @@
       l10n_util::GetStringUTF16(IDS_BLUETOOTH_DEVICE_CHOOSER_RE_SCAN_TOOLTIP));
   re_scan_button_->SetFocusForPlatform();
   re_scan_button_->set_tag(kReScanButtonTag);
-  // Ensures that the focus will never cycle to the help button, because that's
-  // rarely useful and can look weird.
-  re_scan_button_->set_request_focus_on_press(true);
   AddChildView(re_scan_button_);
 
   throbber_ = new views::Throbber();
@@ -304,6 +301,11 @@
   if (sender->tag() == kHelpButtonTag) {
     chooser_controller_->OpenHelpCenterUrl();
   } else if (sender->tag() == kReScanButtonTag) {
+    // Refreshing will cause the table view to yield focus, which
+    // will land on the help button. Instead, briefly let the
+    // rescan button take focus. When it hides itself, focus will
+    // advance to the "Cancel" button as desired.
+    sender->RequestFocus();
     chooser_controller_->RefreshOptions();
   } else {
     NOTREACHED();
diff --git a/chrome/browser/ui/views/exclusive_access_bubble_views_interactive_uitest.cc b/chrome/browser/ui/views/exclusive_access_bubble_views_interactive_uitest.cc
index 71f3942d..cbaab4a 100644
--- a/chrome/browser/ui/views/exclusive_access_bubble_views_interactive_uitest.cc
+++ b/chrome/browser/ui/views/exclusive_access_bubble_views_interactive_uitest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/exclusive_access/fullscreen_controller_test.h"
 #include "chrome/browser/ui/views/exclusive_access_bubble_views.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/test/views/scoped_macviews_browser_mode.h"
 #include "ui/views/widget/widget.h"
 #include "ui/views/widget/widget_observer.h"
 
@@ -31,6 +32,8 @@
   bool was_observing_in_destroying_ = false;
 
  private:
+  test::ScopedMacViewsBrowserMode views_mode_{true};
+
   DISALLOW_COPY_AND_ASSIGN(ExclusiveAccessBubbleViewsTest);
 };
 
diff --git a/chrome/browser/ui/views/extensions/chooser_dialog_view_unittest.cc b/chrome/browser/ui/views/extensions/chooser_dialog_view_unittest.cc
index cc6ffe3..88f14fee 100644
--- a/chrome/browser/ui/views/extensions/chooser_dialog_view_unittest.cc
+++ b/chrome/browser/ui/views/extensions/chooser_dialog_view_unittest.cc
@@ -1,12 +1,12 @@
 // Copyright 2016 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-
 #include "chrome/browser/ui/views/extensions/chooser_dialog_view.h"
 
 #include <memory>
 
 #include "base/macros.h"
+#include "build/build_config.h"
 #include "chrome/browser/chooser_controller/fake_bluetooth_chooser_controller.h"
 #include "chrome/browser/ui/views/device_chooser_content_view.h"
 #include "chrome/test/views/chrome_views_test_base.h"
@@ -14,6 +14,7 @@
 #include "ui/events/base_event_utils.h"
 #include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/table/table_view.h"
+#include "ui/views/test/native_widget_factory.h"
 #include "ui/views/widget/widget.h"
 #include "ui/views/window/dialog_client_view.h"
 
@@ -27,8 +28,28 @@
     controller_ = controller.get();
     dialog_ = new ChooserDialogView(std::move(controller));
 
+#if defined(OS_MACOSX)
+    // We need a native view parent for the dialog to avoid a DCHECK
+    // on Mac.
+    views::Widget::InitParams params =
+        CreateParams(views::Widget::InitParams::TYPE_WINDOW);
+    params.bounds = gfx::Rect(10, 11, 200, 200);
+    params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+    params.native_widget = views::test::CreatePlatformDesktopNativeWidgetImpl(
+        params, &parent_widget_, nullptr);
+    parent_widget_.Init(params);
+
+    widget_ = views::DialogDelegate::CreateDialogWidget(
+        dialog_, GetContext(), parent_widget_.GetNativeView());
+    widget_->SetVisibilityChangedAnimationsEnabled(false);
+    // Necessary for Mac. On other platforms this happens in the focus
+    // manager, but it's disabled for Mac due to crbug.com/650859.
+    parent_widget_.Activate();
+    widget_->Activate();
+#else
     widget_ = views::DialogDelegate::CreateDialogWidget(dialog_, GetContext(),
                                                         nullptr);
+#endif
     controller_->SetBluetoothStatus(
         FakeBluetoothChooserController::BluetoothStatus::IDLE);
 
@@ -38,6 +59,9 @@
 
   void TearDown() override {
     widget_->Close();
+#if defined(OS_MACOSX)
+    parent_widget_.Close();
+#endif
     ChromeViewsTestBase::TearDown();
   }
 
@@ -62,6 +86,9 @@
   FakeBluetoothChooserController* controller_ = nullptr;
 
  private:
+#if defined(OS_MACOSX)
+  views::Widget parent_widget_;
+#endif
   views::Widget* widget_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(ChooserDialogViewTest);
diff --git a/chrome/browser/ui/views/extensions/extension_dialog_interactive_uitest.cc b/chrome/browser/ui/views/extensions/extension_dialog_interactive_uitest.cc
index 408e0e8..adb55eb 100644
--- a/chrome/browser/ui/views/extensions/extension_dialog_interactive_uitest.cc
+++ b/chrome/browser/ui/views/extensions/extension_dialog_interactive_uitest.cc
@@ -7,11 +7,25 @@
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/extensions/extension_view_host.h"
 #include "chrome/browser/ui/browser_window.h"
+#include "chrome/test/views/scoped_macviews_browser_mode.h"
 #include "content/public/test/browser_test_utils.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "ui/base/test/ui_controls.h"
 
-using ExtensionDialogUiTest = ExtensionBrowserTest;
+namespace {
+
+class ExtensionDialogUiTest : public ExtensionBrowserTest {
+ public:
+  ExtensionDialogUiTest() = default;
+  ~ExtensionDialogUiTest() override = default;
+
+ private:
+  test::ScopedMacViewsBrowserMode views_mode_{true};
+
+  DISALLOW_COPY_AND_ASSIGN(ExtensionDialogUiTest);
+};
+
+}  // namespace
 
 IN_PROC_BROWSER_TEST_F(ExtensionDialogUiTest, TabFocusLoop) {
   ExtensionTestMessageListener init_listener("ready", false /* will_reply */);
diff --git a/chrome/browser/ui/views/frame/browser_view_focus_uitest.cc b/chrome/browser/ui/views/frame/browser_view_focus_uitest.cc
index ae7bc9d..742d5d9 100644
--- a/chrome/browser/ui/views/frame/browser_view_focus_uitest.cc
+++ b/chrome/browser/ui/views/frame/browser_view_focus_uitest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/views/frame/browser_view.h"
 
+#include "base/macros.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
@@ -13,6 +14,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "chrome/test/views/scoped_macviews_browser_mode.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/views/controls/webview/webview.h"
@@ -24,9 +26,17 @@
 
 class BrowserViewFocusTest : public InProcessBrowserTest {
  public:
+  BrowserViewFocusTest() = default;
+  ~BrowserViewFocusTest() override = default;
+
   bool IsViewFocused(ViewID vid) {
     return ui_test_utils::IsViewFocused(browser(), vid);
   }
+
+ private:
+  test::ScopedMacViewsBrowserMode views_mode_{true};
+
+  DISALLOW_COPY_AND_ASSIGN(BrowserViewFocusTest);
 };
 
 IN_PROC_BROWSER_TEST_F(BrowserViewFocusTest, BrowsersRememberFocus) {
diff --git a/chrome/browser/ui/views/keyboard_access_browsertest.cc b/chrome/browser/ui/views/keyboard_access_browsertest.cc
index 94f3268..7ecd475 100644
--- a/chrome/browser/ui/views/keyboard_access_browsertest.cc
+++ b/chrome/browser/ui/views/keyboard_access_browsertest.cc
@@ -25,6 +25,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "chrome/test/views/scoped_macviews_browser_mode.h"
 #include "ui/base/test/ui_controls.h"
 #include "ui/events/event_constants.h"
 #include "ui/events/keycodes/keyboard_codes.h"
@@ -180,6 +181,9 @@
   // not display twice.
   void TestMenuKeyboardAccessAndDismiss();
 
+ private:
+  test::ScopedMacViewsBrowserMode views_mode_{true};
+
   DISALLOW_COPY_AND_ASSIGN(KeyboardAccessTest);
 };
 
diff --git a/chrome/browser/ui/views/location_bar/location_icon_view_interactive_uitest.cc b/chrome/browser/ui/views/location_bar/location_icon_view_interactive_uitest.cc
index 9d6a04d..7ad9c8e5 100644
--- a/chrome/browser/ui/views/location_bar/location_icon_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/location_bar/location_icon_view_interactive_uitest.cc
@@ -11,10 +11,20 @@
 #include "chrome/browser/ui/views/translate/translate_icon_view.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
+#include "chrome/test/views/scoped_macviews_browser_mode.h"
 
 namespace {
 
-typedef InProcessBrowserTest LocationIconViewTest;
+class LocationIconViewTest : public InProcessBrowserTest {
+ public:
+  LocationIconViewTest() = default;
+  ~LocationIconViewTest() override = default;
+
+ private:
+  test::ScopedMacViewsBrowserMode views_mode_{true};
+
+  DISALLOW_COPY_AND_ASSIGN(LocationIconViewTest);
+};
 
 // Verify that clicking the location icon a second time hides the bubble.
 IN_PROC_BROWSER_TEST_F(LocationIconViewTest, HideOnSecondClick) {
diff --git a/chrome/browser/ui/views/location_bar/selected_keyword_view_interactive_uitest.cc b/chrome/browser/ui/views/location_bar/selected_keyword_view_interactive_uitest.cc
index 55262df05..ed01ce2 100644
--- a/chrome/browser/ui/views/location_bar/selected_keyword_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/location_bar/selected_keyword_view_interactive_uitest.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/ui/views/location_bar/selected_keyword_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/test/base/interactive_test_utils.h"
+#include "chrome/test/views/scoped_macviews_browser_mode.h"
 
 namespace {
 
@@ -21,7 +22,16 @@
   }
 }
 
-using SelectedKeywordViewTest = ExtensionBrowserTest;
+class SelectedKeywordViewTest : public ExtensionBrowserTest {
+ public:
+  SelectedKeywordViewTest() = default;
+  ~SelectedKeywordViewTest() override = default;
+
+ private:
+  test::ScopedMacViewsBrowserMode views_mode_{true};
+
+  DISALLOW_COPY_AND_ASSIGN(SelectedKeywordViewTest);
+};
 
 // Tests that an extension's short name is registered as the value of the
 // extension's omnibox keyword. When the extension's omnibox keyword is
diff --git a/chrome/browser/ui/views/location_bar/zoom_bubble_view.cc b/chrome/browser/ui/views/location_bar/zoom_bubble_view.cc
index 91d1eef..6f27b6a 100644
--- a/chrome/browser/ui/views/location_bar/zoom_bubble_view.cc
+++ b/chrome/browser/ui/views/location_bar/zoom_bubble_view.cc
@@ -10,6 +10,7 @@
 #include "base/i18n/number_formatting.h"
 #include "base/i18n/rtl.h"
 #include "base/strings/stringprintf.h"
+#include "build/build_config.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/platform_util.h"
@@ -22,6 +23,7 @@
 #include "chrome/browser/ui/views/harmony/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
 #include "chrome/browser/ui/views/location_bar/zoom_view.h"
+#include "chrome/browser/ui/views_mode_controller.h"
 #include "chrome/common/extensions/api/extension_action/action_info.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/theme_resources.h"
@@ -112,6 +114,86 @@
   DISALLOW_COPY_AND_ASSIGN(ZoomValue);
 };
 
+views::View* GetAnchorViewForBrowser(Browser* browser, bool is_fullscreen) {
+#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
+#if BUILDFLAG(MAC_VIEWS_BROWSER)
+  if (views_mode_controller::IsViewsBrowserCocoa())
+    return nullptr;  // Cocoa browsers always use anchor rects instead of views.
+#endif
+  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
+  if (!is_fullscreen ||
+      browser_view->immersive_mode_controller()->IsRevealed()) {
+    LocationBarView* location_bar = browser_view->GetLocationBarView();
+    return ui::MaterialDesignController::IsSecondaryUiMaterial()
+               ? static_cast<views::View*>(location_bar)
+               : static_cast<views::View*>(location_bar->zoom_view());
+  }
+  return nullptr;
+#else  // OS_MACOSX && !MAC_VIEWS_BROWSER
+  return nullptr;
+#endif
+}
+
+ImmersiveModeController* GetImmersiveModeControllerForBrowser(
+    Browser* browser) {
+#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
+#if BUILDFLAG(MAC_VIEWS_BROWSER)
+  if (views_mode_controller::IsViewsBrowserCocoa())
+    return nullptr;
+#endif
+  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
+  return browser_view->immersive_mode_controller();
+#else
+  return nullptr;
+#endif
+}
+
+#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
+void ParentToViewsBrowser(Browser* browser,
+                          ZoomBubbleView* zoom_bubble,
+                          views::View* anchor_view,
+                          content::WebContents* web_contents) {
+  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
+  // If we do not have an anchor view, parent the bubble to the content area.
+  if (!anchor_view)
+    zoom_bubble->set_parent_window(web_contents->GetNativeView());
+
+  views::Widget* zoom_bubble_widget =
+      views::BubbleDialogDelegateView::CreateBubble(zoom_bubble);
+  if (zoom_bubble_widget && anchor_view) {
+    browser_view->GetLocationBarView()->zoom_view()->OnBubbleWidgetCreated(
+        zoom_bubble_widget);
+  }
+}
+#endif
+
+#if defined(OS_MACOSX)
+void ParentToCocoaBrowser(Browser* browser, ZoomBubbleView* zoom_bubble) {
+  gfx::NativeView parent =
+      platform_util::GetViewForWindow(browser->window()->GetNativeWindow());
+  DCHECK(parent);
+  zoom_bubble->set_arrow(views::BubbleBorder::TOP_RIGHT);
+  zoom_bubble->set_parent_window(parent);
+  views::BubbleDialogDelegateView::CreateBubble(zoom_bubble);
+}
+#endif
+
+void ParentToBrowser(Browser* browser,
+                     ZoomBubbleView* zoom_bubble,
+                     views::View* anchor_view,
+                     content::WebContents* web_contents) {
+#if defined(OS_MACOSX) && BUILDFLAG(MAC_VIEWS_BROWSER)
+  if (views_mode_controller::IsViewsBrowserCocoa())
+    ParentToCocoaBrowser(browser, zoom_bubble);
+  else
+    ParentToViewsBrowser(browser, zoom_bubble, anchor_view, web_contents);
+#elif defined(OS_MACOSX)
+  ParentToCocoaBrowser(browser, zoom_bubble);
+#else
+  ParentToViewsBrowser(browser, zoom_bubble, anchor_view, web_contents);
+#endif
+}
+
 }  // namespace
 
 // static
@@ -129,20 +211,10 @@
   DCHECK(browser->window() &&
          browser->exclusive_access_manager()->fullscreen_controller());
 
-  views::View* anchor_view = nullptr;
-  ImmersiveModeController* immersive_mode_controller = nullptr;
   bool is_fullscreen = browser->window()->IsFullscreen();
-#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
-  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
-  if (!is_fullscreen ||
-      browser_view->immersive_mode_controller()->IsRevealed()) {
-    if (ui::MaterialDesignController::IsSecondaryUiMaterial())
-      anchor_view = browser_view->GetLocationBarView();
-    else
-      anchor_view = browser_view->GetLocationBarView()->zoom_view();
-  }
-  immersive_mode_controller = browser_view->immersive_mode_controller();
-#endif
+  views::View* anchor_view = GetAnchorViewForBrowser(browser, is_fullscreen);
+  ImmersiveModeController* immersive_mode_controller =
+      GetImmersiveModeControllerForBrowser(browser);
 
   // Find the extension that initiated the zoom change, if any.
   zoom::ZoomController* zoom_controller =
@@ -173,25 +245,7 @@
             ->extension());
   }
 
-#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
-  // If we do not have an anchor view, parent the bubble to the content area.
-  if (!anchor_view)
-    zoom_bubble_->set_parent_window(web_contents->GetNativeView());
-
-  views::Widget* zoom_bubble_widget =
-      views::BubbleDialogDelegateView::CreateBubble(zoom_bubble_);
-  if (zoom_bubble_widget && anchor_view) {
-    browser_view->GetLocationBarView()->zoom_view()->OnBubbleWidgetCreated(
-        zoom_bubble_widget);
-  }
-#else
-  gfx::NativeView parent =
-      platform_util::GetViewForWindow(browser->window()->GetNativeWindow());
-  DCHECK(parent);
-  zoom_bubble_->set_arrow(views::BubbleBorder::TOP_RIGHT);
-  zoom_bubble_->set_parent_window(parent);
-  views::BubbleDialogDelegateView::CreateBubble(zoom_bubble_);
-#endif
+  ParentToBrowser(browser, zoom_bubble_, anchor_view, web_contents);
 
   // Adjust for fullscreen after creation as it relies on the content size.
   if (is_fullscreen)
diff --git a/chrome/browser/ui/views/location_bar/zoom_bubble_view_browsertest.cc b/chrome/browser/ui/views/location_bar/zoom_bubble_view_browsertest.cc
index 4fe32ec..9bdfc4a 100644
--- a/chrome/browser/ui/views/location_bar/zoom_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/location_bar/zoom_bubble_view_browsertest.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
 #include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/views/scoped_macviews_browser_mode.h"
 #include "ui/base/ui_features.h"
 #include "ui/views/test/test_widget_observer.h"
 
@@ -40,6 +41,11 @@
 
 }  // namespace
 
+class ZoomBubbleViewsBrowserTest : public ZoomBubbleBrowserTest {
+ private:
+  test::ScopedMacViewsBrowserMode views_mode_{true};
+};
+
 // TODO(linux_aura) http://crbug.com/163931
 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA)
 #define MAYBE_NonImmersiveFullscreen DISABLED_NonImmersiveFullscreen
@@ -49,7 +55,8 @@
 #if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
 // Test whether the zoom bubble is anchored and whether it is visible when in
 // non-immersive fullscreen.
-IN_PROC_BROWSER_TEST_F(ZoomBubbleBrowserTest, MAYBE_NonImmersiveFullscreen) {
+IN_PROC_BROWSER_TEST_F(ZoomBubbleViewsBrowserTest,
+                       MAYBE_NonImmersiveFullscreen) {
   BrowserView* browser_view = static_cast<BrowserView*>(browser()->window());
   content::WebContents* web_contents = browser_view->GetActiveWebContents();
 
diff --git a/chrome/browser/ui/views/passwords/manage_passwords_icon_view_interactive_uitest.cc b/chrome/browser/ui/views/passwords/manage_passwords_icon_view_interactive_uitest.cc
index 707d1b4..39f5199 100644
--- a/chrome/browser/ui/views/passwords/manage_passwords_icon_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/passwords/manage_passwords_icon_view_interactive_uitest.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/ui/views/passwords/manage_passwords_icon_views.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/grit/generated_resources.h"
+#include "chrome/test/views/scoped_macviews_browser_mode.h"
 #include "components/password_manager/core/common/password_manager_ui.h"
 #include "content/public/test/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -42,6 +43,8 @@
   }
 
  private:
+  test::ScopedMacViewsBrowserMode views_mode_{true};
+
   DISALLOW_COPY_AND_ASSIGN(ManagePasswordsIconViewTest);
 };
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.h b/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.h
index 877b0ae..37c807b 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.h
@@ -34,7 +34,7 @@
   void Show();
 
   const views::Label* item_list() const { return item_list_; }
-  const views::ImageButton* learn_more_button() const { return image_button_; }
+  views::ImageButton* learn_more_button() const { return image_button_; }
 
  private:
   friend class ToolbarActionsBarBubbleViewsTest;
diff --git a/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views_unittest.cc b/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views_unittest.cc
index 23d031ad..837b3e2 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views_unittest.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views_unittest.cc
@@ -16,7 +16,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/events/event_utils.h"
-#include "ui/events/test/event_generator.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_unittest_util.h"
@@ -86,12 +85,14 @@
     bubble_widget_ = nullptr;
   }
 
-  void ClickView(const views::View* view) {
-    ASSERT_TRUE(view);
-    ui::test::EventGenerator generator(GetContext(),
-                                       anchor_widget_->GetNativeWindow());
-    generator.MoveMouseTo(view->GetBoundsInScreen().CenterPoint());
-    generator.ClickLeftButton();
+  void ClickButton(views::Button* button) {
+    ASSERT_TRUE(button);
+    const gfx::Point point(10, 10);
+    const ui::MouseEvent event(ui::ET_MOUSE_PRESSED, point, point,
+                               ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
+                               ui::EF_LEFT_MOUSE_BUTTON);
+    button->OnMousePressed(event);
+    button->OnMouseReleased(event);
     base::RunLoop().RunUntilIdle();
   }
 
@@ -250,7 +251,8 @@
   views::test::TestWidgetObserver bubble_observer(bubble_widget());
 
   EXPECT_FALSE(delegate.close_action());
-  ClickView(bubble()->GetDialogClientView()->ok_button());
+
+  ClickButton(bubble()->GetDialogClientView()->ok_button());
   ASSERT_TRUE(delegate.close_action());
   EXPECT_EQ(ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE,
             *delegate.close_action());
@@ -266,7 +268,8 @@
   views::test::TestWidgetObserver bubble_observer(bubble_widget());
 
   EXPECT_FALSE(delegate.close_action());
-  ClickView(bubble()->GetDialogClientView()->cancel_button());
+
+  ClickButton(bubble()->GetDialogClientView()->cancel_button());
   ASSERT_TRUE(delegate.close_action());
   EXPECT_EQ(ToolbarActionsBarBubbleDelegate::CLOSE_DISMISS_USER_ACTION,
             *delegate.close_action());
@@ -281,7 +284,8 @@
   views::test::TestWidgetObserver bubble_observer(bubble_widget());
 
   EXPECT_FALSE(delegate.close_action());
-  ClickView(bubble()->learn_more_button());
+
+  ClickButton(bubble()->learn_more_button());
   ASSERT_TRUE(delegate.close_action());
   EXPECT_EQ(ToolbarActionsBarBubbleDelegate::CLOSE_LEARN_MORE,
             *delegate.close_action());
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc b/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc
index 42b1a01..6e5485b 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc
@@ -28,6 +28,7 @@
 #include "chrome/browser/ui/views/toolbar/toolbar_button.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
+#include "chrome/test/views/scoped_macviews_browser_mode.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/browser/bookmark_utils.h"
 #include "ui/views/focus/focus_manager.h"
@@ -74,6 +75,8 @@
   void SetUpOnMainThread() override;
   void TearDownOnMainThread() override;
 
+  test::ScopedMacViewsBrowserMode views_mode_{true};
+
   ToolbarView* toolbar_view_;
 
   BrowserActionsContainer* browser_actions_;
@@ -189,6 +192,8 @@
   void RunToolbarCycleFocusTest(Browser* browser);
 
  private:
+  test::ScopedMacViewsBrowserMode views_mode_{true};
+
   DISALLOW_COPY_AND_ASSIGN(ToolbarViewTest);
 };
 
diff --git a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
index e35e276d..f5036ca 100644
--- a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
@@ -33,6 +33,7 @@
 #include "components/strings/grit/components_strings.h"
 #include "components/subresource_filter/core/browser/subresource_filter_features.h"
 #include "content/public/browser/web_ui_data_source.h"
+#include "content/public/common/content_features.h"
 #include "services/device/public/cpp/device_features.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -2187,6 +2188,13 @@
     {"siteSettingsAdsBlock", IDS_SETTINGS_SITE_SETTINGS_ADS_BLOCK},
     {"siteSettingsAdsBlockRecommended",
      IDS_SETTINGS_SITE_SETTINGS_ADS_BLOCK_RECOMMENDED},
+    {"siteSettingsPaymentHandler", IDS_SETTINGS_SITE_SETTINGS_PAYMENT_HANDLER},
+    {"siteSettingsPaymentHandlerAllow",
+     IDS_SETTINGS_SITE_SETTINGS_PAYMENT_HANDLER_ALLOW},
+    {"siteSettingsPaymentHandlerAllowRecommended",
+     IDS_SETTINGS_SITE_SETTINGS_PAYMENT_HANDLER_ALLOW_RECOMMENDED},
+    {"siteSettingsPaymentHandlerBlock",
+     IDS_SETTINGS_SITE_SETTINGS_PAYMENT_HANDLER_BLOCK},
   };
   AddLocalizedStringsBulk(html_source, localized_strings,
                           arraysize(localized_strings));
@@ -2211,6 +2219,10 @@
       "enableSensorsContentSetting",
       base::FeatureList::IsEnabled(features::kGenericSensorExtraClasses));
 
+  html_source->AddBoolean(
+      "enablePaymentHandlerContentSetting",
+      base::FeatureList::IsEnabled(features::kServiceWorkerPaymentApps));
+
   if (PluginUtils::ShouldPreferHtmlOverPlugins(
           HostContentSettingsMapFactory::GetForProfile(profile))) {
     LocalizedString flash_strings[] = {
diff --git a/chrome/browser/vr/elements/full_screen_rect.cc b/chrome/browser/vr/elements/full_screen_rect.cc
index 1255b22e..0b0130f 100644
--- a/chrome/browser/vr/elements/full_screen_rect.cc
+++ b/chrome/browser/vr/elements/full_screen_rect.cc
@@ -16,7 +16,7 @@
                             const CameraModel& model) const {
   gfx::Transform m;
   m.Scale3d(2.0f, 2.0f, 1.0f);
-  renderer->DrawGradientQuad(m, edge_color(), center_color(), gfx::PointF(),
+  renderer->DrawGradientQuad(m, edge_color(), center_color(),
                              computed_opacity(), size(), corner_radii());
 }
 
diff --git a/chrome/browser/vr/elements/rect.cc b/chrome/browser/vr/elements/rect.cc
index 911a672..2f941e75 100644
--- a/chrome/browser/vr/elements/rect.cc
+++ b/chrome/browser/vr/elements/rect.cc
@@ -42,8 +42,25 @@
 
 void Rect::Render(UiElementRenderer* renderer, const CameraModel& model) const {
   renderer->DrawGradientQuad(model.view_proj_matrix * world_space_transform(),
-                             edge_color_, center_color_, center_point_,
-                             computed_opacity(), size(), corner_radii());
+                             edge_color_, center_color_,
+                             computed_opacity() * local_opacity_, size(),
+                             corner_radii());
+}
+
+void Rect::SetLocalOpacity(float opacity) {
+  animation().TransitionFloatTo(last_frame_time(), LOCAL_OPACITY,
+                                local_opacity_, opacity);
+}
+
+void Rect::NotifyClientFloatAnimated(float value,
+                                     int target_property_id,
+                                     cc::KeyframeModel* keyframe_model) {
+  if (target_property_id == LOCAL_OPACITY) {
+    local_opacity_ = value;
+  } else {
+    UiElement::NotifyClientFloatAnimated(value, target_property_id,
+                                         keyframe_model);
+  }
 }
 
 }  // namespace vr
diff --git a/chrome/browser/vr/elements/rect.h b/chrome/browser/vr/elements/rect.h
index cac666b..fd0426e5 100644
--- a/chrome/browser/vr/elements/rect.h
+++ b/chrome/browser/vr/elements/rect.h
@@ -38,18 +38,18 @@
   void Render(UiElementRenderer* renderer,
               const CameraModel& model) const override;
 
-  void set_center_point(const gfx::PointF& center_point) {
-    center_point_ = center_point;
-  }
+  void SetLocalOpacity(float opacity);
+
+  void NotifyClientFloatAnimated(float value,
+                                 int target_property_id,
+                                 cc::KeyframeModel* keyframe_model) override;
 
  private:
   SkColor center_color_ = SK_ColorWHITE;
   SkColor edge_color_ = SK_ColorWHITE;
 
-  // This is the center point of the gradient, in aspect-corrected, local
-  // coordinates. That is, {0, 0} is always the center of the quad, and the
-  // longer extent always varies between -0.5 and 0.5.
-  gfx::PointF center_point_;
+  // This value is not inherited by descendants.
+  float local_opacity_ = 1.0f;
 
   DISALLOW_COPY_AND_ASSIGN(Rect);
 };
diff --git a/chrome/browser/vr/elements/resizer.cc b/chrome/browser/vr/elements/resizer.cc
index 0e6498f..de727a0 100644
--- a/chrome/browser/vr/elements/resizer.cc
+++ b/chrome/browser/vr/elements/resizer.cc
@@ -77,4 +77,13 @@
   return false;
 }
 
+#ifndef NDEBUG
+void Resizer::DumpGeometry(std::ostringstream* os) const {
+  gfx::Transform t = LocalTransform();
+  gfx::Vector3dF right = {1, 0, 0};
+  t.TransformVector(&right);
+  *os << "s(" << right.x() << ") ";
+}
+#endif
+
 }  // namespace vr
diff --git a/chrome/browser/vr/elements/resizer.h b/chrome/browser/vr/elements/resizer.h
index f79427cb1..67c82ab6 100644
--- a/chrome/browser/vr/elements/resizer.h
+++ b/chrome/browser/vr/elements/resizer.h
@@ -29,6 +29,10 @@
   void SetEnabled(bool enabled);
   void Reset();
 
+#ifndef NDEBUG
+  void DumpGeometry(std::ostringstream* os) const override;
+#endif
+
  private:
   gfx::Transform LocalTransform() const override;
   gfx::Transform GetTargetLocalTransform() const override;
diff --git a/chrome/browser/vr/elements/shadow.cc b/chrome/browser/vr/elements/shadow.cc
index 6f0d7bda..29a2f943 100644
--- a/chrome/browser/vr/elements/shadow.cc
+++ b/chrome/browser/vr/elements/shadow.cc
@@ -140,7 +140,7 @@
 void Shadow::LayOutChildren() {
   DCHECK(!children().empty());
   gfx::Point3F p;
-  children().front()->LocalTransform().TransformPoint(&p);
+  children().back()->LocalTransform().TransformPoint(&p);
   DCHECK_GE(kMaximumChildDepth, p.z());
   depth_ = base::ClampToRange(p.z() / kMaximumChildDepth, 0.0f, 1.0f);
   // This is an arbitrary function that quickly accelerates from 0 toward 1.
@@ -149,7 +149,7 @@
               gfx::Tween::FloatValueBetween(depth_, kYMinShadowGradientFactor,
                                             kYMaxShadowGradientFactor));
   if (children().size() == 1u)
-    set_corner_radius(children().front()->corner_radii().MaxRadius());
+    set_corner_radius(children().back()->corner_radii().MaxRadius());
 }
 
 Shadow::Renderer::Renderer()
diff --git a/chrome/browser/vr/elements/ui_element.cc b/chrome/browser/vr/elements/ui_element.cc
index 8f4e597e..56f71f9 100644
--- a/chrome/browser/vr/elements/ui_element.cc
+++ b/chrome/browser/vr/elements/ui_element.cc
@@ -256,14 +256,6 @@
 
 void UiElement::OnSetSize(const gfx::SizeF& size) {}
 
-gfx::SizeF UiElement::ContributedSize() const {
-  gfx::RectF bounds(size());
-  if (!bounds_contain_padding_) {
-    bounds.Inset(left_padding_, bottom_padding_, right_padding_, top_padding_);
-  }
-  return bounds.size();
-}
-
 void UiElement::SetVisible(bool visible) {
   SetOpacity(visible ? opacity_when_visible_ : 0.0);
 }
@@ -702,7 +694,13 @@
   bool requires_relayout = false;
   gfx::RectF bounds;
   for (auto& child : children_) {
-    gfx::SizeF size = child->ContributedSize();
+    gfx::RectF outer_bounds(child->size());
+    gfx::RectF inner_bounds(child->size());
+    if (!child->bounds_contain_padding_) {
+      inner_bounds.Inset(child->left_padding_, child->bottom_padding_,
+                         child->right_padding_, child->top_padding_);
+    }
+    gfx::SizeF size = inner_bounds.size();
     if (child->x_anchoring() != NONE || child->y_anchoring() != NONE) {
       DCHECK(!child->contributes_to_parent_bounds());
       requires_relayout = true;
@@ -711,7 +709,9 @@
         !child->contributes_to_parent_bounds()) {
       continue;
     }
-    gfx::Point3F child_center(child->local_origin());
+    gfx::Vector2dF delta =
+        outer_bounds.CenterPoint() - inner_bounds.CenterPoint();
+    gfx::Point3F child_center(child->local_origin() - delta);
     gfx::Vector3dF corner_offset(size.width(), size.height(), 0);
     corner_offset.Scale(-0.5);
     gfx::Point3F child_upper_left = child_center + corner_offset;
diff --git a/chrome/browser/vr/elements/ui_element.h b/chrome/browser/vr/elements/ui_element.h
index df491db..c47278b 100644
--- a/chrome/browser/vr/elements/ui_element.h
+++ b/chrome/browser/vr/elements/ui_element.h
@@ -71,7 +71,7 @@
 struct HitTestRequest {
   gfx::Point3F ray_origin;
   gfx::Point3F ray_target;
-  float max_distance_to_plane;
+  float max_distance_to_plane = 1000.f;
 };
 
 // The result of performing a hit test.
@@ -226,12 +226,6 @@
   void SetSize(float width, float hight);
   virtual void OnSetSize(const gfx::SizeF& size);
 
-  // Elements may report a different size to parents that resize to contain
-  // their children. Eg, for shadows.
-  // TODO(crbug.com/820507): change this to LayoutSize and update all layout
-  // code to make use of this instead of size().
-  gfx::SizeF ContributedSize() const;
-
   gfx::PointF local_origin() const { return local_origin_; }
 
   // These are convenience functions for setting the transform operations. They
diff --git a/chrome/browser/vr/elements/ui_element_name.cc b/chrome/browser/vr/elements/ui_element_name.cc
index 0010ac9..9bba5cd 100644
--- a/chrome/browser/vr/elements/ui_element_name.cc
+++ b/chrome/browser/vr/elements/ui_element_name.cc
@@ -25,9 +25,10 @@
     "kWebVrRoot",
     "kWebVrViewportAwareRoot",
     "kContentResizer",
+    "kContentFrame",
+    "kContentFrameHitPlane",
     "kContentQuad",
     "kContentQuadShadow",
-    "kContentQuadRepositionButton",
     "kControllerRoot",
     "kControllerGroup",
     "kLaser",
@@ -54,6 +55,11 @@
     "kUrlBarOriginRegion",
     "kUrlBarOriginContent",
     "kUrlBarHintText",
+    "kUrlBarOverflowButton",
+    "kUrlBarOverflowButtonIcon",
+    "kOverflowMenuBackplane",
+    "kOverflowMenu",
+    "kOverflowMenuLayout",
     "kOmniboxVisibiltyControlForVoice",
     "kOmniboxVisibilityControlForAudioPermissionPrompt",
     "kOmniboxDmmRoot",
diff --git a/chrome/browser/vr/elements/ui_element_name.h b/chrome/browser/vr/elements/ui_element_name.h
index e3a4b41..14ad852 100644
--- a/chrome/browser/vr/elements/ui_element_name.h
+++ b/chrome/browser/vr/elements/ui_element_name.h
@@ -24,9 +24,10 @@
   kWebVrRoot,
   kWebVrViewportAwareRoot,
   kContentResizer,
+  kContentFrame,
+  kContentFrameHitPlane,
   kContentQuad,
   kContentQuadShadow,
-  kContentQuadRepositionButton,
   kControllerRoot,
   kControllerGroup,
   kLaser,
@@ -53,6 +54,11 @@
   kUrlBarOriginRegion,
   kUrlBarOriginContent,
   kUrlBarHintText,
+  kUrlBarOverflowButton,
+  kUrlBarOverflowButtonIcon,
+  kOverflowMenuBackplane,
+  kOverflowMenu,
+  kOverflowMenuLayout,
   kOmniboxVisibiltyControlForVoice,
   kOmniboxVisibilityControlForAudioPermissionPrompt,
   kOmniboxDmmRoot,
diff --git a/chrome/browser/vr/elements/ui_element_type.cc b/chrome/browser/vr/elements/ui_element_type.cc
index e82595c1..353f4775 100644
--- a/chrome/browser/vr/elements/ui_element_type.cc
+++ b/chrome/browser/vr/elements/ui_element_type.cc
@@ -38,6 +38,8 @@
     "kTypeSnackbarDescription",
     "kTypeCursorBackground",
     "kTypeCursorForeground",
+    "kTypeOverflowMenuButton",
+    "kTypeOverflowMenuItem",
 };
 
 static_assert(
diff --git a/chrome/browser/vr/elements/ui_element_type.h b/chrome/browser/vr/elements/ui_element_type.h
index 7c69daf..12aa336 100644
--- a/chrome/browser/vr/elements/ui_element_type.h
+++ b/chrome/browser/vr/elements/ui_element_type.h
@@ -38,6 +38,8 @@
   kTypeSnackbarDescription,
   kTypeCursorBackground,
   kTypeCursorForeground,
+  kTypeOverflowMenuButton,
+  kTypeOverflowMenuItem,
 
   // This must be last.
   kNumUiElementTypes,
diff --git a/chrome/browser/vr/elements/ui_element_unittest.cc b/chrome/browser/vr/elements/ui_element_unittest.cc
index 4930e196..cbcecb20 100644
--- a/chrome/browser/vr/elements/ui_element_unittest.cc
+++ b/chrome/browser/vr/elements/ui_element_unittest.cc
@@ -89,6 +89,40 @@
   EXPECT_FLOAT_EQ(-3.9, p.y());
 }
 
+TEST(UiElement, IgnoringAsymmetricPadding) {
+  // This test ensures that when we ignore asymmetric padding that we don't
+  // accidentally shift the location of the parent; it should stay put.
+  auto a = std::make_unique<UiElement>();
+  a->set_bounds_contain_children(true);
+
+  auto b = std::make_unique<UiElement>();
+  b->set_bounds_contain_children(true);
+  b->set_bounds_contain_padding(false);
+  b->set_padding(0.0f, 5.0f, 0.0f, 0.0f);
+
+  auto c = std::make_unique<UiElement>();
+  c->set_bounds_contain_children(true);
+  c->set_bounds_contain_padding(false);
+  c->set_padding(0.0f, 2.0f, 0.0f, 0.0f);
+
+  auto d = std::make_unique<UiElement>();
+  d->SetSize(0.5f, 0.5f);
+
+  c->AddChild(std::move(d));
+  c->DoLayOutChildren();
+  b->AddChild(std::move(c));
+  b->DoLayOutChildren();
+  a->AddChild(std::move(b));
+  a->DoLayOutChildren();
+
+  a->UpdateWorldSpaceTransformRecursive();
+
+  gfx::Point3F p;
+  a->world_space_transform().TransformPoint(&p);
+
+  EXPECT_VECTOR3DF_EQ(gfx::Point3F(), p);
+}
+
 TEST(UiElement, BoundsContainScaledChildren) {
   auto a = std::make_unique<UiElement>();
   a->SetSize(0.4, 0.3);
diff --git a/chrome/browser/vr/elements/url_bar_texture.cc b/chrome/browser/vr/elements/url_bar_texture.cc
index 9219913..7ca9e1b 100644
--- a/chrome/browser/vr/elements/url_bar_texture.cc
+++ b/chrome/browser/vr/elements/url_bar_texture.cc
@@ -155,14 +155,15 @@
 
   // Site security state icon.
   if (state_.vector_icon != nullptr) {
-    gfx::RectF icon_region(left_edge, kHeight / 2 - kUrlBarIconSizeDMM / 2,
-                           kUrlBarIconSizeDMM, kUrlBarIconSizeDMM);
+    gfx::RectF icon_region(left_edge,
+                           kHeight / 2 - kUrlBarButtonIconSizeDMM / 2,
+                           kUrlBarButtonIconSizeDMM, kUrlBarButtonIconSizeDMM);
     VectorIcon::DrawVectorIcon(
-        &gfx_canvas, *state_.vector_icon, ToPixels(kUrlBarIconSizeDMM),
+        &gfx_canvas, *state_.vector_icon, ToPixels(kUrlBarButtonIconSizeDMM),
         {ToPixels(icon_region.x()), ToPixels(icon_region.y())},
         GetIconColor(state_.security_level, colors_));
     security_hit_region_ = icon_region;
-    left_edge += kUrlBarIconSizeDMM;
+    left_edge += kUrlBarButtonIconSizeDMM;
   }
 
   std::unique_ptr<gfx::RenderText> render_text;
diff --git a/chrome/browser/vr/model/color_scheme.cc b/chrome/browser/vr/model/color_scheme.cc
index 6a1226d..12f1943 100644
--- a/chrome/browser/vr/model/color_scheme.cc
+++ b/chrome/browser/vr/model/color_scheme.cc
@@ -152,6 +152,8 @@
   normal_scheme.incognito_factor = 0.0f;
   normal_scheme.fullscreen_factor = 0.0f;
 
+  normal_scheme.content_reposition_frame = 0x66FFFFFF;
+
   normal_scheme.cursor_background_center = 0x24000000;
   normal_scheme.cursor_background_edge = SK_ColorTRANSPARENT;
   normal_scheme.cursor_foreground = SK_ColorWHITE;
diff --git a/chrome/browser/vr/model/color_scheme.h b/chrome/browser/vr/model/color_scheme.h
index 3fde9c2d..816ea62 100644
--- a/chrome/browser/vr/model/color_scheme.h
+++ b/chrome/browser/vr/model/color_scheme.h
@@ -137,6 +137,8 @@
   SkColor reposition_label;
   SkColor reposition_label_background;
 
+  SkColor content_reposition_frame;
+
   SkColor cursor_background_center;
   SkColor cursor_background_edge;
   SkColor cursor_foreground;
diff --git a/chrome/browser/vr/model/model.h b/chrome/browser/vr/model/model.h
index 45733d6..96d7319 100644
--- a/chrome/browser/vr/model/model.h
+++ b/chrome/browser/vr/model/model.h
@@ -49,6 +49,7 @@
   bool background_loaded = false;
   bool supports_selection = true;
   bool needs_keyboard_update = false;
+  bool overflow_menu_enabled = false;
 
   // WebVR state.
   WebVrModel web_vr;
diff --git a/chrome/browser/vr/renderers/gradient_quad_renderer.cc b/chrome/browser/vr/renderers/gradient_quad_renderer.cc
index d35a027..9f25480 100644
--- a/chrome/browser/vr/renderers/gradient_quad_renderer.cc
+++ b/chrome/browser/vr/renderers/gradient_quad_renderer.cc
@@ -72,13 +72,8 @@
   uniform mediump float u_Opacity;
   uniform vec4 u_CenterColor;
   uniform vec4 u_EdgeColor;
-  uniform vec2 u_CenterPosition;
   void main() {
-    // NB: this is on the range [0, 1] and hits its extrema at the horizontal
-    // and vertical edges of the quad, regardless of its aspect ratio. If we
-    // want to have a true circular gradient, we will need to do some extra
-    // math.
-    vec2 position = v_Position - u_CenterPosition;
+    vec2 position = v_Position;
     float edge_color_weight = clamp(2.0 * length(position), 0.0, 1.0);
     float center_color_weight = 1.0 - edge_color_weight;
     vec4 color = u_CenterColor * center_color_weight + u_EdgeColor *
@@ -122,8 +117,6 @@
   center_color_handle_ = glGetUniformLocation(program_handle_, "u_CenterColor");
   edge_color_handle_ = glGetUniformLocation(program_handle_, "u_EdgeColor");
   aspect_ratio_handle_ = glGetUniformLocation(program_handle_, "u_AspectRatio");
-  center_position_handle_ =
-      glGetUniformLocation(program_handle_, "u_CenterPosition");
 }
 
 GradientQuadRenderer::~GradientQuadRenderer() = default;
@@ -131,7 +124,6 @@
 void GradientQuadRenderer::Draw(const gfx::Transform& model_view_proj_matrix,
                                 SkColor edge_color,
                                 SkColor center_color,
-                                const gfx::PointF& center_position,
                                 float opacity,
                                 const gfx::SizeF& element_size,
                                 const CornerRadii& radii) {
@@ -169,9 +161,6 @@
   glUniform1f(aspect_ratio_handle_,
               element_size.width() / element_size.height());
 
-  glUniform2f(center_position_handle_, center_position.x(),
-              center_position.y());
-
   // Pass in model view project matrix.
   glUniformMatrix4fv(model_view_proj_matrix_handle_, 1, false,
                      MatrixToGLArray(model_view_proj_matrix).data());
diff --git a/chrome/browser/vr/renderers/gradient_quad_renderer.h b/chrome/browser/vr/renderers/gradient_quad_renderer.h
index 63e88f9..8cf86b0 100644
--- a/chrome/browser/vr/renderers/gradient_quad_renderer.h
+++ b/chrome/browser/vr/renderers/gradient_quad_renderer.h
@@ -27,7 +27,6 @@
   void Draw(const gfx::Transform& model_view_proj_matrix,
             SkColor edge_color,
             SkColor center_color,
-            const gfx::PointF& center_position,
             float opacity,
             const gfx::SizeF& element_size,
             const CornerRadii& radii);
@@ -44,7 +43,6 @@
   GLuint center_color_handle_;
   GLuint edge_color_handle_;
   GLuint aspect_ratio_handle_;
-  GLuint center_position_handle_;
 
   DISALLOW_COPY_AND_ASSIGN(GradientQuadRenderer);
 };
diff --git a/chrome/browser/vr/target_property.h b/chrome/browser/vr/target_property.h
index 4fa4b30..4514d717 100644
--- a/chrome/browser/vr/target_property.h
+++ b/chrome/browser/vr/target_property.h
@@ -23,6 +23,7 @@
   NORMAL_COLOR_FACTOR,
   INCOGNITO_COLOR_FACTOR,
   FULLSCREEN_COLOR_FACTOR,
+  LOCAL_OPACITY,
 
   // This must be last.
   NUM_TARGET_PROPERTIES
diff --git a/chrome/browser/vr/test/fake_ui_element_renderer.cc b/chrome/browser/vr/test/fake_ui_element_renderer.cc
index 7f0ebfd..4714e5c3 100644
--- a/chrome/browser/vr/test/fake_ui_element_renderer.cc
+++ b/chrome/browser/vr/test/fake_ui_element_renderer.cc
@@ -27,7 +27,6 @@
     const gfx::Transform& view_proj_matrix,
     const SkColor edge_color,
     const SkColor center_color,
-    const gfx::PointF& center_point,
     float opacity,
     const gfx::SizeF& element_size,
     const CornerRadii& corner_radii) {
diff --git a/chrome/browser/vr/test/fake_ui_element_renderer.h b/chrome/browser/vr/test/fake_ui_element_renderer.h
index 2a039ad..55f92a2 100644
--- a/chrome/browser/vr/test/fake_ui_element_renderer.h
+++ b/chrome/browser/vr/test/fake_ui_element_renderer.h
@@ -29,7 +29,6 @@
   void DrawGradientQuad(const gfx::Transform& view_proj_matrix,
                         const SkColor edge_color,
                         const SkColor center_color,
-                        const gfx::PointF& center_point,
                         float opacity,
                         const gfx::SizeF& element_size,
                         const CornerRadii& corner_radii) override;
diff --git a/chrome/browser/vr/ui_element_renderer.cc b/chrome/browser/vr/ui_element_renderer.cc
index 96f2da9f..fbb98d7e 100644
--- a/chrome/browser/vr/ui_element_renderer.cc
+++ b/chrome/browser/vr/ui_element_renderer.cc
@@ -77,15 +77,13 @@
     const gfx::Transform& model_view_proj_matrix,
     const SkColor edge_color,
     const SkColor center_color,
-    const gfx::PointF& center_position,
     float opacity,
     const gfx::SizeF& element_size,
     const CornerRadii& radii) {
   TRACE_EVENT0("gpu", "UiElementRenderer::DrawGradientQuad");
   FlushIfNecessary(gradient_quad_renderer_.get());
   gradient_quad_renderer_->Draw(model_view_proj_matrix, edge_color,
-                                center_color, center_position, opacity,
-                                element_size, radii);
+                                center_color, opacity, element_size, radii);
 }
 
 void UiElementRenderer::DrawGradientGridQuad(
diff --git a/chrome/browser/vr/ui_element_renderer.h b/chrome/browser/vr/ui_element_renderer.h
index 7491d77..28c37560 100644
--- a/chrome/browser/vr/ui_element_renderer.h
+++ b/chrome/browser/vr/ui_element_renderer.h
@@ -70,7 +70,6 @@
       const gfx::Transform& model_view_proj_matrix,
       const SkColor edge_color,
       const SkColor center_color,
-      const gfx::PointF& center_position,
       float opacity,
       const gfx::SizeF& element_size,
       const CornerRadii& radii);
diff --git a/chrome/browser/vr/ui_scene_constants.h b/chrome/browser/vr/ui_scene_constants.h
index e78c8cf17..ffde007 100644
--- a/chrome/browser/vr/ui_scene_constants.h
+++ b/chrome/browser/vr/ui_scene_constants.h
@@ -63,12 +63,13 @@
 static constexpr float kUrlBarVerticalOffsetDMM = -0.516f;
 static constexpr float kUrlBarRotationRad = gfx::DegToRad(-10.0f);
 static constexpr float kUrlBarFontHeightDMM = 0.027f;
-static constexpr float kUrlBarIconSizeDMM = 0.038f;
-static constexpr float kUrlBarBackButtonWidthDMM = 0.087f;
-static constexpr float kUrlBarBackButtonIconOffsetDMM = 0.0045f;
+static constexpr float kUrlBarButtonSizeDMM = 0.064f;
+static constexpr float kUrlBarButtonIconSizeDMM = 0.038f;
+static constexpr float kUrlBarEndButtonIconOffsetDMM = 0.0045f;
+static constexpr float kUrlBarEndButtonWidthDMM = 0.088f;
 static constexpr float kUrlBarSeparatorWidthDMM = 0.002f;
-static constexpr float kUrlBarOriginRegionWidthDMM = 0.583f;
-static constexpr float kUrlBarOriginContentWidthDMM = 0.543f;
+static constexpr float kUrlBarOriginRegionWidthDMM = 0.492f;
+static constexpr float kUrlBarOriginContentWidthDMM = 0.452f;
 static constexpr float kUrlBarOriginContentOffsetDMM = 0.020f;
 static constexpr float kUrlBarFieldSpacingDMM = 0.014f;
 static constexpr float kUrlBarOfflineIconTextSpacingDMM = 0.004f;
@@ -77,6 +78,9 @@
 static constexpr float kUrlBarOriginMinimumPathWidth = 0.044f;
 static constexpr float kUrlBarBackplaneTopPadding = 0.065f;
 static constexpr float kUrlBarBackplanePadding = 0.005f;
+static constexpr float kUrlBarItemCornerRadiusDMM = 0.006f;
+static constexpr float kUrlBarButtonIconScaleFactor =
+    kUrlBarButtonIconSizeDMM / kUrlBarButtonSizeDMM;
 
 static constexpr float kOverlayPlaneDistance = 2.3f;
 
@@ -110,9 +114,7 @@
 static constexpr float kToastWidthDMM = 0.512f;
 static constexpr float kToastHeightDMM = 0.064f;
 static constexpr float kToastOffsetDMM = 0.004f;
-static constexpr float kFullScreenToastOffsetDMM =
-    kFullscreenVerticalOffsetDMM + kFullscreenHeightDMM / 2 + kToastHeightDMM +
-    0.004f;
+static constexpr float kFullScreenToastOffsetDMM = 0.1f;
 static constexpr float kExclusiveScreenToastXPaddingDMM = 0.017f;
 static constexpr float kExclusiveScreenToastYPaddingDMM = 0.02f;
 static constexpr float kExclusiveScreenToastCornerRadiusDMM = 0.004f;
@@ -132,6 +134,7 @@
 static constexpr float kButtonZOffsetHoverDMM = 0.048f;
 
 static constexpr float kCloseButtonDistance = 2.4f;
+static constexpr float kCloseButtonRelativeOffset = -0.8f;
 static constexpr float kCloseButtonVerticalOffset =
     kFullscreenVerticalOffset - (kFullscreenHeight * 0.5f) - 0.35f;
 static constexpr float kCloseButtonDiameter =
@@ -222,7 +225,6 @@
 
 static constexpr float kOmniboxTextFieldIconSizeDMM = 0.05f;
 static constexpr float kOmniboxTextFieldIconButtonSizeDMM = 0.064f;
-static constexpr float kOmniboxTextFieldIconButtonRadiusDMM = 0.006f;
 static constexpr float kOmniboxTextFieldIconButtonHoverOffsetDMM = 0.012f;
 static constexpr float kOmniboxTextFieldRightMargin =
     ((kOmniboxHeightDMM - kOmniboxTextFieldIconButtonSizeDMM) / 2);
@@ -299,6 +301,21 @@
 static constexpr float kMinResizerScale = 0.5f;
 static constexpr float kMaxResizerScale = 1.5f;
 
+static constexpr float kRepositionFrameTolerance = 0.3f;
+static constexpr float kRepositionFrameTopPadding = 0.25f;
+static constexpr float kRepositionFrameEdgePadding = 0.04f;
+static constexpr float kRepositionFrameMaxOpacity = 0.6f;
+static constexpr float kRepositionFrameHitPlaneTopPadding = 0.5f;
+static constexpr float kRepositionFrameTransitionDurationMs = 300;
+
+static constexpr float kOverflowMenuOffset = 0.016f;
+static constexpr float kOverflowMenuMinimumWidth = 0.312f;
+static constexpr float kOverflowButtonRegionHeight = 0.088f;
+static constexpr float kOverflowButtonXOffset = 0.016f;
+static constexpr float kOverflowMenuYPadding = 0.012f;
+static constexpr float kOverflowMenuItemHeight = 0.080f;
+static constexpr float kOverflowMenuItemXPadding = 0.024f;
+
 }  // namespace vr
 
 #endif  // CHROME_BROWSER_VR_UI_SCENE_CONSTANTS_H_
diff --git a/chrome/browser/vr/ui_scene_creator.cc b/chrome/browser/vr/ui_scene_creator.cc
index 88e747a..24024f9 100644
--- a/chrome/browser/vr/ui_scene_creator.cc
+++ b/chrome/browser/vr/ui_scene_creator.cc
@@ -637,6 +637,7 @@
   CreatePrompts();
   CreateSystemIndicators();
   CreateUrlBar();
+  CreateOverflowMenu();
   CreateLoadingIndicator();
   if (model_->update_ready_snackbar_enabled) {
     CreateSnackbars();
@@ -945,9 +946,6 @@
   auto hit_plane = Create<InvisibleHitTarget>(kBackplane, kPhaseBackplanes);
   hit_plane->SetSize(kBackplaneSize, kSceneHeight);
   hit_plane->set_contributes_to_parent_bounds(false);
-  hit_plane->set_cursor_type(kCursorReposition);
-
-  hit_plane->set_event_handlers(CreateRepositioningHandlers(model_, scene_));
 
   scene_->AddUiElement(k2dBrowsingContentGroup, std::move(hit_plane));
 
@@ -1034,9 +1032,62 @@
                         const EditedText& value) { e->UpdateInput(value); },
                      base::Unretained(main_content.get()))));
 
+  auto frame = Create<Rect>(kContentFrame, kPhaseForeground);
+  frame->set_hit_testable(true);
+  frame->set_bounds_contain_children(true);
+  frame->set_padding(kRepositionFrameEdgePadding, kRepositionFrameTopPadding,
+                     kRepositionFrameEdgePadding, kRepositionFrameEdgePadding);
+  frame->set_corner_radius(kContentCornerRadius);
+  frame->set_bounds_contain_padding(false);
+  frame->SetTransitionedProperties({LOCAL_OPACITY});
+  frame->SetTransitionDuration(
+      base::TimeDelta::FromMilliseconds(kRepositionFrameTransitionDurationMs));
+  VR_BIND_COLOR(model_, frame.get(), &ColorScheme::content_reposition_frame,
+                &Rect::SetColor);
+  frame->AddBinding(std::make_unique<Binding<gfx::PointF>>(
+      VR_BIND_LAMBDA(
+          [](Model* model, UiElement* e) {
+            HitTestRequest request;
+            request.ray_origin = model->controller.laser_origin;
+            request.ray_target = model->reticle.target_point;
+            HitTestResult result;
+            e->HitTest(request, &result);
+            return gfx::PointF(
+                result.local_hit_point.x() * e->stale_size().width(),
+                result.local_hit_point.y() * e->stale_size().height());
+          },
+          base::Unretained(model_), base::Unretained(frame.get())),
+      VR_BIND_LAMBDA(
+          [](Rect* e, const gfx::PointF& value) {
+            gfx::RectF inner(e->stale_size());
+            inner.Inset(kRepositionFrameEdgePadding, kRepositionFrameTopPadding,
+                        kRepositionFrameEdgePadding,
+                        kRepositionFrameEdgePadding);
+            gfx::RectF outer(e->stale_size());
+            outer.Inset(
+                kRepositionFrameEdgePadding,
+                kRepositionFrameTopPadding - kRepositionFrameHitPlaneTopPadding,
+                kRepositionFrameEdgePadding, kRepositionFrameEdgePadding);
+            const bool is_on_frame =
+                outer.Contains(value) && !inner.Contains(value);
+            e->SetLocalOpacity(is_on_frame ? 1.0f : 0.0f);
+          },
+          base::Unretained(frame.get()))));
+
+  auto plane =
+      Create<InvisibleHitTarget>(kContentFrameHitPlane, kPhaseForeground);
+  plane->set_bounds_contain_children(true);
+  plane->set_bounds_contain_padding(false);
+  plane->set_corner_radius(kContentCornerRadius);
+  plane->set_cursor_type(kCursorReposition);
+  plane->set_padding(0, kRepositionFrameHitPlaneTopPadding, 0, 0);
+  plane->set_event_handlers(CreateRepositioningHandlers(model_, scene_));
+
   shadow->AddChild(std::move(main_content));
   resizer->AddChild(std::move(shadow));
-  scene_->AddUiElement(k2dBrowsingContentGroup, std::move(resizer));
+  plane->AddChild(std::move(resizer));
+  frame->AddChild(std::move(plane));
+  scene_->AddUiElement(k2dBrowsingContentGroup, std::move(frame));
 
   // Limit reticle distance to a sphere based on maximum content distance.
   scene_->set_background_distance(kFullscreenDistance *
@@ -1688,20 +1739,11 @@
   scaler->set_contributes_to_parent_bounds(false);
   scene_->AddUiElement(kUrlBarPositioner, std::move(scaler));
 
-  auto backplane =
-      Create<InvisibleHitTarget>(kUrlBarBackplane, kPhaseBackplanes);
-  backplane->set_bounds_contain_children(true);
-  backplane->set_contributes_to_parent_bounds(false);
-  backplane->set_padding(kUrlBarBackplanePadding, kUrlBarBackplaneTopPadding,
-                         kUrlBarBackplanePadding, kUrlBarBackplanePadding);
-  backplane->set_event_handlers(CreateRepositioningHandlers(model_, scene_));
-  VR_BIND_VISIBILITY(backplane, !model->fullscreen_enabled());
-  scene_->AddUiElement(kUrlBarDmmRoot, std::move(backplane));
-
   auto url_bar = Create<UiElement>(kUrlBar, kPhaseNone);
   url_bar->SetRotate(1, 0, 0, kUrlBarRotationRad);
   url_bar->set_bounds_contain_children(true);
-  scene_->AddUiElement(kUrlBarBackplane, std::move(url_bar));
+  VR_BIND_VISIBILITY(url_bar, !model->fullscreen_enabled());
+  scene_->AddUiElement(kUrlBarDmmRoot, std::move(url_bar));
 
   auto layout =
       Create<LinearLayout>(kUrlBarLayout, kPhaseNone, LinearLayout::kRight);
@@ -1712,7 +1754,7 @@
       Create<Button>(kUrlBarBackButton, kPhaseForeground,
                      base::BindRepeating(&UiBrowserInterface::NavigateBack,
                                          base::Unretained(browser_)));
-  back_button->SetSize(kUrlBarBackButtonWidthDMM, kUrlBarHeightDMM);
+  back_button->SetSize(kUrlBarEndButtonWidthDMM, kUrlBarHeightDMM);
   back_button->SetCornerRadii(
       {kUrlBarHeightDMM / 2, 0, kUrlBarHeightDMM / 2, 0});
   back_button->set_hover_offset(0.0f);
@@ -1727,8 +1769,8 @@
   auto back_icon =
       Create<VectorIcon>(kUrlBarBackButtonIcon, kPhaseForeground, 128);
   back_icon->SetIcon(vector_icons::kBackArrowIcon);
-  back_icon->SetSize(kUrlBarIconSizeDMM, kUrlBarIconSizeDMM);
-  back_icon->SetTranslate(kUrlBarBackButtonIconOffsetDMM, 0, 0);
+  back_icon->SetSize(kUrlBarButtonIconSizeDMM, kUrlBarButtonIconSizeDMM);
+  back_icon->SetTranslate(kUrlBarEndButtonIconOffsetDMM, 0, 0);
   back_icon->AddBinding(VR_BIND_FUNC(
       SkColor, Model, model_,
       model->can_navigate_back
@@ -1747,8 +1789,6 @@
   auto origin_region = Create<Rect>(kUrlBarOriginRegion, kPhaseForeground);
   origin_region->set_hit_testable(true);
   origin_region->SetSize(kUrlBarOriginRegionWidthDMM, kUrlBarHeightDMM);
-  origin_region->SetCornerRadii(
-      {0, kUrlBarHeightDMM / 2, 0, kUrlBarHeightDMM / 2});
   VR_BIND_COLOR(model_, origin_region.get(), &ColorScheme::element_background,
                 &Rect::SetColor);
   scene_->AddUiElement(kUrlBarLayout, std::move(origin_region));
@@ -1805,6 +1845,167 @@
   event_handlers.button_up = url_click_callback;
   hint_text->set_event_handlers(event_handlers);
   scene_->AddUiElement(kUrlBarOriginRegion, std::move(hint_text));
+
+  separator = Create<Rect>(kUrlBarSeparator, kPhaseForeground);
+  separator->set_hit_testable(true);
+  separator->SetSize(kUrlBarSeparatorWidthDMM, kUrlBarHeightDMM);
+  VR_BIND_COLOR(model_, separator.get(), &ColorScheme::url_bar_separator,
+                &Rect::SetColor);
+  scene_->AddUiElement(kUrlBarLayout, std::move(separator));
+
+  auto overflow_button = Create<Button>(
+      kUrlBarOverflowButton, kPhaseForeground,
+      base::BindRepeating(
+          [](Model* model) { model->overflow_menu_enabled = true; },
+          base::Unretained(model_)));
+  overflow_button->SetSize(kUrlBarEndButtonWidthDMM, kUrlBarHeightDMM);
+  overflow_button->SetCornerRadii(
+      {0, kUrlBarHeightDMM / 2, 0, kUrlBarHeightDMM / 2});
+  overflow_button->set_hover_offset(0.0f);
+  overflow_button->SetSounds(kSoundButtonHover, kSoundButtonClick,
+                             audio_delegate_);
+  VR_BIND_BUTTON_COLORS(model_, overflow_button.get(),
+                        &ColorScheme::back_button, &Button::SetButtonColors);
+  scene_->AddUiElement(kUrlBarLayout, std::move(overflow_button));
+
+  auto overflow_icon =
+      Create<VectorIcon>(kUrlBarOverflowButtonIcon, kPhaseForeground, 128);
+  overflow_icon->SetIcon(kMoreVertIcon);
+  overflow_icon->SetSize(kUrlBarButtonIconSizeDMM, kUrlBarButtonIconSizeDMM);
+  overflow_icon->SetTranslate(-kUrlBarEndButtonIconOffsetDMM, 0, 0);
+  overflow_icon->AddBinding(VR_BIND_FUNC(
+      SkColor, Model, model_,
+      model->can_navigate_back
+          ? model->color_scheme().button_colors.foreground
+          : model->color_scheme().button_colors.foreground_disabled,
+      VectorIcon, overflow_icon.get(), SetColor));
+  scene_->AddUiElement(kUrlBarOverflowButton, std::move(overflow_icon));
+}
+
+void UiSceneCreator::CreateOverflowMenu() {
+  auto overflow_backplane =
+      Create<InvisibleHitTarget>(kOverflowMenuBackplane, kPhaseForeground);
+  EventHandlers event_handlers;
+  event_handlers.button_up = base::BindRepeating(
+      [](Model* model) { model->overflow_menu_enabled = false; },
+      base::Unretained(model_));
+  overflow_backplane->set_event_handlers(event_handlers);
+  overflow_backplane->SetSize(kBackplaneSize, kBackplaneSize);
+  overflow_backplane->set_y_anchoring(TOP);
+  overflow_backplane->SetRotate(1, 0, 0, -kUrlBarRotationRad);
+  VR_BIND_VISIBILITY(overflow_backplane, model->overflow_menu_enabled);
+
+  auto overflow_menu = Create<Rect>(kOverflowMenu, kPhaseForeground);
+  overflow_menu->set_hit_testable(true);
+  overflow_menu->set_y_centering(BOTTOM);
+  overflow_menu->set_bounds_contain_children(true);
+  overflow_menu->SetTranslate(0, kOverflowMenuOffset, 0);
+  overflow_menu->set_corner_radius(kUrlBarItemCornerRadiusDMM);
+  VR_BIND_COLOR(model_, overflow_menu.get(), &ColorScheme::element_background,
+                &Rect::SetColor);
+
+  auto overflow_layout =
+      Create<LinearLayout>(kOverflowMenuLayout, kPhaseNone, LinearLayout::kUp);
+
+  // The forward and refresh buttons are not in the menu layout, but appear as
+  // such. Instead, a placeholder element is inserted into the layout to make
+  // space for them, and the buttons themselves are anchored to the bottom
+  // corners of the overall layout. In the future, when we have more buttons,
+  // they may instead be placed in a linear layout (locked to one side).
+  std::vector<std::tuple<LayoutAlignment, const gfx::VectorIcon&,
+                         base::RepeatingCallback<void()>>>
+      menu_buttons = {
+          {LEFT, vector_icons::kForwardArrowIcon, base::DoNothing()},
+          {RIGHT, vector_icons::kReloadIcon, base::DoNothing()},
+      };
+  for (auto& item : menu_buttons) {
+    auto button = std::make_unique<DiscButton>(
+        base::BindRepeating(
+            [](base::RepeatingCallback<void()> callback, Model* model) {
+              model->overflow_menu_enabled = false;
+              callback.Run();
+            },
+            std::get<2>(item), base::Unretained(model_)),
+        std::get<1>(item), audio_delegate_);
+    button->SetType(kTypeOverflowMenuButton);
+    button->SetDrawPhase(kPhaseForeground);
+    button->SetSize(kUrlBarButtonSizeDMM, kUrlBarButtonSizeDMM);
+    button->set_icon_scale_factor(kUrlBarButtonIconScaleFactor);
+    button->set_hover_offset(kOmniboxTextFieldIconButtonHoverOffsetDMM);
+    button->set_corner_radius(kUrlBarItemCornerRadiusDMM);
+    button->set_requires_layout(false);
+    button->set_contributes_to_parent_bounds(false);
+    button->set_x_anchoring(std::get<0>(item));
+    button->set_x_centering(std::get<0>(item));
+    button->set_y_anchoring(BOTTOM);
+    button->set_y_centering(BOTTOM);
+    button->SetTranslate(
+        kOverflowButtonXOffset * (std::get<0>(item) == RIGHT ? -1 : 1),
+        kOverflowMenuYPadding, 0);
+    VR_BIND_BUTTON_COLORS(model_, button.get(), &ColorScheme::back_button,
+                          &Button::SetButtonColors);
+    overflow_menu->AddChild(std::move(button));
+  }
+
+  // The item that reserves space in the menu layout for the buttons.
+  auto button_spacer = Create<Rect>(kNone, kPhaseNone);
+  button_spacer->SetType(kTypeSpacer);
+  button_spacer->SetSize(kOverflowMenuMinimumWidth,
+                         kOverflowButtonRegionHeight);
+  overflow_layout->AddChild(std::move(button_spacer));
+
+  std::vector<std::pair<int, base::RepeatingCallback<void()>>> menu_items = {
+      {IDS_VR_MENU_CLOSE_INCOGNITO_TABS, base::DoNothing()},
+      {IDS_VR_MENU_NEW_INCOGNITO_TAB, base::DoNothing()},
+  };
+
+  for (auto& item : menu_items) {
+    auto layout = std::make_unique<LinearLayout>(LinearLayout::kRight);
+    layout->SetType(kTypeOverflowMenuItem);
+    layout->SetDrawPhase(kPhaseNone);
+    layout->set_layout_length(kOverflowMenuMinimumWidth);
+
+    auto text =
+        Create<Text>(kNone, kPhaseForeground, kSuggestionContentTextHeightDMM);
+    text->SetDrawPhase(kPhaseForeground);
+    text->SetText(l10n_util::GetStringUTF16(std::get<0>(item)));
+    text->SetLayoutMode(TextLayoutMode::kSingleLineFixedHeight);
+    text->SetAlignment(UiTexture::kTextAlignmentLeft);
+    layout->AddChild(std::move(text));
+
+    auto spacer = Create<Rect>(kNone, kPhaseNone);
+    spacer->SetType(kTypeSpacer);
+    spacer->SetSize(0, kOverflowMenuItemHeight);
+    spacer->set_resizable_by_layout(true);
+    layout->AddChild(std::move(spacer));
+
+    auto background = Create<Button>(
+        kNone, kPhaseForeground,
+        base::BindRepeating(
+            [](base::RepeatingCallback<void()> callback, Model* model) {
+              model->overflow_menu_enabled = false;
+              callback.Run();
+            },
+            std::get<1>(item), base::Unretained(model_)));
+    background->set_hit_testable(true);
+    background->set_bounds_contain_children(true);
+    background->set_hover_offset(0);
+    background->set_padding(kOverflowMenuItemXPadding, 0);
+    VR_BIND_BUTTON_COLORS(model_, background.get(), &ColorScheme::button_colors,
+                          &Button::SetButtonColors);
+    background->AddChild(std::move(layout));
+
+    overflow_layout->AddChild(std::move(background));
+  }
+
+  auto top_cap = Create<Rect>(kNone, kPhaseNone);
+  top_cap->SetType(kTypeSpacer);
+  top_cap->SetSize(kOverflowMenuMinimumWidth, kOverflowMenuYPadding);
+  overflow_layout->AddChild(std::move(top_cap));
+
+  overflow_menu->AddChild(std::move(overflow_layout));
+  overflow_backplane->AddChild(std::move(overflow_menu));
+  scene_->AddUiElement(kUrlBarOverflowButton, std::move(overflow_backplane));
 }
 
 void UiSceneCreator::CreateLoadingIndicator() {
@@ -2074,7 +2275,7 @@
   mic_button->SetSize(kOmniboxTextFieldIconButtonSizeDMM,
                       kOmniboxTextFieldIconButtonSizeDMM);
   mic_button->set_hover_offset(kOmniboxTextFieldIconButtonHoverOffsetDMM);
-  mic_button->set_corner_radius(kOmniboxTextFieldIconButtonRadiusDMM);
+  mic_button->set_corner_radius(kUrlBarItemCornerRadiusDMM);
   mic_button->SetSounds(kSoundButtonHover, kSoundButtonClick, audio_delegate_);
 
   VR_BIND_VISIBILITY(mic_button,
@@ -2185,7 +2386,8 @@
   element->set_contributes_to_parent_bounds(false);
   element->SetSize(kCloseButtonDiameter, kCloseButtonDiameter);
   element->set_hover_offset(kButtonZOffsetHoverDMM * kCloseButtonDistance);
-  element->SetTranslate(0, kCloseButtonVerticalOffset, -kCloseButtonDistance);
+  element->set_y_anchoring(BOTTOM);
+  element->SetTranslate(0, kCloseButtonRelativeOffset, -kCloseButtonDistance);
   VR_BIND_BUTTON_COLORS(model_, element.get(), &ColorScheme::button_colors,
                         &DiscButton::SetButtonColors);
 
@@ -2195,9 +2397,7 @@
   element->AddBinding(
       VR_BIND(bool, Model, model_, model->fullscreen_enabled(), UiElement,
               element.get(),
-              view->SetTranslate(0,
-                                 value ? kCloseButtonFullscreenVerticalOffset
-                                       : kCloseButtonVerticalOffset,
+              view->SetTranslate(0, kCloseButtonRelativeOffset,
                                  value ? -kCloseButtonFullscreenDistance
                                        : -kCloseButtonDistance)));
   element->AddBinding(VR_BIND(
@@ -2404,6 +2604,7 @@
   auto parent = CreateTransientParent(kExclusiveScreenToastTransientParent,
                                       kToastTimeoutSeconds, false);
   parent->set_contributes_to_parent_bounds(false);
+  parent->set_y_anchoring(TOP);
   VR_BIND_VISIBILITY(parent, model->fullscreen_enabled());
   scene_->AddUiElement(k2dBrowsingForeground, std::move(parent));
 
diff --git a/chrome/browser/vr/ui_scene_creator.h b/chrome/browser/vr/ui_scene_creator.h
index 8395a0b7..c246dfb 100644
--- a/chrome/browser/vr/ui_scene_creator.h
+++ b/chrome/browser/vr/ui_scene_creator.h
@@ -43,6 +43,7 @@
   void CreateBackground();
   void CreateViewportAwareRoot();
   void CreateUrlBar();
+  void CreateOverflowMenu();
   void CreateLoadingIndicator();
   void CreateSnackbars();
   void CreateOmnibox();
diff --git a/chrome/browser/vr/ui_unittest.cc b/chrome/browser/vr/ui_unittest.cc
index d679fbf..abd493d 100644
--- a/chrome/browser/vr/ui_unittest.cc
+++ b/chrome/browser/vr/ui_unittest.cc
@@ -49,6 +49,8 @@
     kBackgroundBottom,
     kCeiling,
     kFloor,
+    kContentFrame,
+    kContentFrameHitPlane,
     kContentQuad,
     kContentQuadShadow,
     kBackplane,
@@ -58,6 +60,8 @@
     kUrlBarSeparator,
     kUrlBarOriginRegion,
     kUrlBarOriginContent,
+    kUrlBarOverflowButton,
+    kUrlBarOverflowButtonIcon,
     kController,
     kReticle,
     kLaser,
@@ -457,6 +461,8 @@
   visible_in_fullscreen.insert(kControllerHomeButton);
   visible_in_fullscreen.insert(kLaser);
   visible_in_fullscreen.insert(kReticle);
+  visible_in_fullscreen.insert(kContentFrame);
+  visible_in_fullscreen.insert(kContentFrameHitPlane);
 
   CreateScene(kNotInCct, kNotInWebVr);
 
diff --git a/chrome/browser/vr/vector_icons/BUILD.gn b/chrome/browser/vr/vector_icons/BUILD.gn
index 5414141..f4983820 100644
--- a/chrome/browser/vr/vector_icons/BUILD.gn
+++ b/chrome/browser/vr/vector_icons/BUILD.gn
@@ -8,11 +8,12 @@
   icon_directory = "."
 
   icons = [
-    "file_download_done.icon",
-    "reposition.icon",
-    "sad_tab.icon",
     "daydream_controller_home_button.icon",
     "daydream_controller_app_button.icon",
+    "file_download_done.icon",
+    "more_vert.icon",
+    "reposition.icon",
+    "sad_tab.icon",
   ]
 }
 
diff --git a/chrome/browser/vr/vector_icons/more_vert.icon b/chrome/browser/vr/vector_icons/more_vert.icon
new file mode 100644
index 0000000..586047c
--- /dev/null
+++ b/chrome/browser/vr/vector_icons/more_vert.icon
@@ -0,0 +1,23 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 24,
+MOVE_TO, 12, 8,
+R_CUBIC_TO, 1.1f, 0, 2, -0.9f, 2, -2,
+R_CUBIC_TO, 0, -1.1f, -0.9f, -2, -2, -2,
+R_CUBIC_TO, -1.1f, 0, -2, 0.9f, -2, 2,
+R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,
+CLOSE,
+R_MOVE_TO, 0, 2,
+R_CUBIC_TO, -1.1f, 0, -2, 0.9f, -2, 2,
+R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,
+R_CUBIC_TO, 1.1f, 0, 2, -0.9f, 2, -2,
+R_CUBIC_TO, 0, -1.1f, -0.9f, -2, -2, -2,
+CLOSE,
+R_MOVE_TO, 0, 6,
+R_CUBIC_TO, -1.1f, 0, -2, 0.9f, -2, 2,
+R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,
+R_CUBIC_TO, 1.1f, 0, 2, -0.9f, 2, -2,
+R_CUBIC_TO, 0, -1.1f, -0.9f, -2, -2, -2,
+CLOSE
diff --git a/chrome/renderer/autofill/password_generation_test_utils.cc b/chrome/renderer/autofill/password_generation_test_utils.cc
index ae4f8a5..561b4d2 100644
--- a/chrome/renderer/autofill/password_generation_test_utils.cc
+++ b/chrome/renderer/autofill/password_generation_test_utils.cc
@@ -10,6 +10,7 @@
 #include "components/autofill/content/renderer/test_password_generation_agent.h"
 #include "components/autofill/core/common/password_form_generation_data.h"
 #include "components/autofill/core/common/signatures_util.h"
+#include "net/base/escape.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/WebKit/public/web/WebDocument.h"
 #include "third_party/WebKit/public/web/WebFormElement.h"
@@ -25,9 +26,10 @@
 
 void SetNotBlacklistedMessage(TestPasswordGenerationAgent* generation_agent,
                               const char* form_str) {
+  std::string escaped_form = net::EscapeQueryParamValue(form_str, false);
   autofill::PasswordForm form;
-  form.origin = form_util::StripAuthAndParams(
-      GURL(base::StringPrintf("data:text/html;charset=utf-8,%s", form_str)));
+  form.origin = form_util::StripAuthAndParams(GURL(base::StringPrintf(
+      "data:text/html;charset=utf-8,%s", escaped_form.c_str())));
   generation_agent->FormNotBlacklisted(form);
 }
 
diff --git a/chrome/renderer/safe_browsing/threat_dom_details_browsertest.cc b/chrome/renderer/safe_browsing/threat_dom_details_browsertest.cc
index 6c15e2c..b4fb098e 100644
--- a/chrome/renderer/safe_browsing/threat_dom_details_browsertest.cc
+++ b/chrome/renderer/safe_browsing/threat_dom_details_browsertest.cc
@@ -75,7 +75,8 @@
     details->ExtractResources(&params);
     ASSERT_EQ(1u, params.size());
     auto* param = params[0].get();
-    EXPECT_EQ(GURL(urlprefix + html), param->url);
+    EXPECT_EQ(GURL(urlprefix + net::EscapeQueryParamValue(html, false)),
+              param->url);
     EXPECT_EQ(0, param->node_id);
     EXPECT_EQ(0, param->parent_node_id);
     EXPECT_TRUE(param->child_node_ids.empty());
@@ -92,7 +93,7 @@
     std::string html = "<html><head><script src=\"" + script1_url.spec() +
                        "\"></script><script src=\"" + script2_url.spec() +
                        "\"></script></head></html>";
-    GURL url(urlprefix + html);
+    GURL url(urlprefix + net::EscapeQueryParamValue(html, false));
 
     LoadHTML(html.c_str());
     std::vector<safe_browsing::mojom::ThreatDOMDetailsNodePtr> params;
@@ -149,7 +150,7 @@
         "<iframe src=\"" +
         net::EscapeForHTML(iframe1_url.spec()) +
         "\"></iframe></div></div></head></html>";
-    GURL url(urlprefix + html);
+    GURL url(urlprefix + net::EscapeQueryParamValue(html, false));
 
     LoadHTML(html.c_str());
     std::vector<safe_browsing::mojom::ThreatDOMDetailsNodePtr> params;
@@ -269,7 +270,8 @@
     LoadHTML(html.c_str());
     std::vector<safe_browsing::mojom::ThreatDOMDetailsNodePtr> params;
     details->ExtractResources(&params);
-    GURL url = GURL(urlprefix + html);
+
+    GURL url = GURL(urlprefix + net::EscapeQueryParamValue(html, false));
     ASSERT_EQ(2u, params.size());
     auto* param = params[0].get();
     EXPECT_TRUE(param->url.is_empty());
@@ -334,7 +336,7 @@
       "<iframe id=baz><iframe src=\"" +
       net::EscapeForHTML(iframe2_url.spec()) +
       "\"></iframe></iframe></div></div></head></html>";
-  GURL url(urlprefix + html);
+  GURL url(urlprefix + net::EscapeQueryParamValue(html, false));
 
   LoadHTML(html.c_str());
   std::vector<safe_browsing::mojom::ThreatDOMDetailsNodePtr> params;
diff --git a/chrome/test/data/webui/settings/site_details_tests.js b/chrome/test/data/webui/settings/site_details_tests.js
index e3616e94..25a1324 100644
--- a/chrome/test/data/webui/settings/site_details_tests.js
+++ b/chrome/test/data/webui/settings/site_details_tests.js
@@ -83,6 +83,9 @@
       test_util.createContentSettingTypeToValuePair(
           settings.ContentSettingsTypes.SENSORS,
           [test_util.createRawSiteException('https://foo.com:443')]),
+      test_util.createContentSettingTypeToValuePair(
+          settings.ContentSettingsTypes.PAYMENT_HANDLER,
+          [test_util.createRawSiteException('https://foo.com:443')]),
     ]);
 
     browserProxy = new TestSiteSettingsPrefsBrowserProxy();
@@ -126,6 +129,9 @@
     optionalSiteDetailsContentSettingsTypes[settings.ContentSettingsTypes
                                                 .SENSORS] =
         'enableSensorsContentSetting';
+    optionalSiteDetailsContentSettingsTypes[settings.ContentSettingsTypes
+                                                .PAYMENT_HANDLER] =
+        'enablePaymentHandlerContentSetting';
     browserProxy.setPrefs(prefs);
 
     // First, explicitly set all the optional settings to false.
@@ -195,6 +201,7 @@
     loadTimeData.overrideValues({enableSafeBrowsingSubresourceFilter: true});
     loadTimeData.overrideValues({enableClipboardContentSetting: true});
     loadTimeData.overrideValues({enableSensorsContentSetting: true});
+    loadTimeData.overrideValues({enablePaymentHandlerContentSetting: true});
     testElement = createSiteDetails('https://foo.com:443');
 
     return browserProxy.whenCalled('isOriginValid')
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index e8a76552..2c786b0 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -19,6 +19,7 @@
     "//dbus",
   ]
   deps = [
+    ":account_manager_proto",
     ":attestation_proto",
     ":authpolicy_proto",
     ":biod_proto",
@@ -59,6 +60,8 @@
     "accelerometer/accelerometer_reader.h",
     "accelerometer/accelerometer_types.cc",
     "accelerometer/accelerometer_types.h",
+    "account_manager/account_manager.cc",
+    "account_manager/account_manager.h",
     "app_mode/kiosk_oem_manifest_parser.cc",
     "app_mode/kiosk_oem_manifest_parser.h",
     "attestation/attestation_constants.cc",
@@ -664,6 +667,7 @@
     "//url",
   ]
   sources = [
+    "account_manager/account_manager_unittest.cc",
     "app_mode/kiosk_oem_manifest_parser_unittest.cc",
     "attestation/attestation_flow_unittest.cc",
     "audio/audio_devices_pref_handler_impl_unittest.cc",
@@ -848,3 +852,11 @@
 
   proto_out_dir = "chromeos/dbus/smbprovider"
 }
+
+proto_library("account_manager_proto") {
+  sources = [
+    "account_manager/tokens.proto",
+  ]
+
+  proto_out_dir = "chromeos/account_manager"
+}
diff --git a/chromeos/account_manager/OWNERS b/chromeos/account_manager/OWNERS
new file mode 100644
index 0000000..48fcf91
--- /dev/null
+++ b/chromeos/account_manager/OWNERS
@@ -0,0 +1 @@
+sinhak@chromium.org
diff --git a/chromeos/account_manager/account_manager.cc b/chromeos/account_manager/account_manager.cc
new file mode 100644
index 0000000..dff9752
--- /dev/null
+++ b/chromeos/account_manager/account_manager.cc
@@ -0,0 +1,176 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/account_manager/account_manager.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/important_file_writer.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+#include "base/task_runner_util.h"
+#include "base/task_scheduler/post_task.h"
+#include "chromeos/account_manager/tokens.pb.h"
+#include "third_party/protobuf/src/google/protobuf/message_lite.h"
+
+namespace chromeos {
+
+namespace {
+
+constexpr base::FilePath::CharType kTokensFileName[] =
+    FILE_PATH_LITERAL("AccountManagerTokens.bin");
+constexpr int kTokensFileMaxSizeInBytes = 100000;  // ~100 KB
+
+AccountManager::TokenMap LoadTokensFromDisk(
+    const base::FilePath& tokens_file_path) {
+  AccountManager::TokenMap tokens;
+
+  VLOG(1) << "AccountManager::LoadTokensFromDisk";
+  std::string token_file_data;
+  bool success = ReadFileToStringWithMaxSize(tokens_file_path, &token_file_data,
+                                             kTokensFileMaxSizeInBytes);
+  if (!success) {
+    LOG(ERROR) << "Failed to read tokens file";
+    return tokens;
+  }
+
+  chromeos::account_manager::Tokens tokens_proto;
+  success = tokens_proto.ParseFromString(token_file_data);
+  if (!success) {
+    LOG(ERROR) << "Failed to parse tokens from file";
+    return tokens;
+  }
+
+  tokens.insert(tokens_proto.login_scoped_tokens().begin(),
+                tokens_proto.login_scoped_tokens().end());
+
+  return tokens;
+}
+
+std::string GetSerializedTokens(const AccountManager::TokenMap& tokens) {
+  chromeos::account_manager::Tokens tokens_proto;
+  *tokens_proto.mutable_login_scoped_tokens() =
+      ::google::protobuf::Map<std::string, std::string>(tokens.begin(),
+                                                        tokens.end());
+  return tokens_proto.SerializeAsString();
+}
+
+}  // namespace
+
+AccountManager::AccountManager() : weak_factory_(this) {}
+
+void AccountManager::Initialize(const base::FilePath& home_dir) {
+  Initialize(home_dir, base::CreateSequencedTaskRunnerWithTraits(
+                           {base::TaskShutdownBehavior::BLOCK_SHUTDOWN,
+                            base::MayBlock()}));
+}
+
+void AccountManager::Initialize(
+    const base::FilePath& home_dir,
+    scoped_refptr<base::SequencedTaskRunner> task_runner) {
+  VLOG(1) << "AccountManager::Initialize";
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (init_state_ != InitializationState::kNotStarted) {
+    // |Initialize| has already been called once. To help diagnose possible race
+    // conditions, check whether the |home_dir| parameter provided by the first
+    // invocation of |Initialize| matches the one it is currently being called
+    // with.
+    DCHECK_EQ(home_dir, writer_->path().DirName());
+    return;
+  }
+
+  init_state_ = InitializationState::kInProgress;
+  task_runner_ = task_runner;
+  writer_ = std::make_unique<base::ImportantFileWriter>(
+      home_dir.Append(kTokensFileName), task_runner_);
+
+  PostTaskAndReplyWithResult(
+      task_runner_.get(), FROM_HERE,
+      base::BindOnce(&LoadTokensFromDisk, writer_->path()),
+      base::BindOnce(&AccountManager::InsertTokensAndRunInitializationCallbacks,
+                     weak_factory_.GetWeakPtr()));
+}
+
+void AccountManager::InsertTokensAndRunInitializationCallbacks(
+    const AccountManager::TokenMap& tokens) {
+  VLOG(1) << "AccountManager::InsertTokensAndRunInitializationCallbacks";
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  tokens_.insert(tokens.begin(), tokens.end());
+  init_state_ = InitializationState::kInitialized;
+
+  for (auto& cb : initialization_callbacks_) {
+    std::move(cb).Run();
+  }
+  initialization_callbacks_.clear();
+}
+
+AccountManager::~AccountManager() {
+  // AccountManager is supposed to be used as a leaky global.
+}
+
+void AccountManager::RunOnInitialization(base::OnceClosure closure) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (init_state_ != InitializationState::kInitialized) {
+    initialization_callbacks_.emplace_back(std::move(closure));
+  } else {
+    std::move(closure).Run();
+  }
+}
+
+void AccountManager::GetAccounts(
+    base::OnceCallback<void(std::vector<std::string>)> callback) {
+  DCHECK_NE(init_state_, InitializationState::kNotStarted);
+
+  base::OnceClosure closure =
+      base::BindOnce(&AccountManager::GetAccountsInternal,
+                     weak_factory_.GetWeakPtr(), std::move(callback));
+  RunOnInitialization(std::move(closure));
+}
+
+void AccountManager::GetAccountsInternal(
+    base::OnceCallback<void(std::vector<std::string>)> callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_EQ(init_state_, InitializationState::kInitialized);
+
+  std::vector<std::string> accounts;
+  accounts.reserve(tokens_.size());
+  for (auto& key_val : tokens_) {
+    accounts.emplace_back(key_val.first);
+  }
+  std::move(callback).Run(std::move(accounts));
+}
+
+void AccountManager::UpsertToken(const std::string& account_id,
+                                 const std::string& login_scoped_token) {
+  DCHECK_NE(init_state_, InitializationState::kNotStarted);
+
+  base::OnceClosure closure = base::BindOnce(
+      &AccountManager::UpsertTokenInternal, weak_factory_.GetWeakPtr(),
+      account_id, login_scoped_token);
+  RunOnInitialization(std::move(closure));
+}
+
+void AccountManager::UpsertTokenInternal(
+    const std::string& account_id,
+    const std::string& login_scoped_token) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_EQ(init_state_, InitializationState::kInitialized);
+
+  tokens_[account_id] = login_scoped_token;
+  PersistTokensAsync();
+}
+
+void AccountManager::PersistTokensAsync() {
+  // Schedule (immediately) a non-blocking write.
+  writer_->WriteNow(
+      std::make_unique<std::string>(GetSerializedTokens(tokens_)));
+}
+
+}  // namespace chromeos
diff --git a/chromeos/account_manager/account_manager.h b/chromeos/account_manager/account_manager.h
new file mode 100644
index 0000000..315cbfd
--- /dev/null
+++ b/chromeos/account_manager/account_manager.h
@@ -0,0 +1,110 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_H_
+#define CHROMEOS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "chromeos/chromeos_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+class ImportantFileWriter;
+}  // namespace base
+
+namespace chromeos {
+
+class CHROMEOS_EXPORT AccountManager {
+ public:
+  // A map of account identifiers to login scoped tokens.
+  using TokenMap = std::unordered_map<std::string, std::string>;
+
+  // Note: |Initialize| MUST be called at least once on this object.
+  AccountManager();
+  ~AccountManager();
+
+  // |home_dir| is the path of the Device Account's home directory (root of the
+  // user's cryptohome). This method MUST be called at least once.
+  void Initialize(const base::FilePath& home_dir);
+
+  // Gets (async) a list of account identifiers known to |AccountManager|.
+  void GetAccounts(base::OnceCallback<void(std::vector<std::string>)> callback);
+
+  // Updates or inserts an LST (Login Scoped Token), for the account
+  // corresponding to the given account id.
+  void UpsertToken(const std::string& account_id,
+                   const std::string& login_scoped_token);
+
+ private:
+  enum InitializationState {
+    kNotStarted,   // Initialize has not been called
+    kInProgress,   // Initialize has been called but not completed
+    kInitialized,  // Initialization was successfully completed
+  };
+
+  friend class AccountManagerTest;
+  FRIEND_TEST_ALL_PREFIXES(AccountManagerTest, TestInitialization);
+  FRIEND_TEST_ALL_PREFIXES(AccountManagerTest, TestUpsert);
+  FRIEND_TEST_ALL_PREFIXES(AccountManagerTest, TestPersistence);
+
+  // Initializes |AccountManager| with the provided |task_runner| and location
+  // of the user's home directory.
+  void Initialize(const base::FilePath& home_dir,
+                  scoped_refptr<base::SequencedTaskRunner> task_runner);
+
+  // Reads tokens from |tokens| and inserts them in |tokens_| and runs all
+  // callbacks waiting on |AccountManager| initialization.
+  void InsertTokensAndRunInitializationCallbacks(const TokenMap& tokens);
+
+  // Accepts a closure and runs it immediately if |AccountManager| has already
+  // been initialized, otherwise saves the |closure| for running later, when the
+  // class is initialized.
+  void RunOnInitialization(base::OnceClosure closure);
+
+  // Does the actual work of getting a list of accounts. Assumes that
+  // |AccountManager| initialization (|init_state_|) is complete.
+  void GetAccountsInternal(
+      base::OnceCallback<void(std::vector<std::string>)> callback);
+
+  // Does the actual work of updating or inserting tokens. Assumes that
+  // |AccountManager| initialization (|init_state_|) is complete.
+  void UpsertTokenInternal(const std::string& account_id,
+                           const std::string& login_scoped_token);
+
+  // Persists (async) the current state of |tokens_| on disk.
+  void PersistTokensAsync();
+
+  // Status of this object's initialization.
+  InitializationState init_state_ = InitializationState::kNotStarted;
+
+  // A task runner for disk I/O.
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
+  std::unique_ptr<base::ImportantFileWriter> writer_;
+
+  // A map of account ids to login scoped tokens.
+  TokenMap tokens_;
+
+  // Callbacks waiting on class initialization (|init_state_|).
+  std::vector<base::OnceClosure> initialization_callbacks_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<AccountManager> weak_factory_;
+  DISALLOW_COPY_AND_ASSIGN(AccountManager);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_H_
diff --git a/chromeos/account_manager/account_manager_unittest.cc b/chromeos/account_manager/account_manager_unittest.cc
new file mode 100644
index 0000000..d0f3edc
--- /dev/null
+++ b/chromeos/account_manager/account_manager_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/account_manager/account_manager.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+
+class AccountManagerTest : public testing::Test {
+ public:
+  AccountManagerTest() = default;
+  ~AccountManagerTest() override {}
+
+ protected:
+  void SetUp() override {
+    ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
+    account_manager_ = std::make_unique<AccountManager>();
+    account_manager_->Initialize(tmp_dir_.GetPath(),
+                                 base::SequencedTaskRunnerHandle::Get());
+  }
+
+  // Check base/test/scoped_task_environment.h. This must be the first member /
+  // declared before any member that cares about tasks.
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  base::ScopedTempDir tmp_dir_;
+  std::unique_ptr<AccountManager> account_manager_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AccountManagerTest);
+};
+
+TEST_F(AccountManagerTest, TestInitialization) {
+  AccountManager account_manager;
+
+  EXPECT_EQ(account_manager.init_state_,
+            AccountManager::InitializationState::kNotStarted);
+  account_manager.Initialize(tmp_dir_.GetPath(),
+                             base::SequencedTaskRunnerHandle::Get());
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(account_manager.init_state_,
+            AccountManager::InitializationState::kInitialized);
+}
+
+TEST_F(AccountManagerTest, TestUpsert) {
+  account_manager_->UpsertToken("abc", "123");
+
+  std::vector<std::string> accounts;
+  base::RunLoop run_loop;
+  account_manager_->GetAccounts(base::BindOnce(
+      [](std::vector<std::string>* accounts, base::OnceClosure quit_closure,
+         std::vector<std::string> stored_accounts) -> void {
+        *accounts = stored_accounts;
+        std::move(quit_closure).Run();
+      },
+      base::Unretained(&accounts), run_loop.QuitClosure()));
+  run_loop.Run();
+
+  EXPECT_EQ(1UL, accounts.size());
+  EXPECT_EQ("abc", accounts[0]);
+}
+
+TEST_F(AccountManagerTest, TestPersistence) {
+  account_manager_->UpsertToken("abc", "123");
+  base::RunLoop().RunUntilIdle();
+
+  account_manager_ = std::make_unique<AccountManager>();
+  account_manager_->Initialize(tmp_dir_.GetPath(),
+                               base::SequencedTaskRunnerHandle::Get());
+
+  std::vector<std::string> accounts;
+  base::RunLoop run_loop;
+  account_manager_->GetAccounts(base::BindOnce(
+      [](std::vector<std::string>* accounts, base::OnceClosure quit_closure,
+         std::vector<std::string> stored_accounts) -> void {
+        *accounts = stored_accounts;
+        std::move(quit_closure).Run();
+      },
+      base::Unretained(&accounts), run_loop.QuitClosure()));
+  run_loop.Run();
+
+  EXPECT_EQ(1UL, accounts.size());
+  EXPECT_EQ("abc", accounts[0]);
+}
+
+}  // namespace chromeos
diff --git a/chromeos/account_manager/tokens.proto b/chromeos/account_manager/tokens.proto
new file mode 100644
index 0000000..7787d5e21
--- /dev/null
+++ b/chromeos/account_manager/tokens.proto
@@ -0,0 +1,14 @@
+// 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.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package chromeos.account_manager;
+
+message Tokens {
+  // A mapping from GAIA id to a Login Scoped (Refresh) Token
+  map<string, string> login_scoped_tokens = 1;
+}
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.cc b/chromeos/services/assistant/assistant_manager_service_impl.cc
index cd063aa..2ee46223 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl.cc
@@ -11,6 +11,7 @@
 #include "chromeos/assistant/internal/internal_util.h"
 #include "chromeos/services/assistant/service.h"
 #include "libassistant/shared/internal_api/assistant_manager_internal.h"
+#include "url/gurl.h"
 
 namespace chromeos {
 namespace assistant {
@@ -79,7 +80,8 @@
 }
 
 void AssistantManagerServiceImpl::OnOpenUrl(const std::string& url) {
-  subscribers_.ForAllPtrs([&url](auto* ptr) { ptr->OnOpenUrlResponse(url); });
+  subscribers_.ForAllPtrs(
+      [&url](auto* ptr) { ptr->OnOpenUrlResponse(GURL(url)); });
 }
 
 }  // namespace assistant
diff --git a/chromeos/services/assistant/platform/audio_input_provider_impl.cc b/chromeos/services/assistant/platform/audio_input_provider_impl.cc
index 4df0e8f..ad72192a6 100644
--- a/chromeos/services/assistant/platform/audio_input_provider_impl.cc
+++ b/chromeos/services/assistant/platform/audio_input_provider_impl.cc
@@ -79,7 +79,7 @@
     : binding_(this) {
   mojom::AudioInputObserverPtr observer;
   binding_.Bind(mojo::MakeRequest(&observer));
-  audio_input->AddAudioInputObserver(std::move(observer));
+  audio_input->AddObserver(std::move(observer));
 }
 
 AudioInputImpl::~AudioInputImpl() = default;
diff --git a/components/chrome_cleaner/public/constants/BUILD.gn b/components/chrome_cleaner/public/constants/BUILD.gn
index 826ffa6c..38c17138 100644
--- a/components/chrome_cleaner/public/constants/BUILD.gn
+++ b/components/chrome_cleaner/public/constants/BUILD.gn
@@ -6,5 +6,6 @@
   sources = [
     "constants.cc",
     "constants.h",
+    "result_codes.h",
   ]
 }
diff --git a/components/chrome_cleaner/public/constants/constants.h b/components/chrome_cleaner/public/constants/constants.h
index 685873f..157777e 100644
--- a/components/chrome_cleaner/public/constants/constants.h
+++ b/components/chrome_cleaner/public/constants/constants.h
@@ -5,6 +5,8 @@
 #ifndef COMPONENTS_CHROME_CLEANER_PUBLIC_CONSTANTS_CONSTANTS_H_
 #define COMPONENTS_CHROME_CLEANER_PUBLIC_CONSTANTS_CONSTANTS_H_
 
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+
 // Constants shared by the Chromium and the Chrome Cleanaup tool repos.
 
 namespace chrome_cleaner {
@@ -93,14 +95,19 @@
 extern const wchar_t kVersionValueName[];
 
 // Exit codes from the Software Reporter process identified by Chrome.
-constexpr int kSwReporterCleanupNeeded = 0;
-constexpr int kSwReporterNothingFound = 2;
-constexpr int kSwReporterPostRebootCleanupNeeded = 4;
-constexpr int kSwReporterNonRemovableOnly = 10;
-constexpr int kSwReporterDelayedPostRebootCleanupNeeded = 15;
-constexpr int kSwReporterSuspiciousOnly = 32;
-constexpr int kSwReporterTimeoutWithoutUwS = 34;
-constexpr int kSwReporterTimeoutWithUwS = 35;
+constexpr int kSwReporterCleanupNeeded = RESULT_CODE_SUCCESS;
+constexpr int kSwReporterNothingFound = RESULT_CODE_NO_PUPS_FOUND;
+constexpr int kSwReporterPostRebootCleanupNeeded =
+    DEPRECATED_RESULT_CODE_ABOUT_TO_REBOOT;
+constexpr int kSwReporterNonRemovableOnly =
+    RESULT_CODE_EXAMINED_FOR_REMOVAL_ONLY;
+constexpr int kSwReporterDelayedPostRebootCleanupNeeded =
+    RESULT_CODE_PENDING_REBOOT;
+constexpr int kSwReporterSuspiciousOnly = RESULT_CODE_REPORT_ONLY_PUPS_FOUND;
+constexpr int kSwReporterTimeoutWithoutUwS =
+    RESULT_CODE_WATCHDOG_TIMEOUT_WITHOUT_REMOVABLE_UWS;
+constexpr int kSwReporterTimeoutWithUwS =
+    RESULT_CODE_WATCHDOG_TIMEOUT_WITH_REMOVABLE_UWS;
 
 // Values to be passed to the kChromePromptSwitch of the Chrome Cleanup Tool to
 // indicate how the user interacted with the accept button.
diff --git a/components/chrome_cleaner/public/constants/result_codes.h b/components/chrome_cleaner/public/constants/result_codes.h
new file mode 100644
index 0000000..0450ab3d
--- /dev/null
+++ b/components/chrome_cleaner/public/constants/result_codes.h
@@ -0,0 +1,224 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CHROME_CLEANER_PUBLIC_CONSTANTS_RESULT_CODES_H_
+#define COMPONENTS_CHROME_CLEANER_PUBLIC_CONSTANTS_RESULT_CODES_H_
+
+#include <limits>
+
+namespace chrome_cleaner {
+
+// The exit code of a scanner or cleaner process, containing the result of a
+// scanning or cleaning session.
+typedef int ResultCode;
+
+// A list of standard result codes.
+enum ResultCodeValues : ResultCode {
+  // Generic success.
+  RESULT_CODE_SUCCESS = 0,
+
+  // Generic failure.
+  RESULT_CODE_FAILED = 1,
+
+  // The scanner didn't find any UwS.
+  RESULT_CODE_NO_PUPS_FOUND = 2,
+
+  // The scanning or cleanup has been canceled.
+  RESULT_CODE_CANCELED = 3,
+
+  // DEPRECATED. Cleanup succeeded and a reboot is about to happen to complete
+  // it.
+  // This value should not be used in new code, but is still defined for use in
+  // Chromium code that reads results from older versions of the cleaner.
+  DEPRECATED_RESULT_CODE_ABOUT_TO_REBOOT = 4,
+
+  // Could not register the application to be automatically rerun after a
+  // reboot to confirm post-reboot success.
+  RESULT_CODE_RUNONCE_REGISTRATION_FAILED = 5,
+
+  // Shutdown could not be initiated, so a reboot won't automatically happen.
+  RESULT_CODE_INITIATE_SHUTDOWN_FAILED = 6,
+
+  // Failure to clean a registry entry.
+  RESULT_CODE_REGISTRY_CLEAN_FAILED = 7,
+
+  // Failure to remove a file.
+  RESULT_CODE_DISK_CLEAN_FAILED = 8,
+
+  // Failed to use the restart manager.
+  RESULT_CODE_RESTART_MANAGER_FAILED = 9,
+
+  // Found only UwS which has been fully examined for removal but not yet
+  // enabled.  Older versions also used this for report-only, which is now
+  // RESULT_CODE_REPORT_ONLY_PUPS_FOUND.
+  RESULT_CODE_EXAMINED_FOR_REMOVAL_ONLY = 10,
+
+  // Failure to check cleanup requirements.
+  RESULT_CODE_CLEANUP_REQUIREMENTS_FAILED = 11,
+
+  // To identify that we didn't exit yet, e.g., when we send intermediate logs
+  // reports.
+  RESULT_CODE_PENDING = 12,
+
+  // To identify that a custom action failed.
+  RESULT_CODE_CUSTOM_ACTION_FAILED = 13,
+
+  // To identify a success that happened post-reboot.
+  RESULT_CODE_POST_REBOOT_SUCCESS = 14,
+
+  // A reboot is required to finish, but the user hasn't accepted it yet.
+  RESULT_CODE_PENDING_REBOOT = 15,
+
+  // To identify that the user refused to elevate the post-reboot run.
+  RESULT_CODE_POST_REBOOT_ELEVATION_DENIED = 16,
+
+  // To identify that the app is not running elevated, even though the command
+  // line says that it should be. Note: this does not indicate that the user
+  // declined the elevation prompt.
+  RESULT_CODE_FAILED_TO_ELEVATE = 17,
+
+  // To identify that we failed to read the upload logs file.
+  RESULT_CODE_FAILED_TO_READ_UPLOAD_LOGS_FILE = 18,
+
+  // To identify that we failed to upload pending logs.
+  RESULT_CODE_FAILED_TO_UPLOAD_LOGS = 19,
+
+  // To identify that we succeeded uploading pending logs.
+  RESULT_CODE_UPLOADED_PENDING_LOGS = 20,
+
+  // Could not register the application to be automatically rerun after a
+  // reboot to confirm post-reboot success.
+  RESULT_CODE_TASK_SCHEDULING_FAILED = 21,
+
+  // Reports when another instance of Chrome Cleanup is already running.
+  RESULT_CODE_ALREADY_RUNNING = 22,
+
+  // This code is used when a scheduled task runs because a cleanup never got to
+  // start.
+  RESULT_CODE_FAILED_TO_START_CLEANUP = 23,
+
+  // This code is used when a scheduled task runs because a cleanup wasn't
+  // completed.
+  RESULT_CODE_FAILED_TO_COMPLETE_CLEANUP = 24,
+
+  // This code is used when the |kUploadLogFileSwitch| was specified but there
+  // was no client identifier to use.
+  RESULT_CODE_EMPTY_CLIENT_ID_UPLOAD_ATTEMPT = 25,
+
+  // DEPRECATED. Report-inhibiting PUP found. This category of PUP is no longer
+  // used.
+  // 26
+
+  // DEPRECATED. Removal-inhibiting PUP found. This category of PUP is no
+  // longer used.
+  // 27
+
+  // This code is used when the reporter didn't find any UwS but the signature
+  // matcher was invalid.
+  RESULT_CODE_NO_PUPS_FOUND_NO_SIGNATURE_MATCHER = 28,
+
+  // This code is used when the validation step detects that one or more PUP
+  // files are remaining after a cleanup and fails to remove them.
+  RESULT_CODE_POST_CLEANUP_VALIDATION_FAILED = 29,
+
+  // DEPRECATED. The Kasko reporter failed to initialize.
+  // 30
+
+  // DEPRECATED. Replaced with
+  // RESULT_CODE_WATCHDOG_TIMEOUT_WITHOUT_REMOVABLE_UWS or
+  // RESULT_CODE_WATCHDOG_TIMEOUT_WITHOUT_REMOVABLE_UWS.
+  // 31
+
+  // Found PUPs which we currently only report.
+  RESULT_CODE_REPORT_ONLY_PUPS_FOUND = 32,
+
+  // Logs upload from the Crashpad crash handler.
+  RESULT_CODE_CRASH_HANDLER = 33,
+
+  // The process failed to complete in a reasonable amount of time and our
+  // watchdog terminated the process. At that time no removable UwS had been
+  // found.
+  RESULT_CODE_WATCHDOG_TIMEOUT_WITHOUT_REMOVABLE_UWS = 34,
+
+  // The process failed to complete in a reasonable amount of time and our
+  // watchdog terminated the process. At that time removable UwS had been found.
+  RESULT_CODE_WATCHDOG_TIMEOUT_WITH_REMOVABLE_UWS = 35,
+
+  // Identifies when something has failed when initializing/setting up the
+  // engine.
+  RESULT_CODE_ENGINE_INITIALIZATION_FAILED = 36,
+
+  // Identifies failures when starting the sandbox process.
+  RESULT_CODE_FAILED_TO_START_SANDBOX_PROCESS = 37,
+
+  // Identifies that the scanner reported a found UwS ID we don't support.
+  RESULT_CODE_ENGINE_REPORTED_UNSUPPORTED_UWS = 38,
+
+  // Identifies that an initialization error occurred.
+  RESULT_CODE_INITIALIZATION_ERROR = 39,
+
+  // Identifies when the scan did not complete because the engine returned an
+  // error during the scan.
+  RESULT_CODE_SCANNING_ENGINE_ERROR = 40,
+
+  // Identifies that the verification of the signature on loaded libraries has
+  // failed.
+  RESULT_CODE_SIGNATURE_VERIFICATION_FAILED = 41,
+
+  // Chrome IPC switches were passed to the command line but the execution mode
+  // is not kScanning.
+  RESULT_CODE_EXPECTED_SCANNING_EXECUTION_MODE = 42,
+
+  // Chrome only sent one of the two expected switches corresponding to the
+  // prompt IPC.
+  RESULT_CODE_INVALID_IPC_SWITCHES = 43,
+
+  // The parent process disconnected from the IPC while the pipe was still
+  // needed by the cleaner process (scanner still running or waiting for a
+  // response from Chrome).
+  RESULT_CODE_CHROME_PROMPT_IPC_DISCONNECTED_TOO_SOON = 44,
+
+  // DEPRECATED. The target process for the signature matcher disconnected from
+  // the IPC while the pipe was still needed by the broker process.
+  // 45
+
+  // The user declined the elevation prompt or didn't have admin rights to
+  // accept elevation.
+  RESULT_CODE_ELEVATION_PROMPT_DECLINED = 46,
+
+  // The target process for the ESET sandbox disconnected from the IPC
+  // while the pipe was still needed by the broker process.
+  RESULT_CODE_ESET_SANDBOX_DISCONNECTED_TOO_SOON = 47,
+
+  // The user denied the cleanup prompt.
+  RESULT_CODE_CLEANUP_PROMPT_DENIED = 48,
+
+  // The cleaner process in scanning mode timed out while waiting for the user
+  // the respond to the Chrome prompt.
+  RESULT_CODE_WATCHDOG_TIMEOUT_WAITING_FOR_PROMPT_RESPONSE = 49,
+
+  // The cleaner process in cleanup mode took too long to complete. Time out is
+  // controlled by the cleaner process in scanning mode.
+  RESULT_CODE_WATCHDOG_TIMEOUT_CLEANING = 50,
+
+  // The cleaner process attempted to clean some UwS while using an engine that
+  // only supports scanning.
+  RESULT_CODE_CLEANUP_NOT_SUPPORTED_BY_ENGINE = 51,
+
+  // The cleaner executable was run manually by a user without providing the
+  // required execution mode flags. This used to launch the Chrome Cleaner's UI
+  // which has been deprecated.
+  RESULT_CODE_MANUAL_EXECUTION_BY_USER = 52,
+
+  // WHEN YOU ADD NEW EXIT CODES, DON'T FORGET TO UPDATE THE MONITORING RULES.
+  // See http://go/chrome-cleaner-exit-codes. (Google internal only - external
+  // contributors please ask one of the OWNERS to do the update.)
+
+  // Used mainly for testing.
+  RESULT_CODE_INVALID = std::numeric_limits<ResultCode>::max(),
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // COMPONENTS_CHROME_CLEANER_PUBLIC_CONSTANTS_RESULT_CODES_H_
diff --git a/components/constrained_window/BUILD.gn b/components/constrained_window/BUILD.gn
index 9f055219..de9893a 100644
--- a/components/constrained_window/BUILD.gn
+++ b/components/constrained_window/BUILD.gn
@@ -52,7 +52,7 @@
     "constrained_window_views_unittest.cc",
   ]
 
-  if (is_mac && !mac_views_browser) {
+  if (is_mac) {
     sources += [ "test_create_native_web_modal_manager_cocoa.cc" ]
   }
 
diff --git a/components/cronet/android/BUILD.gn b/components/cronet/android/BUILD.gn
index 8e63aa83..28b22ca 100644
--- a/components/cronet/android/BUILD.gn
+++ b/components/cronet/android/BUILD.gn
@@ -27,7 +27,7 @@
 }
 
 generate_jni_registration("cronet_jni_registration") {
-  target = ":cronet_sample_apk"
+  target = ":cronet_jni_apk"
   output = "$root_gen_dir/components/cronet/android/${target_name}.h"
   exception_files = jni_exception_files
 
@@ -242,7 +242,6 @@
 
 # cronet_api_java.jar defines Cronet API.
 android_library("cronet_api_java") {
-  output_name = "cronet_api"
   java_files = [
     "api/src/org/chromium/net/BidirectionalStream.java",
     "api/src/org/chromium/net/CronetEngine.java",
@@ -387,6 +386,16 @@
   ]
 }
 
+# This target exists only to provide input to the generate_jni_registration
+# target previously declared.  It contains all Java with JNI declarations.
+android_apk("cronet_jni_apk") {
+  apk_name = "CronetJni"
+  android_manifest = "sample/AndroidManifest.xml"
+  deps = [
+    ":cronet_impl_all_java",
+  ]
+}
+
 android_resources("cronet_sample_apk_resources") {
   resource_dirs = [ "sample/res" ]
   android_manifest = "sample/AndroidManifest.xml"
@@ -402,10 +411,9 @@
   ]
 
   deps = [
-    ":cronet_api_java",
-    ":cronet_impl_all_java",
     ":cronet_sample_apk_resources",
-    "//base:base_java",
+    ":package_api_java",
+    ":package_impl_native_java",
     "//third_party/android_tools:android_support_v7_appcompat_java",
   ]
 }
@@ -419,15 +427,15 @@
     ":cronet_combine_proguard_flags",
     ":cronet_sample_apk_java",
     ":cronet_sample_apk_resources",
-    "//base:base_java",
-    "//third_party/jsr-305:jsr_305_javalib",
   ]
 
+  # Cronet jars will include this, so don't duplicate.
+  generate_buildconfig_java = false
+
   proguard_enabled = true
   proguard_configs = [
     "$target_gen_dir/cronet_impl_native_proguard.cfg",
     "cronet_impl_common_proguard.cfg",
-    "sample/javatests/proguard.cfg",
     "//base/android/proguard/chromium_apk.flags",
   ]
 }
@@ -446,25 +454,16 @@
   apk_under_test = ":cronet_sample_apk"
   android_manifest = "sample/javatests/AndroidManifest.xml"
   java_files = [
-    "sample/javatests/src/org/chromium/cronet_sample_apk/Criteria.java",
     "sample/javatests/src/org/chromium/cronet_sample_apk/CronetSampleTest.java",
   ]
-
   deps = [
-    ":cronet_api_java",
-    ":cronet_impl_all_java",
-    ":cronet_sample_apk_java",
-    ":cronet_sample_test_apk_resources",
-    "//base:base_java",
-    "//base:base_java_test_support",
-    "//net/android:net_java_test_support",
     "//third_party/android_support_test_runner:rules_java",
     "//third_party/android_support_test_runner:runner_java",
     "//third_party/junit",
   ]
-  additional_apks = [ "//net/android:net_test_support_apk" ]
 
   proguard_enabled = true
+  proguard_configs = [ "sample/javatests/proguard.cfg" ]
 }
 
 generate_jni("cronet_tests_jni_headers") {
@@ -1166,7 +1165,6 @@
     "sample/AndroidManifest.xml",
     "sample/javatests/AndroidManifest.xml",
     "sample/javatests/proguard.cfg",
-    "sample/javatests/src/org/chromium/cronet_sample_apk/Criteria.java",
     "sample/javatests/src/org/chromium/cronet_sample_apk/CronetSampleTest.java",
     "sample/README",
     "sample/res/layout/activity_main.xml",
@@ -1298,6 +1296,16 @@
         ":$_dep_name",
       ]
     }
+
+    # _copy_target_name includes ${dep} which includes "_java", so in turn
+    # _copy_target_name contains "_java" which triggers
+    # build/config/android/internal_rules.gni whitelist of target names that
+    # must have build_configs, so emit one here.
+    write_build_config("${_copy_target_name}__build_config") {
+      build_config = "$target_gen_dir/$_copy_target_name.build_config"
+      type = "group"
+    }
+
     _deps += [ ":" + _copy_target_name ]
   }
 
@@ -1306,7 +1314,7 @@
   }
 }
 
-copy_java8_jars("copy_cronet_java8_jars") {
+copy_java8_jars("copy_cronet_java8_java") {
   deps = [
     ":cronet_api_java",
     ":cronet_impl_platform_java",
@@ -1430,6 +1438,35 @@
   ]
 }
 
+# These package_* targets represent how Cronet is used in production code.
+# Using these targets is preferred to using the underlying targets like
+# :cronet_api_java or :cronet_impl_all_java, as it better tests production
+# usage.
+android_java_prebuilt("package_api_java") {
+  jar_path = "$_package_dir/cronet_api.jar"
+  deps = [
+    ":copy_cronet_java8_java_:cronet_api_java",
+  ]
+}
+
+android_java_prebuilt("package_impl_common_java") {
+  jar_path = "$_package_dir/cronet_impl_common_java.jar"
+  deps = [
+    ":package_api_java",
+    ":repackage_extracted_common_jars",
+  ]
+}
+
+android_java_prebuilt("package_impl_native_java") {
+  jar_path = "$_package_dir/cronet_impl_native_java.jar"
+  deps = [
+    ":package_api_java",
+    ":package_impl_common_java",
+    ":repackage_extracted_native_jars",
+    "//third_party/jsr-305:jsr_305_javalib",
+  ]
+}
+
 # Enforce that ARM Neon is not used when building for ARMv7
 if (target_cpu == "arm" && arm_version == 7 && !arm_use_neon) {
   action("enforce_no_neon") {
@@ -1463,7 +1500,7 @@
   args = [
     "--api_jar",
     rebase_path(
-        "$root_out_dir/lib.java/components/cronet/android/cronet_api.jar",
+        "$root_out_dir/lib.java/components/cronet/android/cronet_api_java.jar",
         root_build_dir),
     "--impl_jar",
     rebase_path(
@@ -1506,7 +1543,7 @@
       (!(target_cpu == "arm" && arm_version == 7) || !arm_use_neon)) {
     deps = [
       ":api_static_checks",
-      ":copy_cronet_java8_jars",
+      ":copy_cronet_java8_java",
       ":cronet_package_copy",
       ":cronet_package_copy_native_lib",
       ":cronet_package_copy_native_lib_unstripped",
diff --git a/components/cronet/android/cronet_impl_native_proguard.cfg b/components/cronet/android/cronet_impl_native_proguard.cfg
index 62042856..99989ff 100644
--- a/components/cronet/android/cronet_impl_native_proguard.cfg
+++ b/components/cronet/android/cronet_impl_native_proguard.cfg
@@ -14,9 +14,11 @@
 -dontwarn org.chromium.base.WindowCallbackWrapper
 
 # Generated for chrome apk and not included into cronet.
--dontwarn org.chromium.base.BuildConfig
 -dontwarn org.chromium.base.library_loader.NativeLibraries
 -dontwarn org.chromium.base.multidex.ChromiumMultiDexInstaller
+-dontwarn org.chromium.base.metrics.CachedMetrics
+-dontwarn org.chromium.base.library_loader.LibraryLoader
+-dontwarn org.chromium.base.SysUtils
 
 # Objects of this type are passed around by native code, but the class
 # is never used directly by native code. Since the class is not loaded, it does
diff --git a/components/cronet/android/sample/javatests/src/org/chromium/cronet_sample_apk/Criteria.java b/components/cronet/android/sample/javatests/src/org/chromium/cronet_sample_apk/Criteria.java
deleted file mode 100644
index c097683..0000000
--- a/components/cronet/android/sample/javatests/src/org/chromium/cronet_sample_apk/Criteria.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.cronet_sample_apk;
-
-/**
- * Provides a means for validating whether some condition/criteria has been met.
- */
-public interface Criteria {
-
-    /**
-     * @return Whether the criteria this is testing has been satisfied.
-     */
-    public boolean isSatisfied();
-
-}
diff --git a/components/cronet/android/sample/javatests/src/org/chromium/cronet_sample_apk/CronetSampleTest.java b/components/cronet/android/sample/javatests/src/org/chromium/cronet_sample_apk/CronetSampleTest.java
index 60a1afa..0c58a7e 100644
--- a/components/cronet/android/sample/javatests/src/org/chromium/cronet_sample_apk/CronetSampleTest.java
+++ b/components/cronet/android/sample/javatests/src/org/chromium/cronet_sample_apk/CronetSampleTest.java
@@ -11,47 +11,29 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.widget.TextView;
 
-import org.junit.After;
 import org.junit.Assert;
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.BaseJUnit4ClassRunner;
-import org.chromium.base.test.util.Feature;
-import org.chromium.net.test.EmbeddedTestServer;
-
 /**
  * Base test class for all CronetSample based tests.
  */
-@RunWith(BaseJUnit4ClassRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class CronetSampleTest {
-    private EmbeddedTestServer mTestServer;
-    private String mUrl;
+    private final String mUrl = "http://localhost";
 
     @Rule
     public ActivityTestRule<CronetSampleActivity> mActivityTestRule =
             new ActivityTestRule<>(CronetSampleActivity.class, false, false);
 
-    @Before
-    public void setUp() throws Exception {
-        mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
-        mUrl = mTestServer.getURL("/echo?status=200");
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mTestServer.stopAndDestroyServer();
-    }
-
     @Test
     @SmallTest
-    @Feature({"Cronet"})
     public void testLoadUrl() throws Exception {
         CronetSampleActivity activity = launchCronetSampleWithUrl(mUrl);
 
@@ -70,7 +52,8 @@
 
             @Override
             public void onTextChanged(CharSequence s, int start, int before, int count) {
-                if (s.toString().equals("Completed " + mUrl + " (200)")) {
+                if (s.toString().startsWith("Failed " + mUrl
+                            + " (Exception in CronetUrlRequest: net::ERR_CONNECTION_REFUSED")) {
                     done.open();
                 }
             }
diff --git a/components/favicon/core/BUILD.gn b/components/favicon/core/BUILD.gn
index 6324dd82..59f1e72 100644
--- a/components/favicon/core/BUILD.gn
+++ b/components/favicon/core/BUILD.gn
@@ -62,6 +62,7 @@
     "//components/history/core/browser:browser",
     "//components/history/core/test:test",
     "//components/image_fetcher/core",
+    "//components/image_fetcher/core:test_support",
     "//components/variations:test_support",
     "//net:test_support",
     "//skia",
diff --git a/components/favicon/core/large_icon_service.cc b/components/favicon/core/large_icon_service.cc
index 23e6a9b1..eac4973 100644
--- a/components/favicon/core/large_icon_service.cc
+++ b/components/favicon/core/large_icon_service.cc
@@ -334,8 +334,7 @@
       fallback_icon_style_(
           std::make_unique<favicon_base::FallbackIconStyle>()) {}
 
-LargeIconWorker::~LargeIconWorker() {
-}
+LargeIconWorker::~LargeIconWorker() {}
 
 void LargeIconWorker::OnIconLookupComplete(
     const GURL& page_url,
@@ -458,8 +457,7 @@
   // a DCHECK(image_fetcher_) here.
 }
 
-LargeIconService::~LargeIconService() {
-}
+LargeIconService::~LargeIconService() {}
 
 base::CancelableTaskTracker::TaskId
 LargeIconService::GetLargeIconOrFallbackStyle(
@@ -603,10 +601,10 @@
 
   image_fetcher_->SetDataUseServiceName(
       data_use_measurement::DataUseUserData::LARGE_ICON_SERVICE);
-  image_fetcher_->StartOrQueueNetworkRequest(
+  image_fetcher_->FetchImage(
       server_request_url.spec(), server_request_url,
-      base::Bind(&OnFetchIconFromGoogleServerComplete, favicon_service_,
-                 page_url, callback),
+      base::BindRepeating(&OnFetchIconFromGoogleServerComplete,
+                          favicon_service_, page_url, callback),
       traffic_annotation);
 }
 
diff --git a/components/favicon/core/large_icon_service_unittest.cc b/components/favicon/core/large_icon_service_unittest.cc
index 8d60141..7007c78 100644
--- a/components/favicon/core/large_icon_service_unittest.cc
+++ b/components/favicon/core/large_icon_service_unittest.cc
@@ -21,6 +21,7 @@
 #include "components/favicon_base/fallback_icon_style.h"
 #include "components/favicon_base/favicon_types.h"
 #include "components/image_fetcher/core/image_fetcher.h"
+#include "components/image_fetcher/core/mock_image_fetcher.h"
 #include "components/image_fetcher/core/request_metadata.h"
 #include "components/variations/variations_params_manager.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
@@ -37,6 +38,7 @@
 namespace favicon {
 namespace {
 
+using image_fetcher::MockImageFetcher;
 using testing::ElementsAre;
 using testing::IsEmpty;
 using testing::IsNull;
@@ -55,12 +57,13 @@
 
 ACTION_P(PostFetchReply, p0) {
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(arg2, arg0, p0, image_fetcher::RequestMetadata()));
+      FROM_HERE, base::BindOnce(std::move(*arg3), arg0, p0,
+                                image_fetcher::RequestMetadata()));
 }
 
 ACTION_P2(PostFetchReplyWithMetadata, p0, p1) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                                base::Bind(arg2, arg0, p0, p1));
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(*arg3), arg0, p0, p1));
 }
 
 ACTION_P(PostBoolReplyToArg4, p0) {
@@ -116,23 +119,6 @@
          fallback_icon_style.background_color == color;
 }
 
-class MockImageFetcher : public image_fetcher::ImageFetcher {
- public:
-  MOCK_METHOD1(SetImageFetcherDelegate,
-               void(image_fetcher::ImageFetcherDelegate* delegate));
-  MOCK_METHOD1(SetDataUseServiceName,
-               void(image_fetcher::ImageFetcher::DataUseServiceName name));
-  MOCK_METHOD1(SetImageDownloadLimit,
-               void(base::Optional<int64_t> max_download_bytes));
-  MOCK_METHOD1(SetDesiredImageFrameSize, void(const gfx::Size& size));
-  MOCK_METHOD4(StartOrQueueNetworkRequest,
-               void(const std::string&,
-                    const GURL&,
-                    const ImageFetcherCallback&,
-                    const net::NetworkTrafficAnnotationTag&));
-  MOCK_METHOD0(GetImageDecoder, image_fetcher::ImageDecoder*());
-};
-
 // TODO(jkrcal): Make the tests a bit crisper, see crbug.com/725822.
 class LargeIconServiceTest : public testing::Test {
  public:
@@ -168,7 +154,7 @@
 
   base::MockCallback<favicon_base::GoogleFaviconServerCallback> callback;
   EXPECT_CALL(*mock_image_fetcher_,
-              StartOrQueueNetworkRequest(_, kExpectedServerUrl, _, _))
+              FetchImageAndData_(_, kExpectedServerUrl, _, _, _))
       .WillOnce(PostFetchReply(gfx::Image::CreateFrom1xBitmap(
           CreateTestSkBitmap(64, 64, kTestColor))));
   EXPECT_CALL(mock_favicon_service_,
@@ -210,7 +196,7 @@
 
   base::MockCallback<favicon_base::GoogleFaviconServerCallback> callback;
   EXPECT_CALL(*mock_image_fetcher_,
-              StartOrQueueNetworkRequest(_, kExpectedServerUrl, _, _))
+              FetchImageAndData_(_, kExpectedServerUrl, _, _, _))
       .WillOnce(PostFetchReply(gfx::Image::CreateFrom1xBitmap(
           CreateTestSkBitmap(64, 64, kTestColor))));
   EXPECT_CALL(mock_favicon_service_,
@@ -244,7 +230,7 @@
   image_fetcher::RequestMetadata expected_metadata;
   expected_metadata.content_location_header = kExpectedOriginalUrl.spec();
   EXPECT_CALL(*mock_image_fetcher_,
-              StartOrQueueNetworkRequest(_, kExpectedServerUrl, _, _))
+              FetchImageAndData_(_, kExpectedServerUrl, _, _, _))
       .WillOnce(PostFetchReplyWithMetadata(
           gfx::Image::CreateFrom1xBitmap(
               CreateTestSkBitmap(64, 64, kTestColor)),
@@ -279,7 +265,7 @@
       .WillOnce(PostBoolReplyToArg2(true));
 
   EXPECT_CALL(*mock_image_fetcher_,
-              StartOrQueueNetworkRequest(_, kExpectedServerUrl, _, _))
+              FetchImageAndData_(_, kExpectedServerUrl, _, _, _))
       .WillOnce(PostFetchReply(gfx::Image::CreateFrom1xBitmap(
           CreateTestSkBitmap(64, 64, kTestColor))));
   // Verify that the non-trimmed page URL is used when writing to the database.
@@ -302,10 +288,10 @@
                                      favicon_base::IconType::kTouchIcon, _))
       .WillOnce(PostBoolReplyToArg2(true));
   // The request has no "check_seen=true"; full URL is tested elsewhere.
-  EXPECT_CALL(
-      *mock_image_fetcher_,
-      StartOrQueueNetworkRequest(
-          _, Property(&GURL::query, Not(HasSubstr("check_seen=true"))), _, _))
+  EXPECT_CALL(*mock_image_fetcher_,
+              FetchImageAndData_(
+                  _, Property(&GURL::query, Not(HasSubstr("check_seen=true"))),
+                  _, _, _))
       .WillOnce(PostFetchReply(gfx::Image()));
 
   base::MockCallback<favicon_base::GoogleFaviconServerCallback> callback;
@@ -324,8 +310,7 @@
 TEST_F(LargeIconServiceTest, ShouldNotQueryGoogleServerIfInvalidScheme) {
   const GURL kDummyFtpUrl("ftp://www.example.com");
 
-  EXPECT_CALL(*mock_image_fetcher_, StartOrQueueNetworkRequest(_, _, _, _))
-      .Times(0);
+  EXPECT_CALL(*mock_image_fetcher_, FetchImageAndData_(_, _, _, _, _)).Times(0);
 
   base::MockCallback<favicon_base::GoogleFaviconServerCallback> callback;
 
@@ -346,8 +331,7 @@
 TEST_F(LargeIconServiceTest, ShouldNotQueryGoogleServerIfInvalidURL) {
   const GURL kDummyInvalidUrl("htt");
 
-  EXPECT_CALL(*mock_image_fetcher_, StartOrQueueNetworkRequest(_, _, _, _))
-      .Times(0);
+  EXPECT_CALL(*mock_image_fetcher_, FetchImageAndData_(_, _, _, _, _)).Times(0);
 
   base::MockCallback<favicon_base::GoogleFaviconServerCallback> callback;
 
@@ -380,7 +364,7 @@
 
   base::MockCallback<favicon_base::GoogleFaviconServerCallback> callback;
   EXPECT_CALL(*mock_image_fetcher_,
-              StartOrQueueNetworkRequest(_, kExpectedServerUrl, _, _))
+              FetchImageAndData_(_, kExpectedServerUrl, _, _, _))
       .WillOnce(PostFetchReply(gfx::Image()));
   EXPECT_CALL(mock_favicon_service_,
               UnableToDownloadFavicon(kExpectedServerUrl));
@@ -409,8 +393,7 @@
       .WillByDefault(Return(true));
 
   EXPECT_CALL(mock_favicon_service_, UnableToDownloadFavicon(_)).Times(0);
-  EXPECT_CALL(*mock_image_fetcher_, StartOrQueueNetworkRequest(_, _, _, _))
-      .Times(0);
+  EXPECT_CALL(*mock_image_fetcher_, FetchImageAndData_(_, _, _, _, _)).Times(0);
   EXPECT_CALL(mock_favicon_service_, SetOnDemandFavicons(_, _, _, _, _))
       .Times(0);
 
@@ -436,8 +419,7 @@
                                      favicon_base::IconType::kTouchIcon, _))
       .WillOnce(PostBoolReplyToArg2(false));
 
-  EXPECT_CALL(*mock_image_fetcher_, StartOrQueueNetworkRequest(_, _, _, _))
-      .Times(0);
+  EXPECT_CALL(*mock_image_fetcher_, FetchImageAndData_(_, _, _, _, _)).Times(0);
   EXPECT_CALL(mock_favicon_service_, SetOnDemandFavicons(_, _, _, _, _))
       .Times(0);
 
diff --git a/components/feed/OWNERS b/components/feed/OWNERS
index ba0283b8..c8ef00b 100644
--- a/components/feed/OWNERS
+++ b/components/feed/OWNERS
@@ -1,5 +1,6 @@
 fgorski@chromium.org
 pavely@chromium.org
+pnoland@chromium.org
 zea@chromium.org
 
 # Team: chrome-jardin-team@google.com
diff --git a/components/image_fetcher/core/BUILD.gn b/components/image_fetcher/core/BUILD.gn
index 19d9cd4a..ad44616 100644
--- a/components/image_fetcher/core/BUILD.gn
+++ b/components/image_fetcher/core/BUILD.gn
@@ -8,7 +8,6 @@
     "image_data_fetcher.h",
     "image_decoder.h",
     "image_fetcher.h",
-    "image_fetcher_delegate.h",
     "image_fetcher_impl.cc",
     "image_fetcher_impl.h",
     "request_metadata.cc",
@@ -24,6 +23,19 @@
   ]
 }
 
+static_library("test_support") {
+  testonly = true
+  sources = [
+    "mock_image_fetcher.cc",
+    "mock_image_fetcher.h",
+  ]
+
+  deps = [
+    ":core",
+    "//testing/gmock",
+  ]
+}
+
 source_set("unit_tests") {
   testonly = true
   sources = [
@@ -32,6 +44,7 @@
   ]
   deps = [
     ":core",
+    ":test_support",
     "//net",
     "//net:test_support",
     "//testing/gmock",
diff --git a/components/image_fetcher/core/image_fetcher.h b/components/image_fetcher/core/image_fetcher.h
index 5cb084e..086e7f4 100644
--- a/components/image_fetcher/core/image_fetcher.h
+++ b/components/image_fetcher/core/image_fetcher.h
@@ -11,14 +11,13 @@
 #include "base/macros.h"
 #include "base/optional.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
-#include "components/image_fetcher/core/image_fetcher_delegate.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "url/gurl.h"
 
 namespace gfx {
 class Image;
 class Size;
-}
+}  // namespace gfx
 
 namespace image_fetcher {
 
@@ -34,14 +33,19 @@
   virtual ~ImageFetcher() {}
 
   using ImageFetcherCallback =
-      base::Callback<void(const std::string& id,
-                          const gfx::Image& image,
-                          const RequestMetadata& metadata)>;
+      base::OnceCallback<void(const std::string& id,
+                              const gfx::Image& image,
+                              const RequestMetadata& metadata)>;
+
+  // Callback with the |image_data|. If an error prevented a http response,
+  // |request_metadata.response_code| will be RESPONSE_CODE_INVALID.
+  // TODO(treib): Use RefCountedBytes to avoid copying.
+  using ImageDataFetcherCallback =
+      base::OnceCallback<void(const std::string& image_data,
+                              const RequestMetadata& request_metadata)>;
 
   using DataUseServiceName = data_use_measurement::DataUseUserData::ServiceName;
 
-  virtual void SetImageFetcherDelegate(ImageFetcherDelegate* delegate) = 0;
-
   // Sets a service name against which to track data usage.
   virtual void SetDataUseServiceName(
       DataUseServiceName data_use_service_name) = 0;
@@ -60,14 +64,41 @@
   // size.
   virtual void SetDesiredImageFrameSize(const gfx::Size& size) = 0;
 
-  // An empty gfx::Image will be returned to the callback in case the image
-  // could not be fetched.
-  virtual void StartOrQueueNetworkRequest(
+  // Fetch an image and optionally decode it. |image_data_callback| is called
+  // when the image fetch completes, but |image_data_callback| may be empty.
+  // |image_callback| is called when the image is finished decoding.
+  // |image_callback| may be empty if image decoding is not required. If a
+  // callback is provided, it will be called exactly once. On failure, an empty
+  // string/gfx::Image is returned.
+  virtual void FetchImageAndData(
       const std::string& id,
       const GURL& image_url,
-      const ImageFetcherCallback& callback,
+      ImageDataFetcherCallback image_data_callback,
+      ImageFetcherCallback image_callback,
       const net::NetworkTrafficAnnotationTag& traffic_annotation) = 0;
 
+  // Fetch an image and decode it. An empty gfx::Image will be returned to the
+  // callback in case the image could not be fetched. This is the same as
+  // calling FetchImageAndData without an |image_data_callback|.
+  void FetchImage(const std::string& id,
+                  const GURL& image_url,
+                  ImageFetcherCallback callback,
+                  const net::NetworkTrafficAnnotationTag& traffic_annotation) {
+    FetchImageAndData(id, image_url, ImageDataFetcherCallback(),
+                      std::move(callback), traffic_annotation);
+  }
+
+  // Just fetch the image data, do not decode. This is the same as
+  // calling FetchImageAndData without an |image_callback|.
+  void FetchImageData(
+      const std::string& id,
+      const GURL& image_url,
+      ImageDataFetcherCallback callback,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation) {
+    FetchImageAndData(id, image_url, std::move(callback),
+                      ImageFetcherCallback(), traffic_annotation);
+  }
+
   virtual ImageDecoder* GetImageDecoder() = 0;
 
  private:
diff --git a/components/image_fetcher/core/image_fetcher_delegate.h b/components/image_fetcher/core/image_fetcher_delegate.h
deleted file mode 100644
index fd9c362..0000000
--- a/components/image_fetcher/core/image_fetcher_delegate.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_IMAGE_FETCHER_CORE_IMAGE_FETCHER_DELEGATE_H_
-#define COMPONENTS_IMAGE_FETCHER_CORE_IMAGE_FETCHER_DELEGATE_H_
-
-#include <string>
-
-#include "base/macros.h"
-
-namespace gfx {
-class Image;
-}
-
-namespace image_fetcher {
-
-class ImageFetcherDelegate {
- public:
-  ImageFetcherDelegate() {}
-
-  // Called when the data for an image was fetched. |id| is an identifier for
-  // the fetch (as passed to ImageFetcher::StartOrQueueNetworkRequest); |data|
-  // stores (generally compressed) image data owned by the caller, and can be
-  // empty if the fetch failed.
-  virtual void OnImageDataFetched(const std::string& id,
-                                  const std::string& data) {}
-
-  // Called when an image was fetched and decoded. |id| is an identifier for the
-  // fetch (as passed to ImageFetcher::StartOrQueueNetworkRequest); |image|
-  // stores image data owned by the caller, and can be an empty gfx::Image.
-  virtual void OnImageFetched(const std::string& id, const gfx::Image& image) {}
-
- protected:
-  virtual ~ImageFetcherDelegate() {}
-
-  DISALLOW_COPY_AND_ASSIGN(ImageFetcherDelegate);
-};
-
-}  // namespace image_fetcher
-
-#endif  // COMPONENTS_IMAGE_FETCHER_CORE_IMAGE_FETCHER_DELEGATE_H_
diff --git a/components/image_fetcher/core/image_fetcher_impl.cc b/components/image_fetcher/core/image_fetcher_impl.cc
index 0cf6c10..f4cf904 100644
--- a/components/image_fetcher/core/image_fetcher_impl.cc
+++ b/components/image_fetcher/core/image_fetcher_impl.cc
@@ -7,6 +7,7 @@
 #include <string>
 
 #include "base/bind.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "net/base/load_flags.h"
 #include "net/url_request/url_request_context_getter.h"
 
@@ -15,25 +16,17 @@
 ImageFetcherImpl::ImageFetcherImpl(
     std::unique_ptr<ImageDecoder> image_decoder,
     net::URLRequestContextGetter* url_request_context)
-    : delegate_(nullptr),
-      url_request_context_(url_request_context),
+    : url_request_context_(url_request_context),
       image_decoder_(std::move(image_decoder)),
       image_data_fetcher_(new ImageDataFetcher(url_request_context_.get())) {}
 
 ImageFetcherImpl::~ImageFetcherImpl() {}
 
 ImageFetcherImpl::ImageRequest::ImageRequest() {}
-
-ImageFetcherImpl::ImageRequest::ImageRequest(const ImageRequest& other) =
-    default;
+ImageFetcherImpl::ImageRequest::ImageRequest(ImageRequest&& other) = default;
 
 ImageFetcherImpl::ImageRequest::~ImageRequest() {}
 
-void ImageFetcherImpl::SetImageFetcherDelegate(ImageFetcherDelegate* delegate) {
-  DCHECK(delegate);
-  delegate_ = delegate;
-}
-
 void ImageFetcherImpl::SetDataUseServiceName(
     DataUseServiceName data_use_service_name) {
   image_data_fetcher_->SetDataUseServiceName(data_use_service_name);
@@ -48,10 +41,11 @@
   image_data_fetcher_->SetImageDownloadLimit(max_download_bytes);
 }
 
-void ImageFetcherImpl::StartOrQueueNetworkRequest(
+void ImageFetcherImpl::FetchImageAndData(
     const std::string& id,
     const GURL& image_url,
-    const ImageFetcherCallback& callback,
+    ImageDataFetcherCallback image_data_callback,
+    ImageFetcherCallback image_callback,
     const net::NetworkTrafficAnnotationTag& traffic_annotation) {
   // Before starting to fetch the image. Look for a request in progress for
   // |image_url|, and queue if appropriate.
@@ -59,8 +53,13 @@
   if (it == pending_net_requests_.end()) {
     ImageRequest request;
     request.id = id;
-    request.callbacks.push_back(callback);
-    pending_net_requests_[image_url].swap(&request);
+    if (image_callback) {
+      request.image_callbacks.push_back(std::move(image_callback));
+    }
+    if (image_data_callback) {
+      request.image_data_callbacks.push_back(std::move(image_data_callback));
+    }
+    pending_net_requests_.emplace(image_url, std::move(request));
 
     image_data_fetcher_->FetchImageData(
         image_url,
@@ -68,27 +67,52 @@
                    image_url),
         traffic_annotation);
   } else {
+    ImageRequest* request = &it->second;
     // Request in progress. Register as an interested callback.
     // TODO(treib,markusheintz): We're not guaranteed that the ID also matches.
     //                           We probably have to store them all.
-    it->second.callbacks.push_back(callback);
+    if (image_callback) {
+      request->image_callbacks.push_back(std::move(image_callback));
+    }
+    // Call callback if data is already fetched, otherwise register it for
+    // later.
+    if (image_data_callback) {
+      if (!request->image_data.empty()) {
+        base::ThreadTaskRunnerHandle::Get()->PostTask(
+            FROM_HERE,
+            base::BindOnce(std::move(image_data_callback), request->image_data,
+                           request->request_metadata));
+      } else {
+        request->image_data_callbacks.push_back(std::move(image_data_callback));
+      }
+    }
   }
 }
 
 void ImageFetcherImpl::OnImageURLFetched(const GURL& image_url,
                                          const std::string& image_data,
                                          const RequestMetadata& metadata) {
-  // Inform the ImageFetcherDelegate.
-  if (delegate_) {
-    auto it = pending_net_requests_.find(image_url);
-    DCHECK(it != pending_net_requests_.end());
-    delegate_->OnImageDataFetched(it->second.id, image_data);
+  auto it = pending_net_requests_.find(image_url);
+  DCHECK(it != pending_net_requests_.end());
+  ImageRequest* request = &it->second;
+  DCHECK(request->image_data.empty());
+  DCHECK_EQ(RequestMetadata::RESPONSE_CODE_INVALID,
+            request->request_metadata.http_response_code);
+  request->image_data = image_data;
+  request->request_metadata = metadata;
+  for (auto& callback : request->image_data_callbacks) {
+    std::move(callback).Run(request->image_data, request->request_metadata);
   }
+  request->image_data_callbacks.clear();
 
-  image_decoder_->DecodeImage(
-      image_data, desired_image_frame_size_,
-      base::Bind(&ImageFetcherImpl::OnImageDecoded, base::Unretained(this),
-                 image_url, metadata));
+  if (!request->image_callbacks.empty()) {
+    image_decoder_->DecodeImage(
+        image_data, desired_image_frame_size_,
+        base::BindRepeating(&ImageFetcherImpl::OnImageDecoded,
+                            base::Unretained(this), image_url, metadata));
+  } else {
+    pending_net_requests_.erase(it);
+  }
 }
 
 void ImageFetcherImpl::OnImageDecoded(const GURL& image_url,
@@ -99,18 +123,13 @@
   DCHECK(image_iter != pending_net_requests_.end());
   ImageRequest* request = &image_iter->second;
 
-  // Run all callbacks
-  for (const auto& callback : request->callbacks) {
-    if (callback)
-      callback.Run(request->id, image, metadata);
-  }
-
-  // Inform the ImageFetcherDelegate.
-  if (delegate_) {
-    delegate_->OnImageFetched(request->id, image);
+  // Run all image callbacks.
+  for (auto& callback : request->image_callbacks) {
+    std::move(callback).Run(request->id, image, metadata);
   }
 
   // Erase the completed ImageRequest.
+  DCHECK(request->image_data_callbacks.empty());
   pending_net_requests_.erase(image_iter);
 }
 
diff --git a/components/image_fetcher/core/image_fetcher_impl.h b/components/image_fetcher/core/image_fetcher_impl.h
index d71ce20..05c0b468 100644
--- a/components/image_fetcher/core/image_fetcher_impl.h
+++ b/components/image_fetcher/core/image_fetcher_impl.h
@@ -37,11 +37,6 @@
                    net::URLRequestContextGetter* url_request_context);
   ~ImageFetcherImpl() override;
 
-  // Sets the |delegate| of the ImageFetcherImpl. The |delegate| has to be alive
-  // during the lifetime of the ImageFetcherImpl object. It is the caller's
-  // responsibility to ensure this.
-  void SetImageFetcherDelegate(ImageFetcherDelegate* delegate) override;
-
   // Sets a service name against which to track data usage.
   void SetDataUseServiceName(DataUseServiceName data_use_service_name) override;
 
@@ -50,10 +45,11 @@
   void SetImageDownloadLimit(
       base::Optional<int64_t> max_download_bytes) override;
 
-  void StartOrQueueNetworkRequest(
+  void FetchImageAndData(
       const std::string& id,
       const GURL& image_url,
-      const ImageFetcherCallback& callback,
+      ImageDataFetcherCallback image_data_callback,
+      ImageFetcherCallback image_callback,
       const net::NetworkTrafficAnnotationTag& traffic_annotation) override;
 
   ImageDecoder* GetImageDecoder() override;
@@ -62,21 +58,22 @@
   // State related to an image fetch (id, pending callbacks).
   struct ImageRequest {
     ImageRequest();
-    ImageRequest(const ImageRequest& other);
     ~ImageRequest();
-
-    void swap(ImageRequest* other) {
-      std::swap(id, other->id);
-      std::swap(callbacks, other->callbacks);
-    }
+    ImageRequest(ImageRequest&& other);
 
     std::string id;
+    // These have the default value if the image data has not yet been fetched.
+    RequestMetadata request_metadata;
+    std::string image_data;
     // Queue for pending callbacks, which may accumulate while the request is in
     // flight.
-    std::vector<ImageFetcherCallback> callbacks;
+    std::vector<ImageFetcherCallback> image_callbacks;
+    std::vector<ImageDataFetcherCallback> image_data_callbacks;
+
+    DISALLOW_COPY_AND_ASSIGN(ImageRequest);
   };
 
-  using ImageRequestMap = std::map<const GURL, ImageRequest>;
+  using ImageRequestMap = std::map<GURL, ImageRequest>;
 
   // Processes image URL fetched events. This is the continuation method used
   // for creating callbacks that are passed to the ImageDataFetcher.
@@ -90,8 +87,6 @@
                       const RequestMetadata& metadata,
                       const gfx::Image& image);
 
-  ImageFetcherDelegate* delegate_;
-
   gfx::Size desired_image_frame_size_;
 
   scoped_refptr<net::URLRequestContextGetter> url_request_context_;
diff --git a/components/image_fetcher/core/mock_image_fetcher.cc b/components/image_fetcher/core/mock_image_fetcher.cc
new file mode 100644
index 0000000..644a4fa
--- /dev/null
+++ b/components/image_fetcher/core/mock_image_fetcher.cc
@@ -0,0 +1,24 @@
+// 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 "components/image_fetcher/core/mock_image_fetcher.h"
+
+#include "ui/gfx/geometry/size.h"
+
+namespace image_fetcher {
+
+MockImageFetcher::MockImageFetcher() {}
+MockImageFetcher::~MockImageFetcher() {}
+
+void MockImageFetcher::FetchImageAndData(
+    const std::string& id,
+    const GURL& image_url,
+    ImageDataFetcherCallback image_data_callback,
+    ImageFetcherCallback image_callback,
+    const net::NetworkTrafficAnnotationTag& traffic_annotation) {
+  FetchImageAndData_(id, image_url, &image_data_callback, &image_callback,
+                     traffic_annotation);
+}
+
+}  // namespace image_fetcher
diff --git a/components/image_fetcher/core/mock_image_fetcher.h b/components/image_fetcher/core/mock_image_fetcher.h
new file mode 100644
index 0000000..b32deb8
--- /dev/null
+++ b/components/image_fetcher/core/mock_image_fetcher.h
@@ -0,0 +1,36 @@
+// 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 COMPONENTS_IMAGE_FETCHER_CORE_MOCK_IMAGE_FETCHER_H_
+#define COMPONENTS_IMAGE_FETCHER_CORE_MOCK_IMAGE_FETCHER_H_
+
+#include "components/image_fetcher/core/image_fetcher.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace image_fetcher {
+class MockImageFetcher : public ImageFetcher {
+ public:
+  MockImageFetcher();
+  ~MockImageFetcher() override;
+  MOCK_METHOD1(SetDataUseServiceName, void(DataUseServiceName));
+  MOCK_METHOD1(SetImageDownloadLimit,
+               void(base::Optional<int64_t> max_download_bytes));
+  MOCK_METHOD1(SetDesiredImageFrameSize, void(const gfx::Size&));
+  MOCK_METHOD5(FetchImageAndData_,
+               void(const std::string&,
+                    const GURL&,
+                    ImageDataFetcherCallback*,
+                    ImageFetcherCallback*,
+                    const net::NetworkTrafficAnnotationTag&));
+  void FetchImageAndData(
+      const std::string& id,
+      const GURL& image_url,
+      ImageDataFetcherCallback image_data_callback,
+      ImageFetcherCallback image_callback,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation) override;
+  MOCK_METHOD0(GetImageDecoder, image_fetcher::ImageDecoder*());
+};
+}  // namespace image_fetcher
+
+#endif  // COMPONENTS_IMAGE_FETCHER_CORE_MOCK_IMAGE_FETCHER_H_
diff --git a/components/invalidation/impl/BUILD.gn b/components/invalidation/impl/BUILD.gn
index d69b8c4..b56a91a 100644
--- a/components/invalidation/impl/BUILD.gn
+++ b/components/invalidation/impl/BUILD.gn
@@ -207,10 +207,10 @@
       "//base:base_java",
       "//components/signin/core/browser/android:java",
       "//components/sync/android:sync_java",
+      "//third_party/android_protobuf:protobuf_nano_javalib",
       "//third_party/cacheinvalidation:cacheinvalidation_javalib",
       "//third_party/cacheinvalidation:cacheinvalidation_proto_java",
       "//third_party/jsr-305:jsr_305_javalib",
-      "//third_party/protobuf:protobuf_lite_javalib",
     ]
     java_files = [
       "android/java/src/org/chromium/components/invalidation/InvalidationClientService.java",
@@ -223,7 +223,6 @@
     sources = [
       "$proto_path/serialized_invalidation.proto",
     ]
-    generate_lite = true
   }
   android_library("javatests") {
     testonly = true
diff --git a/components/invalidation/impl/android/java/src/org/chromium/components/invalidation/PendingInvalidation.java b/components/invalidation/impl/android/java/src/org/chromium/components/invalidation/PendingInvalidation.java
index 5cba256..1ec8c83 100644
--- a/components/invalidation/impl/android/java/src/org/chromium/components/invalidation/PendingInvalidation.java
+++ b/components/invalidation/impl/android/java/src/org/chromium/components/invalidation/PendingInvalidation.java
@@ -7,6 +7,8 @@
 import android.os.Bundle;
 import android.util.Base64;
 
+import com.google.protobuf.nano.MessageNano;
+
 import org.chromium.base.Log;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.components.invalidation.SerializedInvalidation.Invalidation;
@@ -77,13 +79,12 @@
      */
     public String encodeToString() {
         assert mObjectSource != 0;
-        Invalidation invalidation = Invalidation.newBuilder()
-                                            .setObjectSource(mObjectSource)
-                                            .setObjectId(mObjectId)
-                                            .setVersion(mVersion)
-                                            .setPayload(mPayload)
-                                            .build();
-        return Base64.encodeToString(invalidation.toByteArray(), Base64.DEFAULT);
+        Invalidation invalidation = new Invalidation();
+        invalidation.objectSource = mObjectSource;
+        invalidation.objectId = mObjectId;
+        invalidation.version = mVersion;
+        invalidation.payload = mPayload;
+        return Base64.encodeToString(MessageNano.toByteArray(invalidation), Base64.DEFAULT);
     }
 
     /**
@@ -94,10 +95,8 @@
     public static Bundle decodeToBundle(String encoded) {
         Invalidation invalidation = decodeToInvalidation(encoded);
         if (invalidation == null) return null;
-        return createBundle(invalidation.hasObjectId() ? invalidation.getObjectId() : null,
-                invalidation.getObjectSource(),
-                invalidation.hasVersion() ? invalidation.getVersion() : 0L,
-                invalidation.hasPayload() ? invalidation.getPayload() : null);
+        return createBundle(invalidation.objectId, invalidation.objectSource,
+                invalidation.version != null ? invalidation.version : 0L, invalidation.payload);
     }
 
     /**
@@ -108,10 +107,8 @@
     public static PendingInvalidation decodeToPendingInvalidation(String encoded) {
         Invalidation invalidation = decodeToInvalidation(encoded);
         if (invalidation == null) return null;
-        return new PendingInvalidation(
-                invalidation.hasObjectId() ? invalidation.getObjectId() : null,
-                invalidation.getObjectSource(), invalidation.getVersion(),
-                invalidation.hasPayload() ? invalidation.getPayload() : null);
+        return new PendingInvalidation(invalidation.objectId, invalidation.objectSource,
+                invalidation.version, invalidation.payload);
     }
 
     @Nullable
@@ -120,13 +117,13 @@
         byte[] decoded = Base64.decode(encoded, Base64.DEFAULT);
         Invalidation invalidation;
         try {
-            invalidation = Invalidation.parseFrom(decoded);
+            invalidation = MessageNano.mergeFrom(new Invalidation(), decoded);
         } catch (IOException e) {
             Log.e(TAG, "Could not parse the serialized invalidations.", e);
             return null;
         }
         assert invalidation != null;
-        if (!invalidation.hasObjectSource() || invalidation.getObjectSource() == 0) return null;
+        if (invalidation.objectSource == null || invalidation.objectSource == 0) return null;
         return invalidation;
     }
 
diff --git a/components/invalidation/impl/android/proto/serialized_invalidation.proto b/components/invalidation/impl/android/proto/serialized_invalidation.proto
index f806de7..c191f7e 100644
--- a/components/invalidation/impl/android/proto/serialized_invalidation.proto
+++ b/components/invalidation/impl/android/proto/serialized_invalidation.proto
@@ -7,8 +7,6 @@
 //
 syntax = "proto2";
 package org.chromium.components.invalidation;
-
-// TODO(jkrcal): Remove when protobuf 4.0 is out, https://crbug.com/800281.
 option optimize_for = LITE_RUNTIME;
 
 message Invalidation {
diff --git a/components/metrics_services_manager/metrics_services_manager.cc b/components/metrics_services_manager/metrics_services_manager.cc
index 19b8b1e..fedc69a7 100644
--- a/components/metrics_services_manager/metrics_services_manager.cc
+++ b/components/metrics_services_manager/metrics_services_manager.cc
@@ -143,7 +143,7 @@
       metrics_service_client_->IsHistorySyncEnabledOnAllProfiles();
   bool is_incognito = client_->IsIncognitoSessionActive();
 
-  if (consent_given_ && sync_enabled & !is_incognito) {
+  if (consent_given_ && sync_enabled && !is_incognito) {
     ukm->EnableRecording(
         metrics_service_client_->IsExtensionSyncEnabledOnAllProfiles());
     if (may_upload_)
diff --git a/components/ntp_snippets/BUILD.gn b/components/ntp_snippets/BUILD.gn
index eb88be9..2712507 100644
--- a/components/ntp_snippets/BUILD.gn
+++ b/components/ntp_snippets/BUILD.gn
@@ -227,6 +227,7 @@
     "//components/gcm_driver",
     "//components/gcm_driver/instance_id",
     "//components/image_fetcher/core",
+    "//components/image_fetcher/core:test_support",
     "//components/leveldb_proto:test_support",
     "//components/ntp_snippets/remote/proto",
     "//components/offline_pages/core",
diff --git a/components/ntp_snippets/OWNERS b/components/ntp_snippets/OWNERS
index 3ac3779..8cff5d5 100644
--- a/components/ntp_snippets/OWNERS
+++ b/components/ntp_snippets/OWNERS
@@ -1,5 +1,7 @@
 bauerb@chromium.org
+fgorski@chrmoium.org
 jkrcal@chromium.org
+pnoland@chromium.org
 treib@chromium.org
 tschumann@chromium.org
 
diff --git a/components/ntp_snippets/callbacks.h b/components/ntp_snippets/callbacks.h
index 2d9efcec..16d417a 100644
--- a/components/ntp_snippets/callbacks.h
+++ b/components/ntp_snippets/callbacks.h
@@ -29,6 +29,9 @@
 // ContentSuggestionsProvider.
 using ImageFetchedCallback = base::OnceCallback<void(const gfx::Image&)>;
 
+using ImageDataFetchedCallback =
+    base::OnceCallback<void(const std::string& image_data)>;
+
 // Returns the list of dismissed suggestions when invoked. Currently only used
 // for debugging methods to check the internal state of a provider.
 using DismissedSuggestionsCallback = base::OnceCallback<void(
diff --git a/components/ntp_snippets/contextual/contextual_content_suggestions_service.cc b/components/ntp_snippets/contextual/contextual_content_suggestions_service.cc
index dd12e915..a5f0887 100644
--- a/components/ntp_snippets/contextual/contextual_content_suggestions_service.cc
+++ b/components/ntp_snippets/contextual/contextual_content_suggestions_service.cc
@@ -50,6 +50,7 @@
   if (image_url_iterator != image_url_by_id_.end()) {
     GURL image_url = image_url_iterator->second;
     image_fetcher_->FetchSuggestionImage(suggestion_id, image_url,
+                                         ImageDataFetchedCallback(),
                                          std::move(callback));
   } else {
     DVLOG(1) << "FetchContextualSuggestionImage unknown image"
diff --git a/components/ntp_snippets/contextual/contextual_content_suggestions_service_unittest.cc b/components/ntp_snippets/contextual/contextual_content_suggestions_service_unittest.cc
index 9c3e7dc..d8ab9a1f 100644
--- a/components/ntp_snippets/contextual/contextual_content_suggestions_service_unittest.cc
+++ b/components/ntp_snippets/contextual/contextual_content_suggestions_service_unittest.cc
@@ -83,6 +83,7 @@
 
   void FetchSuggestionImage(const ContentSuggestion::ID&,
                             const GURL& image_url,
+                            ImageDataFetchedCallback image_data_callback,
                             ImageFetchedCallback callback) override {
     gfx::Image image;
     if (image_url.is_valid()) {
diff --git a/components/ntp_snippets/reading_list/reading_list_suggestions_provider.cc b/components/ntp_snippets/reading_list/reading_list_suggestions_provider.cc
index b8a12e1..162b4f0 100644
--- a/components/ntp_snippets/reading_list/reading_list_suggestions_provider.cc
+++ b/components/ntp_snippets/reading_list/reading_list_suggestions_provider.cc
@@ -29,7 +29,7 @@
 bool CompareEntries(const ReadingListEntry* lhs, const ReadingListEntry* rhs) {
   return lhs->UpdateTime() > rhs->UpdateTime();
 }
-}
+}  // namespace
 
 ReadingListSuggestionsProvider::ReadingListSuggestionsProvider(
     ContentSuggestionsProvider::Observer* observer,
diff --git a/components/ntp_snippets/remote/cached_image_fetcher.cc b/components/ntp_snippets/remote/cached_image_fetcher.cc
index b9995faa..eaeda716 100644
--- a/components/ntp_snippets/remote/cached_image_fetcher.cc
+++ b/components/ntp_snippets/remote/cached_image_fetcher.cc
@@ -3,9 +3,10 @@
 // found in the LICENSE file.
 
 #include "components/ntp_snippets/remote/cached_image_fetcher.h"
-
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/location.h"
+#include "base/logging.h"
 #include "components/image_fetcher/core/image_decoder.h"
 #include "components/image_fetcher/core/image_fetcher.h"
 #include "components/ntp_snippets/remote/remote_suggestions_database.h"
@@ -14,105 +15,9 @@
 #include "ui/gfx/image/image.h"
 
 namespace ntp_snippets {
-
-CachedImageFetcher::CachedImageFetcher(
-    std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
-    PrefService* pref_service,
-    RemoteSuggestionsDatabase* database)
-    : image_fetcher_(std::move(image_fetcher)),
-      database_(database),
-      thumbnail_requests_throttler_(
-          pref_service,
-          RequestThrottler::RequestType::CONTENT_SUGGESTION_THUMBNAIL) {
-  // |image_fetcher_| can be null in tests.
-  if (image_fetcher_) {
-    image_fetcher_->SetImageFetcherDelegate(this);
-    image_fetcher_->SetDataUseServiceName(
-        data_use_measurement::DataUseUserData::NTP_SNIPPETS_THUMBNAILS);
-  }
-}
-
-CachedImageFetcher::~CachedImageFetcher() {}
-
-void CachedImageFetcher::FetchSuggestionImage(
-    const ContentSuggestion::ID& suggestion_id,
-    const GURL& url,
-    ImageFetchedCallback callback) {
-  database_->LoadImage(
-      suggestion_id.id_within_category(),
-      base::Bind(&CachedImageFetcher::OnImageFetchedFromDatabase,
-                 base::Unretained(this), base::Passed(std::move(callback)),
-                 suggestion_id, url));
-}
-
-// This function gets only called for caching the image data received from the
-// network. The actual decoding is done in OnImageDecodedFromDatabase().
-void CachedImageFetcher::OnImageDataFetched(
-    const std::string& id_within_category,
-    const std::string& image_data) {
-  if (image_data.empty()) {
-    return;
-  }
-  database_->SaveImage(id_within_category, image_data);
-}
-
-void CachedImageFetcher::OnImageDecodingDone(
-    ImageFetchedCallback callback,
-    const std::string& id_within_category,
-    const gfx::Image& image,
-    const image_fetcher::RequestMetadata& metadata) {
-  std::move(callback).Run(image);
-}
-
-void CachedImageFetcher::OnImageFetchedFromDatabase(
-    ImageFetchedCallback callback,
-    const ContentSuggestion::ID& suggestion_id,
-    const GURL& url,
-    std::string data) {  // SnippetImageCallback requires by-value.
-  // The image decoder is null in tests.
-  if (image_fetcher_->GetImageDecoder() && !data.empty()) {
-    image_fetcher_->GetImageDecoder()->DecodeImage(
-        data,
-        // We're not dealing with multi-frame images.
-        /*desired_image_frame_size=*/gfx::Size(),
-        base::Bind(&CachedImageFetcher::OnImageDecodedFromDatabase,
-                   base::Unretained(this), base::Passed(std::move(callback)),
-                   suggestion_id, url));
-    return;
-  }
-  // Fetching from the DB failed; start a network fetch.
-  FetchImageFromNetwork(suggestion_id, url, std::move(callback));
-}
-
-void CachedImageFetcher::OnImageDecodedFromDatabase(
-    ImageFetchedCallback callback,
-    const ContentSuggestion::ID& suggestion_id,
-    const GURL& url,
-    const gfx::Image& image) {
-  if (!image.IsEmpty()) {
-    std::move(callback).Run(image);
-    return;
-  }
-  // If decoding the image failed, delete the DB entry.
-  database_->DeleteImage(suggestion_id.id_within_category());
-  FetchImageFromNetwork(suggestion_id, url, std::move(callback));
-}
-
-void CachedImageFetcher::FetchImageFromNetwork(
-    const ContentSuggestion::ID& suggestion_id,
-    const GURL& url,
-    ImageFetchedCallback callback) {
-  if (url.is_empty() || !thumbnail_requests_throttler_.DemandQuotaForRequest(
-                            /*interactive_request=*/true)) {
-    // Return an empty image. Directly, this is never synchronous with the
-    // original FetchSuggestionImage() call - an asynchronous database query has
-    // happened in the meantime.
-    std::move(callback).Run(gfx::Image());
-    return;
-  }
-
-  net::NetworkTrafficAnnotationTag traffic_annotation =
-      net::DefineNetworkTrafficAnnotation("remote_suggestions_provider", R"(
+namespace {
+constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
+    net::DefineNetworkTrafficAnnotation("remote_suggestions_provider", R"(
         semantics {
           sender: "Content Suggestion Thumbnail Fetch"
           description:
@@ -134,11 +39,135 @@
           }
         }
       })");
-  image_fetcher_->StartOrQueueNetworkRequest(
+}  // namespace
+
+CachedImageFetcher::CachedImageFetcher(
+    std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
+    PrefService* pref_service,
+    RemoteSuggestionsDatabase* database)
+    : image_fetcher_(std::move(image_fetcher)),
+      database_(database),
+      thumbnail_requests_throttler_(
+          pref_service,
+          RequestThrottler::RequestType::CONTENT_SUGGESTION_THUMBNAIL) {
+  // |image_fetcher_| can be null in tests.
+  if (image_fetcher_) {
+    image_fetcher_->SetDataUseServiceName(
+        data_use_measurement::DataUseUserData::NTP_SNIPPETS_THUMBNAILS);
+  }
+}
+
+CachedImageFetcher::~CachedImageFetcher() {}
+
+void CachedImageFetcher::FetchSuggestionImage(
+    const ContentSuggestion::ID& suggestion_id,
+    const GURL& url,
+    ImageDataFetchedCallback image_data_callback,
+    ImageFetchedCallback image_callback) {
+  database_->LoadImage(
+      suggestion_id.id_within_category(),
+      base::BindOnce(&CachedImageFetcher::OnImageFetchedFromDatabase,
+                     base::Unretained(this), std::move(image_data_callback),
+                     std::move(image_callback), suggestion_id, url));
+}
+
+void CachedImageFetcher::OnImageDecodingDone(
+    ImageFetchedCallback callback,
+    const std::string& id_within_category,
+    const gfx::Image& image,
+    const image_fetcher::RequestMetadata& metadata) {
+  std::move(callback).Run(image);
+}
+
+void CachedImageFetcher::OnImageFetchedFromDatabase(
+    ImageDataFetchedCallback image_data_callback,
+    ImageFetchedCallback image_callback,
+    const ContentSuggestion::ID& suggestion_id,
+    const GURL& url,
+    std::string data) {  // SnippetImageCallback requires by-value.
+  if (data.empty()) {
+    // Fetching from the DB failed; start a network fetch.
+    FetchImageFromNetwork(suggestion_id, url, std::move(image_data_callback),
+                          std::move(image_callback));
+    return;
+  }
+
+  if (image_data_callback) {
+    std::move(image_data_callback).Run(data);
+  }
+
+  if (image_callback) {
+    image_fetcher_->GetImageDecoder()->DecodeImage(
+        data,
+        // We're not dealing with multi-frame images.
+        /*desired_image_frame_size=*/gfx::Size(),
+        base::Bind(&CachedImageFetcher::OnImageDecodedFromDatabase,
+                   base::Unretained(this),
+                   base::Passed(std::move(image_callback)), suggestion_id,
+                   url));
+  }
+}
+
+void CachedImageFetcher::OnImageDecodedFromDatabase(
+    ImageFetchedCallback callback,
+    const ContentSuggestion::ID& suggestion_id,
+    const GURL& url,
+    const gfx::Image& image) {
+  if (!image.IsEmpty()) {
+    std::move(callback).Run(image);
+    return;
+  }
+  // If decoding the image failed, delete the DB entry.
+  database_->DeleteImage(suggestion_id.id_within_category());
+  FetchImageFromNetwork(suggestion_id, url, ImageDataFetchedCallback(),
+                        std::move(callback));
+}
+
+void CachedImageFetcher::FetchImageFromNetwork(
+    const ContentSuggestion::ID& suggestion_id,
+    const GURL& url,
+    ImageDataFetchedCallback image_data_callback,
+    ImageFetchedCallback image_callback) {
+  if (url.is_empty() || !thumbnail_requests_throttler_.DemandQuotaForRequest(
+                            /*interactive_request=*/true)) {
+    // Return an empty image. Directly, this is never synchronous with the
+    // original FetchSuggestionImage() call - an asynchronous database query has
+    // happened in the meantime.
+    if (image_data_callback) {
+      std::move(image_data_callback).Run(std::string());
+    }
+    if (image_callback) {
+      std::move(image_callback).Run(gfx::Image());
+    }
+    return;
+  }
+  // Image decoding callback only set when requested.
+  image_fetcher::ImageFetcher::ImageFetcherCallback decode_callback;
+  if (image_callback) {
+    decode_callback = base::BindOnce(&CachedImageFetcher::OnImageDecodingDone,
+                                     base::Unretained(this),
+                                     base::Passed(std::move(image_callback)));
+  }
+
+  image_fetcher_->FetchImageAndData(
       suggestion_id.id_within_category(), url,
-      base::Bind(&CachedImageFetcher::OnImageDecodingDone,
-                 base::Unretained(this), base::Passed(std::move(callback))),
-      traffic_annotation);
+      base::BindOnce(&CachedImageFetcher::SaveImageAndInvokeDataCallback,
+                     base::Unretained(this), suggestion_id.id_within_category(),
+                     std::move(image_data_callback)),
+      std::move(decode_callback), kTrafficAnnotation);
+}
+
+void CachedImageFetcher::SaveImageAndInvokeDataCallback(
+    const std::string& id_within_category,
+    ImageDataFetchedCallback callback,
+    const std::string& image_data,
+    const image_fetcher::RequestMetadata& request_metadata) {
+  if (!image_data.empty()) {
+    database_->SaveImage(id_within_category, image_data);
+  }
+  if (callback) {
+    std::move(callback).Run(image_data);
+  }
 }
 
 }  // namespace ntp_snippets
diff --git a/components/ntp_snippets/remote/cached_image_fetcher.h b/components/ntp_snippets/remote/cached_image_fetcher.h
index 51eef498..d1e1c6d5 100644
--- a/components/ntp_snippets/remote/cached_image_fetcher.h
+++ b/components/ntp_snippets/remote/cached_image_fetcher.h
@@ -13,7 +13,6 @@
 #include "base/callback_forward.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
-#include "components/image_fetcher/core/image_fetcher_delegate.h"
 #include "components/ntp_snippets/callbacks.h"
 #include "components/ntp_snippets/content_suggestion.h"
 #include "components/ntp_snippets/remote/request_throttler.h"
@@ -35,43 +34,55 @@
 
 // CachedImageFetcher takes care of fetching images from the network and caching
 // them in the database.
-class CachedImageFetcher : public image_fetcher::ImageFetcherDelegate {
+class CachedImageFetcher {
  public:
   // |pref_service| and |database| need to outlive the created image fetcher
   // instance.
   CachedImageFetcher(std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
                      PrefService* pref_service,
                      RemoteSuggestionsDatabase* database);
-  ~CachedImageFetcher() override;
+  virtual ~CachedImageFetcher();
 
   // Fetches the image for a suggestion. The fetcher will first issue a lookup
   // to the underlying cache with a fallback to the network.
-  virtual void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
-                                    const GURL& image_url,
-                                    ImageFetchedCallback callback);
+  virtual void FetchSuggestionImage(
+      const ContentSuggestion::ID& suggestion_id,
+      const GURL& image_url,
+      ImageDataFetchedCallback image_data_callback,
+      ImageFetchedCallback image_callback);
 
  private:
-  // image_fetcher::ImageFetcherDelegate implementation.
   void OnImageDataFetched(const std::string& id_within_category,
-                          const std::string& image_data) override;
+                          const std::string& image_data,
+                          const image_fetcher::RequestMetadata& metadata);
 
   void OnImageDecodingDone(ImageFetchedCallback callback,
                            const std::string& id_within_category,
                            const gfx::Image& image,
                            const image_fetcher::RequestMetadata& metadata);
+
   void OnImageFetchedFromDatabase(
-      ImageFetchedCallback callback,
+      ImageDataFetchedCallback image_data_callback,
+      ImageFetchedCallback image_callback,
       const ContentSuggestion::ID& suggestion_id,
       const GURL& image_url,
       // SnippetImageCallback requires by-value (not const ref).
       std::string data);
+
   void OnImageDecodedFromDatabase(ImageFetchedCallback callback,
                                   const ContentSuggestion::ID& suggestion_id,
                                   const GURL& url,
                                   const gfx::Image& image);
+
   void FetchImageFromNetwork(const ContentSuggestion::ID& suggestion_id,
                              const GURL& url,
-                             ImageFetchedCallback callback);
+                             ImageDataFetchedCallback image_data_callback,
+                             ImageFetchedCallback image_callback);
+  void SaveImageAndInvokeDataCallback(
+      const std::string& id_within_category,
+      ImageDataFetchedCallback callback,
+      const std::string& image_data,
+      const image_fetcher::RequestMetadata& request_metadata);
 
   std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher_;
   RemoteSuggestionsDatabase* database_;
diff --git a/components/ntp_snippets/remote/cached_image_fetcher_unittest.cc b/components/ntp_snippets/remote/cached_image_fetcher_unittest.cc
index 48e47227..2c809c3 100644
--- a/components/ntp_snippets/remote/cached_image_fetcher_unittest.cc
+++ b/components/ntp_snippets/remote/cached_image_fetcher_unittest.cc
@@ -37,6 +37,9 @@
 const char kImageData[] = "data";
 const char kImageURL[] = "http://image.test/test.png";
 const char kSnippetID[] = "http://localhost";
+const ContentSuggestion::ID kSuggestionID(
+    Category::FromKnownCategory(KnownCategories::ARTICLES),
+    kSnippetID);
 
 // Always decodes a valid image for all non-empty input.
 class FakeImageDecoder : public image_fetcher::ImageDecoder {
@@ -45,17 +48,31 @@
       const std::string& image_data,
       const gfx::Size& desired_image_frame_size,
       const image_fetcher::ImageDecodedCallback& callback) override {
+    ASSERT_TRUE(enabled_);
     gfx::Image image;
     if (!image_data.empty()) {
+      ASSERT_EQ(kImageData, image_data);
       image = gfx::test::CreateImage();
     }
-    callback.Run(image);
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindRepeating(callback, image));
   }
+  void SetEnabled(bool enabled) { enabled_ = enabled; }
+
+ private:
+  bool enabled_ = true;
 };
 
+enum TestType {
+  kImageCallback,
+  kImageDataCallback,
+  kBothCallbacks,
+};
 }  // namespace
 
-class CachedImageFetcherTest : public testing::Test {
+// This test is parameterized to run all tests in the three configurations:
+// both callbacks used, only image_callback used, only image_data_callback used.
+class CachedImageFetcherTest : public testing::TestWithParam<TestType> {
  public:
   CachedImageFetcherTest() : fake_url_fetcher_factory_(nullptr) {
     EXPECT_TRUE(database_dir_.CreateUniqueTempDir());
@@ -85,11 +102,35 @@
     RunUntilIdle();
   }
 
-  void FetchImage(ImageFetchedCallback callback) {
-    ContentSuggestion::ID content_suggestion_id(
-        Category::FromKnownCategory(KnownCategories::ARTICLES), kSnippetID);
-    cached_image_fetcher_->FetchSuggestionImage(
-        content_suggestion_id, GURL(kImageURL), std::move(callback));
+  void Fetch(std::string expected_image_data, bool expect_image) {
+    fake_image_decoder()->SetEnabled(GetParam() != kImageDataCallback);
+    base::MockCallback<ImageFetchedCallback> image_callback;
+    base::MockCallback<ImageDataFetchedCallback> image_data_callback;
+    switch (GetParam()) {
+      case kImageCallback: {
+        EXPECT_CALL(image_callback,
+                    Run(Property(&gfx::Image::IsEmpty, Eq(!expect_image))));
+        cached_image_fetcher()->FetchSuggestionImage(
+            kSuggestionID, GURL(kImageURL), ImageDataFetchedCallback(),
+            image_callback.Get());
+
+      } break;
+      case kImageDataCallback: {
+        EXPECT_CALL(image_data_callback, Run(expected_image_data));
+        cached_image_fetcher()->FetchSuggestionImage(
+            kSuggestionID, GURL(kImageURL), image_data_callback.Get(),
+            ImageFetchedCallback());
+      } break;
+      case kBothCallbacks: {
+        EXPECT_CALL(image_data_callback, Run(expected_image_data));
+        EXPECT_CALL(image_callback,
+                    Run(Property(&gfx::Image::IsEmpty, Eq(!expect_image))));
+        cached_image_fetcher()->FetchSuggestionImage(
+            kSuggestionID, GURL(kImageURL), image_data_callback.Get(),
+            image_callback.Get());
+      } break;
+    }
+    RunUntilIdle();
   }
 
   void RunUntilIdle() { scoped_task_environment_.RunUntilIdle(); }
@@ -99,6 +140,9 @@
   net::FakeURLFetcherFactory* fake_url_fetcher_factory() {
     return &fake_url_fetcher_factory_;
   }
+  CachedImageFetcher* cached_image_fetcher() {
+    return cached_image_fetcher_.get();
+  }
 
  private:
   std::unique_ptr<CachedImageFetcher> cached_image_fetcher_;
@@ -113,44 +157,44 @@
   DISALLOW_COPY_AND_ASSIGN(CachedImageFetcherTest);
 };
 
-TEST_F(CachedImageFetcherTest, FetchImageFromCache) {
+TEST_P(CachedImageFetcherTest, FetchImageFromCache) {
   // Save the image in the database.
   database()->SaveImage(kSnippetID, kImageData);
   RunUntilIdle();
 
   // Do not provide any URL responses and expect that the image is fetched (from
   // cache).
-  base::MockCallback<ImageFetchedCallback> mock_image_fetched_callback;
-  EXPECT_CALL(mock_image_fetched_callback,
-              Run(Property(&gfx::Image::IsEmpty, Eq(false))));
-  FetchImage(mock_image_fetched_callback.Get());
-  RunUntilIdle();
+  Fetch(kImageData, true);
 }
 
-TEST_F(CachedImageFetcherTest, FetchImageNotInCache) {
+TEST_P(CachedImageFetcherTest, FetchImagePopulatesCache) {
   // Expect the image to be fetched by URL.
-  fake_url_fetcher_factory()->SetFakeResponse(GURL(kImageURL), kImageData,
-                                              net::HTTP_OK,
-                                              net::URLRequestStatus::SUCCESS);
-  base::MockCallback<ImageFetchedCallback> mock_image_fetched_callback;
-  EXPECT_CALL(mock_image_fetched_callback,
-              Run(Property(&gfx::Image::IsEmpty, Eq(false))));
-  FetchImage(mock_image_fetched_callback.Get());
-  RunUntilIdle();
+  {
+    fake_url_fetcher_factory()->SetFakeResponse(GURL(kImageURL), kImageData,
+                                                net::HTTP_OK,
+                                                net::URLRequestStatus::SUCCESS);
+    Fetch(kImageData, true);
+  }
+  // Fetch again. The cache should be populated, no network request is needed.
+  {
+    fake_url_fetcher_factory()->ClearFakeResponses();
+    Fetch(kImageData, true);
+  }
 }
 
-TEST_F(CachedImageFetcherTest, FetchNonExistingImage) {
+TEST_P(CachedImageFetcherTest, FetchNonExistingImage) {
   const std::string kErrorResponse = "error-response";
   fake_url_fetcher_factory()->SetFakeResponse(GURL(kImageURL), kErrorResponse,
                                               net::HTTP_NOT_FOUND,
                                               net::URLRequestStatus::FAILED);
   // Expect an empty image is fetched if the URL cannot be requested.
-  const std::string kEmptyImageData;
-  base::MockCallback<ImageFetchedCallback> mock_image_fetched_callback;
-  EXPECT_CALL(mock_image_fetched_callback,
-              Run(Property(&gfx::Image::IsEmpty, Eq(true))));
-  FetchImage(mock_image_fetched_callback.Get());
-  RunUntilIdle();
+  Fetch("", false);
 }
 
+INSTANTIATE_TEST_CASE_P(,
+                        CachedImageFetcherTest,
+                        testing::Values(kImageCallback,
+                                        kImageDataCallback,
+                                        kBothCallbacks));
+
 }  // namespace ntp_snippets
diff --git a/components/ntp_snippets/remote/remote_suggestions_database.cc b/components/ntp_snippets/remote/remote_suggestions_database.cc
index 7e317ad9..1da6eb1d 100644
--- a/components/ntp_snippets/remote/remote_suggestions_database.cc
+++ b/components/ntp_snippets/remote/remote_suggestions_database.cc
@@ -91,11 +91,11 @@
   error_callback_ = error_callback;
 }
 
-void RemoteSuggestionsDatabase::LoadSnippets(const SnippetsCallback& callback) {
+void RemoteSuggestionsDatabase::LoadSnippets(SnippetsCallback callback) {
   if (IsInitialized()) {
-    LoadSnippetsImpl(callback);
+    LoadSnippetsImpl(std::move(callback));
   } else {
-    pending_snippets_callbacks_.emplace_back(callback);
+    pending_snippets_callbacks_.emplace_back(std::move(callback));
   }
 }
 
@@ -135,13 +135,12 @@
                  weak_ptr_factory_.GetWeakPtr()));
 }
 
-void RemoteSuggestionsDatabase::LoadImage(
-    const std::string& snippet_id,
-    const SnippetImageCallback& callback) {
+void RemoteSuggestionsDatabase::LoadImage(const std::string& snippet_id,
+                                          SnippetImageCallback callback) {
   if (IsInitialized()) {
-    LoadImageImpl(snippet_id, callback);
+    LoadImageImpl(snippet_id, std::move(callback));
   } else {
-    pending_image_callbacks_.emplace_back(snippet_id, callback);
+    pending_image_callbacks_.emplace_back(snippet_id, std::move(callback));
   }
 }
 
@@ -197,7 +196,7 @@
 }
 
 void RemoteSuggestionsDatabase::OnDatabaseLoaded(
-    const SnippetsCallback& callback,
+    SnippetsCallback callback,
     bool success,
     std::unique_ptr<std::vector<SnippetProto>> entries) {
   if (!success) {
@@ -226,7 +225,7 @@
     }
   }
 
-  callback.Run(std::move(snippets));
+  std::move(callback).Run(std::move(snippets));
 
   // If any of the snippet protos couldn't be converted to actual snippets,
   // clean them up now.
@@ -256,7 +255,7 @@
 }
 
 void RemoteSuggestionsDatabase::OnImageDatabaseLoaded(
-    const SnippetImageCallback& callback,
+    SnippetImageCallback callback,
     bool success,
     std::unique_ptr<SnippetImageProto> entry) {
   if (!success) {
@@ -266,12 +265,12 @@
   }
 
   if (!entry) {
-    callback.Run(std::string());
+    std::move(callback).Run(std::string());
     return;
   }
 
   std::unique_ptr<std::string> data(entry->release_data());
-  callback.Run(std::move(*data));
+  std::move(callback).Run(std::move(*data));
 }
 
 void RemoteSuggestionsDatabase::OnImageDatabaseSaved(bool success) {
@@ -292,23 +291,22 @@
 void RemoteSuggestionsDatabase::ProcessPendingLoads() {
   DCHECK(IsInitialized());
 
-  for (const auto& callback : pending_snippets_callbacks_) {
-    LoadSnippetsImpl(callback);
+  for (auto& callback : pending_snippets_callbacks_) {
+    LoadSnippetsImpl(std::move(callback));
   }
   pending_snippets_callbacks_.clear();
 
-  for (const auto& id_callback : pending_image_callbacks_) {
-    LoadImageImpl(id_callback.first, id_callback.second);
+  for (auto& id_callback : pending_image_callbacks_) {
+    LoadImageImpl(id_callback.first, std::move(id_callback.second));
   }
   pending_image_callbacks_.clear();
 }
 
-void RemoteSuggestionsDatabase::LoadSnippetsImpl(
-    const SnippetsCallback& callback) {
+void RemoteSuggestionsDatabase::LoadSnippetsImpl(SnippetsCallback callback) {
   DCHECK(IsInitialized());
   database_->LoadEntries(
-      base::Bind(&RemoteSuggestionsDatabase::OnDatabaseLoaded,
-                 weak_ptr_factory_.GetWeakPtr(), callback));
+      base::BindOnce(&RemoteSuggestionsDatabase::OnDatabaseLoaded,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
 void RemoteSuggestionsDatabase::SaveSnippetsImpl(
@@ -323,13 +321,13 @@
                  weak_ptr_factory_.GetWeakPtr()));
 }
 
-void RemoteSuggestionsDatabase::LoadImageImpl(
-    const std::string& snippet_id,
-    const SnippetImageCallback& callback) {
+void RemoteSuggestionsDatabase::LoadImageImpl(const std::string& snippet_id,
+                                              SnippetImageCallback callback) {
   DCHECK(IsInitialized());
   image_database_->GetEntry(
-      snippet_id, base::Bind(&RemoteSuggestionsDatabase::OnImageDatabaseLoaded,
-                             weak_ptr_factory_.GetWeakPtr(), callback));
+      snippet_id,
+      base::BindOnce(&RemoteSuggestionsDatabase::OnImageDatabaseLoaded,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
 void RemoteSuggestionsDatabase::DeleteUnreferencedImages(
diff --git a/components/ntp_snippets/remote/remote_suggestions_database.h b/components/ntp_snippets/remote/remote_suggestions_database.h
index 6966cf1..c957dde 100644
--- a/components/ntp_snippets/remote/remote_suggestions_database.h
+++ b/components/ntp_snippets/remote/remote_suggestions_database.h
@@ -31,8 +31,9 @@
 // TODO(gaschler): implement a Fake version for testing
 class RemoteSuggestionsDatabase {
  public:
-  using SnippetsCallback = base::Callback<void(RemoteSuggestion::PtrVector)>;
-  using SnippetImageCallback = base::Callback<void(std::string)>;
+  using SnippetsCallback =
+      base::OnceCallback<void(RemoteSuggestion::PtrVector)>;
+  using SnippetImageCallback = base::OnceCallback<void(std::string)>;
 
   // Creates a RemoteSuggestionsDatabase backed by real ProtoDatabaseImpls.
   RemoteSuggestionsDatabase(const base::FilePath& database_dir);
@@ -58,7 +59,7 @@
   void SetErrorCallback(const base::Closure& error_callback);
 
   // Loads all snippets from storage and passes them to |callback|.
-  void LoadSnippets(const SnippetsCallback& callback);
+  void LoadSnippets(SnippetsCallback callback);
 
   // Adds or updates the given snippet.
   void SaveSnippet(const RemoteSuggestion& snippet);
@@ -72,8 +73,7 @@
 
   // Loads the image data for the snippet with the given ID and passes it to
   // |callback|. Passes an empty string if not found.
-  void LoadImage(const std::string& snippet_id,
-                 const SnippetImageCallback& callback);
+  void LoadImage(const std::string& snippet_id, SnippetImageCallback callback);
 
   // Adds or updates the image data for the given snippet ID.
   void SaveImage(const std::string& snippet_id, const std::string& image_data);
@@ -102,14 +102,14 @@
 
   // Callbacks for ProtoDatabase<SnippetProto> operations.
   void OnDatabaseInited(bool success);
-  void OnDatabaseLoaded(const SnippetsCallback& callback,
+  void OnDatabaseLoaded(SnippetsCallback callback,
                         bool success,
                         std::unique_ptr<std::vector<SnippetProto>> entries);
   void OnDatabaseSaved(bool success);
 
   // Callbacks for ProtoDatabase<SnippetImageProto> operations.
   void OnImageDatabaseInited(bool success);
-  void OnImageDatabaseLoaded(const SnippetImageCallback& callback,
+  void OnImageDatabaseLoaded(SnippetImageCallback callback,
                              bool success,
                              std::unique_ptr<SnippetImageProto> entry);
   void OnImageDatabaseSaved(bool success);
@@ -118,11 +118,11 @@
 
   void ProcessPendingLoads();
 
-  void LoadSnippetsImpl(const SnippetsCallback& callback);
+  void LoadSnippetsImpl(SnippetsCallback callback);
   void SaveSnippetsImpl(std::unique_ptr<KeyEntryVector> entries_to_save);
 
   void LoadImageImpl(const std::string& snippet_id,
-                     const SnippetImageCallback& callback);
+                     SnippetImageCallback callback);
   void DeleteUnreferencedImages(
       std::unique_ptr<std::set<std::string>> references,
       bool load_keys_success,
diff --git a/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc b/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc
index 0035b619..926f560 100644
--- a/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc
+++ b/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc
@@ -418,8 +418,8 @@
   // database is done loading.
   database_load_start_ = base::TimeTicks::Now();
   database_->LoadSnippets(
-      base::Bind(&RemoteSuggestionsProviderImpl::OnDatabaseLoaded,
-                 base::Unretained(this)));
+      base::BindOnce(&RemoteSuggestionsProviderImpl::OnDatabaseLoaded,
+                     base::Unretained(this)));
 }
 
 RemoteSuggestionsProviderImpl::~RemoteSuggestionsProviderImpl() {
@@ -442,7 +442,7 @@
   if (!remote_suggestions_scheduler_->AcquireQuotaForInteractiveFetch()) {
     return;
   }
-  auto callback = base::Bind(
+  auto callback = base::BindOnce(
       [](RemoteSuggestionsScheduler* scheduler, Status status_code) {
         scheduler->OnInteractiveFetchFinished(status_code);
       },
@@ -1423,15 +1423,18 @@
   StoreCategoriesToPrefs();
 }
 
+GURL RemoteSuggestionsProviderImpl::GetImageURLToFetch(
+    const ContentSuggestion::ID& suggestion_id) const {
+  if (!base::ContainsKey(category_contents_, suggestion_id.category())) {
+    return GURL();
+  }
+  return FindSuggestionImageUrl(suggestion_id);
+}
+
 void RemoteSuggestionsProviderImpl::FetchSuggestionImage(
     const ContentSuggestion::ID& suggestion_id,
     ImageFetchedCallback callback) {
-  if (!base::ContainsKey(category_contents_, suggestion_id.category())) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), gfx::Image()));
-    return;
-  }
-  GURL image_url = FindSuggestionImageUrl(suggestion_id);
+  GURL image_url = GetImageURLToFetch(suggestion_id);
   if (image_url.is_empty()) {
     // As we don't know the corresponding suggestion anymore, we don't expect to
     // find it in the database (and also can't fetch it remotely). Cut the
@@ -1441,6 +1444,7 @@
     return;
   }
   image_fetcher_.FetchSuggestionImage(suggestion_id, image_url,
+                                      ImageDataFetchedCallback(),
                                       std::move(callback));
 }
 
diff --git a/components/ntp_snippets/remote/remote_suggestions_provider_impl.h b/components/ntp_snippets/remote/remote_suggestions_provider_impl.h
index 46a367e7..bc77c4c 100644
--- a/components/ntp_snippets/remote/remote_suggestions_provider_impl.h
+++ b/components/ntp_snippets/remote/remote_suggestions_provider_impl.h
@@ -417,6 +417,8 @@
   void NotifyFetchWithLoadingIndicatorStarted();
   void NotifyFetchWithLoadingIndicatorFailedOrTimeouted();
 
+  GURL GetImageURLToFetch(const ContentSuggestion::ID& suggestion_id) const;
+
   State state_;
 
   PrefService* pref_service_;
diff --git a/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc b/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc
index e1a8c43e..8f579741 100644
--- a/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc
+++ b/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc
@@ -31,7 +31,7 @@
 #include "base/timer/timer.h"
 #include "components/image_fetcher/core/image_decoder.h"
 #include "components/image_fetcher/core/image_fetcher.h"
-#include "components/image_fetcher/core/image_fetcher_delegate.h"
+#include "components/image_fetcher/core/mock_image_fetcher.h"
 #include "components/image_fetcher/core/request_metadata.h"
 #include "components/ntp_snippets/breaking_news/breaking_news_listener.h"
 #include "components/ntp_snippets/category.h"
@@ -69,7 +69,7 @@
 
 using base::TestMockTimeTaskRunner;
 using image_fetcher::ImageFetcher;
-using image_fetcher::ImageFetcherDelegate;
+using image_fetcher::MockImageFetcher;
 using ntp_snippets::test::FetchedCategoryBuilder;
 using ntp_snippets::test::RemoteSuggestionBuilder;
 using testing::_;
@@ -162,22 +162,16 @@
   return RemoteSuggestion::CreateFromProto(snippet_proto);
 }
 
-using ServeImageCallback = base::Callback<void(
-    const std::string&,
-    base::Callback<void(const std::string&,
-                        const gfx::Image&,
-                        const image_fetcher::RequestMetadata&)>)>;
-
 void ServeOneByOneImage(
-    image_fetcher::ImageFetcherDelegate* notify,
     const std::string& id,
-    base::Callback<void(const std::string&,
-                        const gfx::Image&,
-                        const image_fetcher::RequestMetadata&)> callback) {
+    image_fetcher::ImageFetcher::ImageDataFetcherCallback* image_data_callback,
+    image_fetcher::ImageFetcher::ImageFetcherCallback* callback) {
+  std::move(*image_data_callback)
+      .Run("1-by-1-image-data", image_fetcher::RequestMetadata());
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(callback, id, gfx::test::CreateImage(1, 1),
-                            image_fetcher::RequestMetadata()));
-  notify->OnImageDataFetched(id, "1-by-1-image-data");
+      FROM_HERE,
+      base::BindOnce(std::move(*callback), id, gfx::test::CreateImage(1, 1),
+                     image_fetcher::RequestMetadata()));
 }
 
 gfx::Image FetchImage(RemoteSuggestionsProviderImpl* provider,
@@ -196,21 +190,6 @@
   return result;
 }
 
-class MockImageFetcher : public ImageFetcher {
- public:
-  MOCK_METHOD1(SetImageFetcherDelegate, void(ImageFetcherDelegate*));
-  MOCK_METHOD1(SetDataUseServiceName, void(DataUseServiceName));
-  MOCK_METHOD1(SetImageDownloadLimit,
-               void(base::Optional<int64_t> max_download_bytes));
-  MOCK_METHOD1(SetDesiredImageFrameSize, void(const gfx::Size&));
-  MOCK_METHOD4(StartOrQueueNetworkRequest,
-               void(const std::string&,
-                    const GURL&,
-                    const ImageFetcherCallback&,
-                    const net::NetworkTrafficAnnotationTag&));
-  MOCK_METHOD0(GetImageDecoder, image_fetcher::ImageDecoder*());
-};
-
 class FakeImageDecoder : public image_fetcher::ImageDecoder {
  public:
   FakeImageDecoder() {}
@@ -412,7 +391,6 @@
     auto image_fetcher = std::make_unique<NiceMock<MockImageFetcher>>();
 
     image_fetcher_ = image_fetcher.get();
-    EXPECT_CALL(*image_fetcher, SetImageFetcherDelegate(_));
     ON_CALL(*image_fetcher, GetImageDecoder())
         .WillByDefault(Return(&image_decoder_));
     EXPECT_FALSE(observer_);
@@ -1342,10 +1320,10 @@
               ElementsAre(Pointee(Property(&RemoteSuggestion::id, "id"))));
 
   image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
-  ServeImageCallback serve_one_by_one_image_callback =
-      base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
-  EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
-      .WillOnce(WithArgs<0, 2>(
+  auto serve_one_by_one_image_callback =
+      base::BindRepeating(&ServeOneByOneImage);
+  EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
+      .WillOnce(WithArgs<0, 2, 3>(
           Invoke(CreateFunctor(serve_one_by_one_image_callback))));
 
   gfx::Image image = FetchImage(provider.get(), MakeArticleID("id"));
@@ -1414,10 +1392,10 @@
       Status::Success(), std::move(fetched_categories));
 
   image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
-  ServeImageCallback serve_one_by_one_image_callback =
-      base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
-  EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
-      .WillOnce(WithArgs<0, 2>(
+  auto serve_one_by_one_image_callback =
+      base::BindRepeating(&ServeOneByOneImage);
+  EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
+      .WillOnce(WithArgs<0, 2, 3>(
           Invoke(CreateFunctor(serve_one_by_one_image_callback))));
 
   gfx::Image image = FetchImage(provider.get(), MakeArticleID("id"));
@@ -1584,11 +1562,10 @@
                         Status::Success(), std::move(fetched_categories));
   // Make sure images of both batches are available. This is to sanity check our
   // assumptions for the test are right.
-  ServeImageCallback cb =
-      base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
-  EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
+  EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
       .Times(2)
-      .WillRepeatedly(WithArgs<0, 2>(Invoke(CreateFunctor(cb))));
+      .WillRepeatedly(WithArgs<0, 2, 3>(
+          Invoke(CreateFunctor(base::BindRepeating(&ServeOneByOneImage)))));
   image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
   gfx::Image image = FetchImage(provider.get(), MakeArticleID("http://id-1"));
   ASSERT_FALSE(image.IsEmpty());
@@ -1750,10 +1727,9 @@
   ASSERT_THAT(provider->GetSuggestionsForTesting(articles_category()),
               SizeIs(1));
   // Load the image to store it in the database.
-  ServeImageCallback cb =
-      base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
-  EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
-      .WillOnce(WithArgs<0, 2>(Invoke(CreateFunctor(cb))));
+  EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
+      .WillOnce(WithArgs<0, 2, 3>(
+          Invoke(CreateFunctor(base::BindRepeating(&ServeOneByOneImage)))));
   image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
   gfx::Image image =
       FetchImage(provider.get(), MakeArticleID("http://site.com"));
@@ -1877,10 +1853,9 @@
   // Load the image to store it in the database.
   // TODO(tschumann): Introduce some abstraction to nicely work with image
   // fetching expectations.
-  ServeImageCallback cb =
-      base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
-  EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
-      .WillOnce(WithArgs<0, 2>(Invoke(CreateFunctor(cb))));
+  EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
+      .WillOnce(WithArgs<0, 2, 3>(
+          Invoke(CreateFunctor(base::BindRepeating(&ServeOneByOneImage)))));
   image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
   gfx::Image image = FetchImage(provider.get(), MakeArticleID("http://first/"));
   EXPECT_FALSE(image.IsEmpty());
@@ -2142,12 +2117,11 @@
 
   gfx::Image image;
   MockFunction<void(const gfx::Image&)> image_fetched;
-  ServeImageCallback cb =
-      base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
   {
     InSequence s;
-    EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
-        .WillOnce(WithArgs<0, 2>(Invoke(CreateFunctor(cb))));
+    EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
+        .WillOnce(WithArgs<0, 2, 3>(
+            Invoke(CreateFunctor(base::BindRepeating(&ServeOneByOneImage)))));
     EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image));
   }
 
@@ -2280,11 +2254,10 @@
           .Build());
   FetchTheseSuggestions(provider.get(), /*interactive_request=*/true,
                         Status::Success(), std::move(fetched_categories));
-  ServeImageCallback cb =
-      base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
 
-  EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
-      .WillOnce(WithArgs<0, 2>(Invoke(CreateFunctor(cb))));
+  EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
+      .WillOnce(WithArgs<0, 2, 3>(
+          Invoke(CreateFunctor(base::BindRepeating(&ServeOneByOneImage)))));
   image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
 
   gfx::Image image = FetchImage(provider.get(), MakeArticleID(kSuggestionUrl));
diff --git a/components/ntp_tiles/BUILD.gn b/components/ntp_tiles/BUILD.gn
index 099466d8..a01efec1 100644
--- a/components/ntp_tiles/BUILD.gn
+++ b/components/ntp_tiles/BUILD.gn
@@ -95,6 +95,7 @@
     "//components/favicon/core",
     "//components/favicon_base",
     "//components/image_fetcher/core",
+    "//components/image_fetcher/core:test_support",
     "//components/pref_registry:pref_registry",
     "//components/rappor:test_support",
     "//components/sync_preferences:test_support",
diff --git a/components/ntp_tiles/icon_cacher_impl.cc b/components/ntp_tiles/icon_cacher_impl.cc
index 9c27c28..0abdb05 100644
--- a/components/ntp_tiles/icon_cacher_impl.cc
+++ b/components/ntp_tiles/icon_cacher_impl.cc
@@ -137,7 +137,7 @@
           setting: "This feature cannot be disabled in settings."
           policy_exception_justification: "Not implemented."
         })");
-  image_fetcher_->StartOrQueueNetworkRequest(
+  image_fetcher_->FetchImage(
       std::string(), IconURL(site),
       base::Bind(&IconCacherImpl::OnPopularSitesFaviconDownloaded,
                  base::Unretained(this), site,
diff --git a/components/ntp_tiles/icon_cacher_impl_unittest.cc b/components/ntp_tiles/icon_cacher_impl_unittest.cc
index 0c490ae..c1ba04b 100644
--- a/components/ntp_tiles/icon_cacher_impl_unittest.cc
+++ b/components/ntp_tiles/icon_cacher_impl_unittest.cc
@@ -25,6 +25,7 @@
 #include "components/history/core/browser/history_service.h"
 #include "components/image_fetcher/core/image_decoder.h"
 #include "components/image_fetcher/core/image_fetcher.h"
+#include "components/image_fetcher/core/mock_image_fetcher.h"
 #include "components/image_fetcher/core/request_metadata.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -34,6 +35,7 @@
 #include "ui/gfx/image/image_unittest_util.h"
 
 using base::Bucket;
+using ::image_fetcher::MockImageFetcher;
 using ::testing::_;
 using ::testing::ElementsAre;
 using ::testing::Eq;
@@ -47,23 +49,6 @@
 namespace ntp_tiles {
 namespace {
 
-class MockImageFetcher : public image_fetcher::ImageFetcher {
- public:
-  MOCK_METHOD1(SetImageFetcherDelegate,
-               void(image_fetcher::ImageFetcherDelegate* delegate));
-  MOCK_METHOD1(SetDataUseServiceName,
-               void(image_fetcher::ImageFetcher::DataUseServiceName name));
-  MOCK_METHOD1(SetImageDownloadLimit,
-               void(base::Optional<int64_t> max_download_bytes));
-  MOCK_METHOD4(StartOrQueueNetworkRequest,
-               void(const std::string& id,
-                    const GURL& image_url,
-                    const ImageFetcherCallback& callback,
-                    const net::NetworkTrafficAnnotationTag&));
-  MOCK_METHOD1(SetDesiredImageFrameSize, void(const gfx::Size&));
-  MOCK_METHOD0(GetImageDecoder, image_fetcher::ImageDecoder*());
-};
-
 class MockImageDecoder : public image_fetcher::ImageDecoder {
  public:
   MOCK_METHOD3(DecodeImage,
@@ -104,8 +89,8 @@
 
 ACTION(FailFetch) {
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::Bind(arg2, arg0, gfx::Image(), image_fetcher::RequestMetadata()));
+      FROM_HERE, base::BindOnce(std::move(*arg3), arg0, gfx::Image(),
+                                image_fetcher::RequestMetadata()));
 }
 
 ACTION_P2(DecodeSuccessfully, width, height) {
@@ -115,8 +100,9 @@
 
 ACTION_P2(PassFetch, width, height) {
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::Bind(arg2, arg0, gfx::test::CreateImage(width, height),
-                            image_fetcher::RequestMetadata()));
+      FROM_HERE, base::BindOnce(std::move(*arg3), arg0,
+                                gfx::test::CreateImage(width, height),
+                                image_fetcher::RequestMetadata()));
 }
 
 ACTION_P(Quit, run_loop) {
@@ -272,7 +258,7 @@
                     data_use_measurement::DataUseUserData::NTP_TILES));
     EXPECT_CALL(*image_fetcher_, SetDesiredImageFrameSize(gfx::Size(128, 128)));
     EXPECT_CALL(*image_fetcher_,
-                StartOrQueueNetworkRequest(_, site_.large_icon_url, _, _))
+                FetchImageAndData_(_, site_.large_icon_url, _, _, _))
         .WillOnce(PassFetch(128, 128));
     EXPECT_CALL(done, Run()).WillOnce(Quit(&loop));
   }
@@ -299,7 +285,7 @@
                     data_use_measurement::DataUseUserData::NTP_TILES));
     EXPECT_CALL(*image_fetcher_, SetDesiredImageFrameSize(gfx::Size(128, 128)));
     EXPECT_CALL(*image_fetcher_,
-                StartOrQueueNetworkRequest(_, site_.favicon_url, _, _))
+                FetchImageAndData_(_, site_.favicon_url, _, _, _))
         .WillOnce(PassFetch(128, 128));
     EXPECT_CALL(done, Run()).WillOnce(Quit(&loop));
   }
@@ -322,7 +308,7 @@
                     data_use_measurement::DataUseUserData::NTP_TILES));
     EXPECT_CALL(*image_fetcher_, SetDesiredImageFrameSize(gfx::Size(128, 128)));
     EXPECT_CALL(*image_fetcher_,
-                StartOrQueueNetworkRequest(_, site_.large_icon_url, _, _))
+                FetchImageAndData_(_, site_.large_icon_url, _, _, _))
         .WillOnce(FailFetch());
   }
 
@@ -331,17 +317,16 @@
   WaitForMainThreadTasksToFinish();
   EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::IconType::kFavicon));
   EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::IconType::kTouchIcon));
-  EXPECT_THAT(
-      histogram_tester.GetAllSamples(
-          "NewTabPage.TileFaviconFetchSuccess.Popular"),
-      ElementsAre(Bucket(/*bucket=*/false, /*count=*/1)));
+  EXPECT_THAT(histogram_tester.GetAllSamples(
+                  "NewTabPage.TileFaviconFetchSuccess.Popular"),
+              ElementsAre(Bucket(/*bucket=*/false, /*count=*/1)));
 }
 
 TEST_F(IconCacherTestPopularSites, HandlesEmptyCallbacksNicely) {
   base::HistogramTester histogram_tester;
   EXPECT_CALL(*image_fetcher_, SetDataUseServiceName(_));
   EXPECT_CALL(*image_fetcher_, SetDesiredImageFrameSize(_));
-  EXPECT_CALL(*image_fetcher_, StartOrQueueNetworkRequest(_, _, _, _))
+  EXPECT_CALL(*image_fetcher_, FetchImageAndData_(_, _, _, _, _))
       .WillOnce(PassFetch(128, 128));
   IconCacherImpl cacher(&favicon_service_, nullptr, std::move(image_fetcher_));
   cacher.StartFetchPopularSites(site_, base::Closure(), base::Closure());
@@ -351,10 +336,9 @@
   EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::IconType::kFavicon));
   EXPECT_TRUE(IconIsCachedFor(site_.url, favicon_base::IconType::kTouchIcon));
   // The histogram gets reported despite empty callbacks.
-  EXPECT_THAT(
-      histogram_tester.GetAllSamples(
-          "NewTabPage.TileFaviconFetchSuccess.Popular"),
-      ElementsAre(Bucket(/*bucket=*/true, /*count=*/1)));
+  EXPECT_THAT(histogram_tester.GetAllSamples(
+                  "NewTabPage.TileFaviconFetchSuccess.Popular"),
+              ElementsAre(Bucket(/*bucket=*/true, /*count=*/1)));
 }
 
 TEST_F(IconCacherTestPopularSites, ProvidesDefaultIconAndSucceedsWithFetching) {
@@ -379,7 +363,7 @@
                     data_use_measurement::DataUseUserData::NTP_TILES));
     EXPECT_CALL(*image_fetcher_, SetDesiredImageFrameSize(gfx::Size(128, 128)));
     EXPECT_CALL(*image_fetcher_,
-                StartOrQueueNetworkRequest(_, site_.large_icon_url, _, _))
+                FetchImageAndData_(_, site_.large_icon_url, _, _, _))
         .WillOnce(PassFetch(128, 128));
 
     // Both callback are called async after the request but preliminary has to
@@ -406,10 +390,9 @@
       Eq(gfx::Size(128, 128)));  // Compares dimensions, not objects.
   // The histogram gets reported only once (for the downloaded icon, not for the
   // default one).
-  EXPECT_THAT(
-      histogram_tester.GetAllSamples(
-          "NewTabPage.TileFaviconFetchSuccess.Popular"),
-      ElementsAre(Bucket(/*bucket=*/true, /*count=*/1)));
+  EXPECT_THAT(histogram_tester.GetAllSamples(
+                  "NewTabPage.TileFaviconFetchSuccess.Popular"),
+              ElementsAre(Bucket(/*bucket=*/true, /*count=*/1)));
 }
 
 TEST_F(IconCacherTestPopularSites, LargeNotCachedAndFetchPerformedOnlyOnce) {
@@ -423,7 +406,7 @@
                     data_use_measurement::DataUseUserData::NTP_TILES));
     EXPECT_CALL(*image_fetcher_, SetDesiredImageFrameSize(gfx::Size(128, 128)));
     EXPECT_CALL(*image_fetcher_,
-                StartOrQueueNetworkRequest(_, site_.large_icon_url, _, _))
+                FetchImageAndData_(_, site_.large_icon_url, _, _, _))
         .WillOnce(PassFetch(128, 128));
     // Success will be notified to both requests.
     EXPECT_CALL(done, Run()).WillOnce(Return()).WillOnce(Quit(&loop));
@@ -494,7 +477,7 @@
                 SetDataUseServiceName(
                     data_use_measurement::DataUseUserData::LARGE_ICON_SERVICE));
     EXPECT_CALL(*fetcher_for_large_icon_service_,
-                StartOrQueueNetworkRequest(_, _, _, _))
+                FetchImageAndData_(_, _, _, _, _))
         .WillOnce(PassFetch(128, 128));
     EXPECT_CALL(done, Run()).WillOnce(Quit(&loop));
   }
@@ -532,7 +515,7 @@
                 SetDataUseServiceName(
                     data_use_measurement::DataUseUserData::LARGE_ICON_SERVICE));
     EXPECT_CALL(*fetcher_for_large_icon_service_,
-                StartOrQueueNetworkRequest(_, _, _, _))
+                FetchImageAndData_(_, _, _, _, _))
         .WillOnce(FailFetch());
     EXPECT_CALL(done, Run()).Times(0);
   }
@@ -564,7 +547,7 @@
 
   EXPECT_CALL(*fetcher_for_large_icon_service_, SetDataUseServiceName(_));
   EXPECT_CALL(*fetcher_for_large_icon_service_,
-              StartOrQueueNetworkRequest(_, _, _, _))
+              FetchImageAndData_(_, _, _, _, _))
       .WillOnce(PassFetch(128, 128));
 
   favicon::LargeIconService large_icon_service(
@@ -602,7 +585,7 @@
                 SetDataUseServiceName(
                     data_use_measurement::DataUseUserData::LARGE_ICON_SERVICE));
     EXPECT_CALL(*fetcher_for_large_icon_service_,
-                StartOrQueueNetworkRequest(_, _, _, _))
+                FetchImageAndData_(_, _, _, _, _))
         .WillOnce(PassFetch(128, 128));
     // Success will be notified to both requests.
     EXPECT_CALL(done, Run()).WillOnce(Return()).WillOnce(Quit(&loop));
diff --git a/components/payments/content/payment_request_state.cc b/components/payments/content/payment_request_state.cc
index a3923fb..f38dab9 100644
--- a/components/payments/content/payment_request_state.cc
+++ b/components/payments/content/payment_request_state.cc
@@ -59,6 +59,8 @@
         web_contents,
         payment_request_delegate_->GetPaymentManifestWebDataService(),
         spec_->method_data(),
+        /*may_crawl_for_installable_payment_apps=*/
+        !spec_->supports_basic_card(),
         base::BindOnce(&PaymentRequestState::GetAllPaymentAppsCallback,
                        weak_ptr_factory_.GetWeakPtr(), web_contents,
                        top_level_origin, frame_origin),
diff --git a/components/payments/content/service_worker_payment_app_factory.cc b/components/payments/content/service_worker_payment_app_factory.cc
index 17ad3b4..67772a37 100644
--- a/components/payments/content/service_worker_payment_app_factory.cc
+++ b/components/payments/content/service_worker_payment_app_factory.cc
@@ -102,6 +102,7 @@
       std::unique_ptr<PaymentManifestDownloader> downloader,
       scoped_refptr<PaymentManifestWebDataService> cache,
       const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
+      bool may_crawl_for_installable_payment_apps,
       ServiceWorkerPaymentAppFactory::GetAllPaymentAppsCallback callback,
       base::OnceClosure finished_using_resources_callback) {
     DCHECK(!verifier_);
@@ -111,9 +112,11 @@
     cache_ = cache;
     verifier_ = std::make_unique<ManifestVerifier>(
         web_contents, downloader_.get(), parser_.get(), cache_.get());
-    // Construct crawler in constructor to allow it observe the web_contents.
-    crawler_ = std::make_unique<InstallablePaymentAppCrawler>(
-        web_contents, downloader_.get(), parser_.get(), cache_.get());
+    if (may_crawl_for_installable_payment_apps) {
+      // Construct crawler in constructor to allow it observe the web_contents.
+      crawler_ = std::make_unique<InstallablePaymentAppCrawler>(
+          web_contents, downloader_.get(), parser_.get(), cache_.get());
+    }
 
     // Method data cannot be copied and is passed in as a const-ref, which
     // cannot be moved, so make a manual copy for using below.
@@ -136,8 +139,7 @@
   }
 
  private:
-  void OnGotAllPaymentApps(
-      content::PaymentAppProvider::PaymentApps apps) {
+  void OnGotAllPaymentApps(content::PaymentAppProvider::PaymentApps apps) {
     if (ignore_port_in_app_scope_for_testing_)
       RemovePortNumbersFromScopesForTest(&apps);
 
@@ -164,7 +166,7 @@
   }
 
   void OnPaymentAppsVerified(content::PaymentAppProvider::PaymentApps apps) {
-    if (apps.empty()) {
+    if (apps.empty() && crawler_ != nullptr) {
       // Crawls installable web payment apps if no web payment apps have been
       // installed.
       is_payment_app_crawler_finished_using_resources_ = false;
@@ -248,6 +250,7 @@
     content::WebContents* web_contents,
     scoped_refptr<PaymentManifestWebDataService> cache,
     const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
+    bool may_crawl_for_installable_payment_apps,
     GetAllPaymentAppsCallback callback,
     base::OnceClosure finished_writing_cache_callback_for_testing) {
   SelfDeletingServiceWorkerPaymentAppFactory* self_delete_factory =
@@ -263,7 +266,8 @@
                     web_contents->GetBrowserContext())
                     ->GetURLRequestContext())
           : std::move(test_downloader_),
-      cache, requested_method_data, std::move(callback),
+      cache, requested_method_data, may_crawl_for_installable_payment_apps,
+      std::move(callback),
       std::move(finished_writing_cache_callback_for_testing));
 }
 
diff --git a/components/payments/content/service_worker_payment_app_factory.h b/components/payments/content/service_worker_payment_app_factory.h
index e7bc186..d1681dd0 100644
--- a/components/payments/content/service_worker_payment_app_factory.h
+++ b/components/payments/content/service_worker_payment_app_factory.h
@@ -63,6 +63,7 @@
       content::WebContents* web_contents,
       scoped_refptr<PaymentManifestWebDataService> cache,
       const std::vector<mojom::PaymentMethodDataPtr>& requested_method_data,
+      bool may_crawl_for_installable_payment_apps,
       GetAllPaymentAppsCallback callback,
       base::OnceClosure finished_writing_cache_callback_for_testing);
 
diff --git a/components/policy/core/common/BUILD.gn b/components/policy/core/common/BUILD.gn
index f60a7ad..632cfccf 100644
--- a/components/policy/core/common/BUILD.gn
+++ b/components/policy/core/common/BUILD.gn
@@ -62,6 +62,8 @@
     "cloud/external_policy_data_fetcher.h",
     "cloud/external_policy_data_updater.cc",
     "cloud/external_policy_data_updater.h",
+    "cloud/machine_level_user_cloud_policy_manager.cc",
+    "cloud/machine_level_user_cloud_policy_manager.h",
     "cloud/machine_level_user_cloud_policy_store.cc",
     "cloud/machine_level_user_cloud_policy_store.h",
     "cloud/policy_header_io_helper.cc",
@@ -202,6 +204,8 @@
     sources -= [
       "cloud/cloud_policy_client_registration_helper.cc",
       "cloud/cloud_policy_client_registration_helper.h",
+      "cloud/machine_level_user_cloud_policy_manager.cc",
+      "cloud/machine_level_user_cloud_policy_manager.h",
       "cloud/machine_level_user_cloud_policy_store.cc",
       "cloud/machine_level_user_cloud_policy_store.h",
       "cloud/user_cloud_policy_manager.cc",
@@ -322,6 +326,7 @@
     ]
   } else {
     sources += [
+      "cloud/machine_level_user_cloud_policy_manager_unittest.cc",
       "cloud/machine_level_user_cloud_policy_store_unittest.cc",
       "cloud/user_cloud_policy_manager_unittest.cc",
       "cloud/user_cloud_policy_store_unittest.cc",
diff --git a/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.cc b/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.cc
new file mode 100644
index 0000000..721df3e2
--- /dev/null
+++ b/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.cc
@@ -0,0 +1,82 @@
+// 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 "components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h"
+
+#include <string>
+#include <utility>
+
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/prefs/pref_service.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace policy {
+namespace {
+
+const base::FilePath::CharType kComponentPolicyCache[] =
+    FILE_PATH_LITERAL("Machine Level User Cloud Component Policy");
+
+}  // namespace
+
+MachineLevelUserCloudPolicyManager::MachineLevelUserCloudPolicyManager(
+    std::unique_ptr<MachineLevelUserCloudPolicyStore> store,
+    std::unique_ptr<CloudExternalDataManager> external_data_manager,
+    const base::FilePath& policy_dir,
+    const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+    const scoped_refptr<base::SequencedTaskRunner>& io_task_runner)
+    : CloudPolicyManager(dm_protocol::kChromeMachineLevelUserCloudPolicyType,
+                         std::string(),
+                         store.get(),
+                         task_runner,
+                         io_task_runner),
+      store_(std::move(store)),
+      external_data_manager_(std::move(external_data_manager)),
+      policy_dir_(policy_dir) {}
+
+MachineLevelUserCloudPolicyManager::~MachineLevelUserCloudPolicyManager() {}
+
+void MachineLevelUserCloudPolicyManager::Connect(
+    PrefService* local_state,
+    scoped_refptr<net::URLRequestContextGetter> request_context,
+    std::unique_ptr<CloudPolicyClient> client) {
+  CHECK(!core()->client());
+
+  CreateComponentCloudPolicyService(
+      dm_protocol::kChromeMachineLevelExtensionCloudPolicyType,
+      policy_dir_.Append(kComponentPolicyCache), request_context, client.get(),
+      schema_registry());
+  core()->Connect(std::move(client));
+  core()->StartRefreshScheduler();
+  core()->TrackRefreshDelayPref(local_state,
+                                policy_prefs::kUserPolicyRefreshRate);
+  if (external_data_manager_)
+    external_data_manager_->Connect(request_context);
+}
+
+bool MachineLevelUserCloudPolicyManager::IsClientRegistered() {
+  return client() && client()->is_registered();
+}
+
+void MachineLevelUserCloudPolicyManager::Init(SchemaRegistry* registry) {
+  DVLOG(1) << "Machine level cloud policy manager initialized";
+  ConfigurationPolicyProvider::Init(registry);
+
+  store()->AddObserver(this);
+
+  // Load the policy from disk synchronously once the manager is initalized
+  // during Chrome launch if the cache and the global dm token exist.
+  store()->LoadImmediately();
+}
+
+void MachineLevelUserCloudPolicyManager::Shutdown() {
+  if (external_data_manager_)
+    external_data_manager_->Disconnect();
+  CloudPolicyManager::Shutdown();
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h b/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h
new file mode 100644
index 0000000..88e92bf
--- /dev/null
+++ b/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h
@@ -0,0 +1,58 @@
+// 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 COMPONENTS_POLICY_CORE_COMMON_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_MANAGER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_MANAGER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "components/policy/core/common/cloud/cloud_policy_manager.h"
+
+class PrefService;
+
+namespace policy {
+
+class MachineLevelUserCloudPolicyStore;
+
+// Implements a cloud policy manager that initializes the machine level user
+// cloud policy.
+class POLICY_EXPORT MachineLevelUserCloudPolicyManager
+    : public CloudPolicyManager {
+ public:
+  MachineLevelUserCloudPolicyManager(
+      std::unique_ptr<MachineLevelUserCloudPolicyStore> store,
+      std::unique_ptr<CloudExternalDataManager> external_data_manager,
+      const base::FilePath& policy_dir,
+      const scoped_refptr<base::SequencedTaskRunner>& task_runner,
+      const scoped_refptr<base::SequencedTaskRunner>& io_task_runner);
+  ~MachineLevelUserCloudPolicyManager() override;
+
+  // Initializes the cloud connection. |local_state| must stay valid until this
+  // object is deleted.
+  void Connect(PrefService* local_state,
+               scoped_refptr<net::URLRequestContextGetter> request_context,
+               std::unique_ptr<CloudPolicyClient> client);
+
+  // Returns true if the underlying CloudPolicyClient is already registered.
+  bool IsClientRegistered();
+
+  MachineLevelUserCloudPolicyStore* store() { return store_.get(); }
+
+  // ConfigurationPolicyProvider:
+  void Init(SchemaRegistry* registry) override;
+  void Shutdown() override;
+
+ private:
+  std::unique_ptr<MachineLevelUserCloudPolicyStore> store_;
+  std::unique_ptr<CloudExternalDataManager> external_data_manager_;
+
+  const base::FilePath policy_dir_;
+
+  DISALLOW_COPY_AND_ASSIGN(MachineLevelUserCloudPolicyManager);
+};
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_CORE_COMMON_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_MANAGER_H_
diff --git a/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager_unittest.cc b/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager_unittest.cc
new file mode 100644
index 0000000..e1d4f39
--- /dev/null
+++ b/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager_unittest.cc
@@ -0,0 +1,60 @@
+// 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 "components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h"
+
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+#include "components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace policy {
+
+class MockMachineLevelUserCloudPolicyStore
+    : public MachineLevelUserCloudPolicyStore {
+ public:
+  MockMachineLevelUserCloudPolicyStore()
+      : MachineLevelUserCloudPolicyStore(
+            std::string(),
+            std::string(),
+            base::FilePath(),
+            base::FilePath(),
+            scoped_refptr<base::SequencedTaskRunner>()) {}
+
+  MOCK_METHOD0(LoadImmediately, void(void));
+};
+
+class MachineLevelUserCloudPolicyManagerTest : public ::testing::Test {
+ public:
+  MachineLevelUserCloudPolicyManagerTest() {}
+  ~MachineLevelUserCloudPolicyManagerTest() override { manager_->Shutdown(); }
+
+  void SetUp() override {
+    auto store = std::make_unique<MockMachineLevelUserCloudPolicyStore>();
+    store_ = store.get();
+    manager_ = std::make_unique<MachineLevelUserCloudPolicyManager>(
+        std::move(store), std::unique_ptr<CloudExternalDataManager>(),
+        base::FilePath(), scoped_refptr<base::SequencedTaskRunner>(),
+        scoped_refptr<base::SequencedTaskRunner>());
+  }
+
+  SchemaRegistry schema_registry_;
+  MockMachineLevelUserCloudPolicyStore* store_ = nullptr;
+  std::unique_ptr<MachineLevelUserCloudPolicyManager> manager_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MachineLevelUserCloudPolicyManagerTest);
+};
+
+TEST_F(MachineLevelUserCloudPolicyManagerTest, InitManager) {
+  EXPECT_CALL(*store_, LoadImmediately());
+  manager_->Init(&schema_registry_);
+  ::testing::Mock::VerifyAndClearExpectations(store_);
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.cc b/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.cc
index 42657ef..c7c1824 100644
--- a/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.cc
+++ b/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.cc
@@ -12,7 +12,6 @@
 namespace policy {
 namespace {
 
-const base::FilePath::CharType kPolicyDir[] = FILE_PATH_LITERAL("Policy");
 const base::FilePath::CharType kPolicyCache[] =
     FILE_PATH_LITERAL("Machine Level User Cloud Policy");
 const base::FilePath::CharType kKeyCache[] =
@@ -40,9 +39,8 @@
 MachineLevelUserCloudPolicyStore::Create(
     const std::string& machine_dm_token,
     const std::string& machine_client_id,
-    const base::FilePath& user_data_dir,
+    const base::FilePath& policy_dir,
     scoped_refptr<base::SequencedTaskRunner> background_task_runner) {
-  base::FilePath policy_dir = user_data_dir.Append(kPolicyDir);
   base::FilePath policy_cache_file = policy_dir.Append(kPolicyCache);
   base::FilePath key_cache_file = policy_dir.Append(kKeyCache);
   return std::make_unique<MachineLevelUserCloudPolicyStore>(
diff --git a/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h b/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h
index d43f285c..187109c 100644
--- a/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h
+++ b/components/policy/core/common/cloud/machine_level_user_cloud_policy_store.h
@@ -30,7 +30,7 @@
   static std::unique_ptr<MachineLevelUserCloudPolicyStore> Create(
       const std::string& machine_dm_token,
       const std::string& machine_client_id,
-      const base::FilePath& user_data_dir,
+      const base::FilePath& policy_dir,
       scoped_refptr<base::SequencedTaskRunner> background_task_runner);
 
   // override DesktopCloudPolicyStore
diff --git a/components/policy/core/common/cloud/machine_level_user_cloud_policy_store_unittest.cc b/components/policy/core/common/cloud/machine_level_user_cloud_policy_store_unittest.cc
index d5e6e88..c33ca52d 100644
--- a/components/policy/core/common/cloud/machine_level_user_cloud_policy_store_unittest.cc
+++ b/components/policy/core/common/cloud/machine_level_user_cloud_policy_store_unittest.cc
@@ -38,7 +38,7 @@
   ~MachineLevelUserCloudPolicyStoreTest() override {}
 
   void SetUp() override {
-    ASSERT_TRUE(tmp_user_data_dir_.CreateUniqueTempDir());
+    ASSERT_TRUE(tmp_policy_dir_.CreateUniqueTempDir());
     store_ = CreateStore();
   }
 
@@ -46,7 +46,7 @@
     std::unique_ptr<MachineLevelUserCloudPolicyStore> store =
         MachineLevelUserCloudPolicyStore::Create(
             PolicyBuilder::kFakeToken, PolicyBuilder::kFakeDeviceId,
-            tmp_user_data_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get());
+            tmp_policy_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get());
     store->AddObserver(&observer_);
     return store;
   }
@@ -59,7 +59,7 @@
 
   std::unique_ptr<MachineLevelUserCloudPolicyStore> store_;
 
-  base::ScopedTempDir tmp_user_data_dir_;
+  base::ScopedTempDir tmp_policy_dir_;
   UserPolicyBuilder policy_;
   MockCloudPolicyStoreObserver observer_;
 
@@ -119,15 +119,10 @@
 TEST_F(MachineLevelUserCloudPolicyStoreTest, StorePolicy) {
   EXPECT_FALSE(store_->policy());
   EXPECT_TRUE(store_->policy_map().empty());
-  const base::FilePath policy_path =
-      tmp_user_data_dir_.GetPath()
-          .Append(FILE_PATH_LITERAL("Policy"))
-          .Append(FILE_PATH_LITERAL("Machine Level User Cloud Policy"));
-  const base::FilePath signing_key_path =
-      tmp_user_data_dir_.GetPath()
-          .Append(FILE_PATH_LITERAL("Policy"))
-          .Append(
-              FILE_PATH_LITERAL("Machine Level User Cloud Policy Signing Key"));
+  const base::FilePath policy_path = tmp_policy_dir_.GetPath().Append(
+      FILE_PATH_LITERAL("Machine Level User Cloud Policy"));
+  const base::FilePath signing_key_path = tmp_policy_dir_.GetPath().Append(
+      FILE_PATH_LITERAL("Machine Level User Cloud Policy Signing Key"));
   EXPECT_FALSE(base::PathExists(policy_path));
   EXPECT_FALSE(base::PathExists(signing_key_path));
 
diff --git a/components/signin/core/browser/account_fetcher_service.cc b/components/signin/core/browser/account_fetcher_service.cc
index 7027fef..255c520 100644
--- a/components/signin/core/browser/account_fetcher_service.cc
+++ b/components/signin/core/browser/account_fetcher_service.cc
@@ -188,7 +188,7 @@
   DCHECK(network_fetches_enabled_);
 
   const base::TimeDelta time_since_update = base::Time::Now() - last_updated_;
-  if(time_since_update > kRefreshFromTokenServiceDelay) {
+  if (time_since_update > kRefreshFromTokenServiceDelay) {
     RefreshAllAccountsAndScheduleNext();
   } else {
     timer_.Start(FROM_HERE, kRefreshFromTokenServiceDelay - time_since_update,
@@ -267,7 +267,6 @@
   if (!image_fetcher_) {
     image_fetcher_ = std::make_unique<image_fetcher::ImageFetcherImpl>(
         std::move(image_decoder_), signin_client_->GetURLRequestContext());
-    image_fetcher_->SetImageFetcherDelegate(this);
   }
   return image_fetcher_.get();
 }
@@ -305,9 +304,10 @@
         })");
   GURL image_url_with_size(signin::GetAvatarImageURLWithOptions(
       picture_url, kAccountImageDownloadSize, true /* no_silhouette */));
-  GetOrCreateImageFetcher()->StartOrQueueNetworkRequest(
-      account_id, image_url_with_size,
-      image_fetcher::ImageFetcher::ImageFetcherCallback(), traffic_annotation);
+  auto callback = base::BindRepeating(&AccountFetcherService::OnImageFetched,
+                                      base::Unretained(this));
+  GetOrCreateImageFetcher()->FetchImage(account_id, image_url_with_size,
+                                        callback, traffic_annotation);
 }
 
 void AccountFetcherService::SetIsChildAccount(const std::string& account_id,
@@ -326,8 +326,7 @@
 void AccountFetcherService::OnRefreshTokenAvailable(
     const std::string& account_id) {
   TRACE_EVENT1("AccountFetcherService",
-               "AccountFetcherService::OnRefreshTokenAvailable",
-               "account_id",
+               "AccountFetcherService::OnRefreshTokenAvailable", "account_id",
                account_id);
   DVLOG(1) << "AVAILABLE " << account_id;
 
@@ -347,8 +346,7 @@
 void AccountFetcherService::OnRefreshTokenRevoked(
     const std::string& account_id) {
   TRACE_EVENT1("AccountFetcherService",
-               "AccountFetcherService::OnRefreshTokenRevoked",
-               "account_id",
+               "AccountFetcherService::OnRefreshTokenRevoked", "account_id",
                account_id);
   DVLOG(1) << "REVOKED " << account_id;
 
@@ -367,7 +365,9 @@
   MaybeEnableNetworkFetches();
 }
 
-void AccountFetcherService::OnImageFetched(const std::string& id,
-                                           const gfx::Image& image) {
+void AccountFetcherService::OnImageFetched(
+    const std::string& id,
+    const gfx::Image& image,
+    const image_fetcher::RequestMetadata&) {
   account_tracker_service_->SetAccountImage(id, image);
 }
diff --git a/components/signin/core/browser/account_fetcher_service.h b/components/signin/core/browser/account_fetcher_service.h
index 6e86435..01ef1ef 100644
--- a/components/signin/core/browser/account_fetcher_service.h
+++ b/components/signin/core/browser/account_fetcher_service.h
@@ -13,9 +13,9 @@
 #include "base/macros.h"
 #include "base/sequence_checker.h"
 #include "base/timer/timer.h"
-#include "components/image_fetcher/core/image_fetcher_delegate.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "google_apis/gaia/oauth2_token_service.h"
+#include "ui/gfx/image/image.h"
 
 class AccountInfoFetcher;
 class AccountTrackerService;
@@ -24,6 +24,7 @@
 class SigninClient;
 
 namespace image_fetcher {
+struct RequestMetadata;
 class ImageDecoder;
 class ImageFetcherImpl;
 }  // namespace image_fetcher
@@ -40,8 +41,7 @@
 // to child account info fetching.
 
 class AccountFetcherService : public KeyedService,
-                              public OAuth2TokenService::Observer,
-                              public image_fetcher::ImageFetcherDelegate {
+                              public OAuth2TokenService::Observer {
  public:
   // Name of the preference that tracks the int64_t representation of the last
   // time the AccountTrackerService was updated.
@@ -125,12 +125,13 @@
   // Called in |OnUserInfoFetchSuccess| after the account info has been fetched.
   void FetchAccountImage(const std::string& account_id);
 
-  // image_fetcher::ImageFetcherDelegate:
-  void OnImageFetched(const std::string& id, const gfx::Image& image) override;
+  void OnImageFetched(const std::string& id,
+                      const gfx::Image& image,
+                      const image_fetcher::RequestMetadata& image_metadata);
 
-  AccountTrackerService* account_tracker_service_;  // Not owned.
-  OAuth2TokenService* token_service_;  // Not owned.
-  SigninClient* signin_client_;  // Not owned.
+  AccountTrackerService* account_tracker_service_;           // Not owned.
+  OAuth2TokenService* token_service_;                        // Not owned.
+  SigninClient* signin_client_;                              // Not owned.
   invalidation::InvalidationService* invalidation_service_;  // Not owned.
   bool network_fetches_enabled_;
   bool profile_loaded_;
diff --git a/components/subresource_filter/README b/components/subresource_filter/README
deleted file mode 100644
index 3ad2025..0000000
--- a/components/subresource_filter/README
+++ /dev/null
@@ -1,4 +0,0 @@
-When users proceed through a Safe Browsing interstitial displayed because the
-site ahead has deceptive embedded content, this component will be used to
-provide an additional layer of protection by filtering network requests for some
-embedded subresources on these sites.
diff --git a/components/subresource_filter/README.md b/components/subresource_filter/README.md
new file mode 100644
index 0000000..7dcb7c70
--- /dev/null
+++ b/components/subresource_filter/README.md
@@ -0,0 +1,127 @@
+#Subresource Filter
+The subresource_filter component deals with code that tags and filters
+subresource requests based on:
+
+1. Some page-level activation signal (e.g. should sub-resource filtering take place on this page).
+2. A ruleset used to match URLs for filtering.
+
+The primary consumer of this component is Chrome's ad filter, which filters ads
+on pages that violate the [Better Ads Standard](https://www.betterads.org/standards/).
+
+Additionally, Chrome will filter pages Safe Browsing determines are used for
+phishing (i.e. on pages after the user has proceeded through the security
+interstitial).
+
+## High Level Description
+At a high level, the component uses a memory mapped file of filtering rules to
+filter subresource requests in Blink, as well as subframe navigations in the
+browser process.
+
+For historical reasons (intention to support iOS), code is split into two
+components, [core](/components/subresource_filter/core) and
+[content](/components/subresource_filter/content). The core code is code that
+we could share with a non-content client like iOS, while all the content code
+depends on the Content API.
+
+Most of the logic in core deals with reading, indexing, and matching URLs off a
+ruleset.
+
+Most of the logic in content deals with tracking navigations, communicating with
+the renderer, and interacting with the //chrome client code.
+
+## Detailed Architecture
+In this section, '=>' represents strong ownership, and '~>' is a weak reference.
+
+### [core](/components/subresource_filter/core)
+#### [core/browser](/components/subresource_filter/core/browser)
+Code in core/browser is responsible for writing and indexing filtering rules.
+The class that does most of this work is the RulesetService.
+
+`BrowserProcessImpl`=>`ContentRulesetService`=>`RulesetService`
+
+The `RulesetService` is responsible for indexing filtering rules into a
+[Flatbuffer](https://google.github.io/flatbuffers/) format, and writing them to
+disk. These rules come from the `RulesetServiceDelegate` as an `UnindexedRuleset`
+which will be downloaded via the [Component Updater](/components/component_updater/README.md).
+It also performs other tasks like deleting obsolete rulesets.
+
+The code in this component also maintains a global `ConfigurationList`, which
+defines how the entire subresource_filter component behaves.
+
+#### [core/common](/components/subresource_filter/core/common)
+The code in core/common deals with logic involved in filtering subresources that
+is used in both browser and renderer processes. The most important class is the
+`DocumentSubresourceFilter` which contains logic to filter subresources in the
+scope of a given document.
+
+In the browser process ownership looks like:
+`ContentSubresourceFilterThrottleManager`=>`AsyncDocumentSubresourceFilter`=>`DocumentSubresourceFilter`
+
+In the renderer, ownership looks like:
+`DocumentLoader`=>`SubresourceFilter`=>`WebDocumentSubresourceFilterImpl`=>`DocumentSubresourceFilter`
+
+### [content](/components/subresource_filter/content)
+#### [content/browser](/components/subresource_filter/content/browser)
+The content/browser code generally orchestrates the whole component.
+
+##### Safe Browsing Integration - Page-level activation
+Filtering for a given page is (mostly) triggered via Safe Browsing. The core
+class that encapsulates that logic is the
+`SubresourceFilterSafeBrowsingActivationThrottle`.
+
+`SubresourceFilterSafeBrowsingActivationThrottle`=>`SubresourceFilterSafeBrowsingClient`=>`SubresourceFilterSafeBrowsingClientRequest`
+The Safe Browsing client owns multiple Safe Browsing requests, and lives on the
+IO thread.
+
+Currently, the `SubresourceFilterSafeBrowsingActivationThrottle` checks every
+redirect URL speculatively, but makes an activation decision based on the last
+URL.
+
+##### Document-level activation
+The ruleset has rules for whitelisting documents in specific ways. How a given
+document is activated is codified in the `ActivationState` struct.
+
+In order to notify a document in the renderer about how it should be activated,
+we read from the ruleset in the browser process and send an IPC to the frame at
+ReadyToCommitNavigationTime.
+
+This logic is Handled by the `ActivationStateComputingNavigationThrottle`.
+`ActivationStateComputingNavigationThrottle`=>`AsyncDocumentSubresourceFilter`
+This ownership is passed to the `ContentSubresourceFilterThrottleManager` at
+`ReadyToCommitNavigation` time.
+
+##### Subframe filtering
+This component also needs to filter subframes that match the ruleset. This is
+done by the `SubframeNavigationFilteringThrottle`, which consults its parent
+frame's `AsyncDocumentSubresourceFilter`.
+
+##### Throttle management
+The `ContentSubresourceFilterThrottleManager` is a `WebContentsObserver`, and manages both the
+`ActivationStateComputingNavigationThrottle` and the
+`SubframeNavigationFilteringThrottle`. It maintains a map of all the activated
+frames in the frame tree, along with that frame's current
+`AsyncDocumentSubresourceFilter`, taken from the
+`ActivationStateComputingNavigationThrottle`.
+
+`ContentSubresourceFilterThrottleManager`=>`AsyncDocumentSubresourceFilter`
+
+##### Ruleset management
+Ruleset management in the browser process is done with handles that live on the
+UI thread and access the ruleset asynchronously on a thread that allows IO.
+
+In order to avoid accessing a potentially corrupt mmapped flatbuffers file in
+the browser process, the handles also provide verification.
+
+`ContentRulesetService`=>`VerifiedRulesetDealer::Handle`=>`VerifiedRulesetDealer`
+`ContentSubresourceFilterThrottleManager`=>`VerifiedRuleset::Handle`=>`VerifiedRuleset`
+
+#### [content/renderer](/components/subresource_filter/content/renderer)
+The code in content/renderer deals with using the ruleset to match  and filter
+resource requests made in the render process.
+
+The most important class in the renderer is the `SubresourceFilterAgent`,
+the `RenderFrameObserver` that communicates with the
+`ContentSubresourceFilterThrottleManager`.
+
+`SubresourceFilterAgent`~>`WebDocumentSubresourceFilterImpl`
+
diff --git a/components/suggestions/BUILD.gn b/components/suggestions/BUILD.gn
index 1dc30fba..3e40c3a5 100644
--- a/components/suggestions/BUILD.gn
+++ b/components/suggestions/BUILD.gn
@@ -64,6 +64,7 @@
     ":suggestions",
     "//base/test:test_support",
     "//components/image_fetcher/core",
+    "//components/image_fetcher/core:test_support",
     "//components/leveldb_proto:test_support",
     "//components/sync",
     "//components/sync:test_support_driver",
diff --git a/components/suggestions/image_manager.cc b/components/suggestions/image_manager.cc
index 58db8e2e..9e63123 100644
--- a/components/suggestions/image_manager.cc
+++ b/components/suggestions/image_manager.cc
@@ -32,15 +32,6 @@
                                            encoded_data->size());
 }
 
-// Wraps an ImageManager callback so that it can be used with the ImageFetcher.
-void WrapCallback(
-    const suggestions::ImageManager::ImageCallback& wrapped_callback,
-    const std::string& url,
-    const gfx::Image& image,
-    const image_fetcher::RequestMetadata& metadata) {
-  wrapped_callback.Run(GURL(url), image);
-}
-
 constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
     net::DefineNetworkTrafficAnnotation("suggestions_image_manager", R"(
         semantics {
@@ -90,7 +81,6 @@
           {base::TaskPriority::USER_VISIBLE})),
       database_ready_(false),
       weak_ptr_factory_(this) {
-  image_fetcher_->SetImageFetcherDelegate(this);
   database_->Init(kDatabaseUMAClientName, database_dir,
                   leveldb_proto::CreateSimpleOptions(),
                   base::Bind(&ImageManager::OnDatabaseInit,
@@ -142,17 +132,23 @@
   ServeFromCacheOrNetwork(url, image_url, callback);
 }
 
-void ImageManager::OnImageFetched(const std::string& url,
-                                  const gfx::Image& image) {
+void ImageManager::SaveImageAndForward(
+    const ImageCallback& image_callback,
+    const std::string& url,
+    const gfx::Image& image,
+    const image_fetcher::RequestMetadata& metadata) {
   // |image| can be empty if image fetch was unsuccessful.
   if (!image.IsEmpty())
     SaveImage(url, *image.ToSkBitmap());
+
+  image_callback.Run(GURL(url), image);
 }
 
 bool ImageManager::GetImageURL(const GURL& url, GURL* image_url) {
   DCHECK(image_url);
   std::map<GURL, GURL>::iterator it = image_url_map_.find(url);
-  if (it == image_url_map_.end()) return false;  // Not found.
+  if (it == image_url_map_.end())
+    return false;  // Not found.
   *image_url = it->second;
   return true;
 }
@@ -175,16 +171,17 @@
   pending_cache_requests_[url] = request;
 }
 
-void ImageManager::OnCacheImageDecoded(
-    const GURL& url,
-    const GURL& image_url,
-    const ImageCallback& callback,
-    std::unique_ptr<SkBitmap> bitmap) {
+void ImageManager::OnCacheImageDecoded(const GURL& url,
+                                       const GURL& image_url,
+                                       const ImageCallback& callback,
+                                       std::unique_ptr<SkBitmap> bitmap) {
   if (bitmap.get()) {
     callback.Run(url, gfx::Image::CreateFrom1xBitmap(*bitmap));
   } else {
-    image_fetcher_->StartOrQueueNetworkRequest(
-        url.spec(), image_url, base::Bind(&WrapCallback, callback),
+    image_fetcher_->FetchImage(
+        url.spec(), image_url,
+        base::BindRepeating(&ImageManager::SaveImageAndForward,
+                            base::Unretained(this), callback),
         kTrafficAnnotation);
   }
 }
@@ -198,10 +195,9 @@
   return nullptr;
 }
 
-void ImageManager::ServeFromCacheOrNetwork(
-    const GURL& url,
-    const GURL& image_url,
-    ImageCallback callback) {
+void ImageManager::ServeFromCacheOrNetwork(const GURL& url,
+                                           const GURL& image_url,
+                                           ImageCallback callback) {
   scoped_refptr<base::RefCountedMemory> encoded_data =
       GetEncodedImageFromCache(url);
   if (encoded_data.get()) {
@@ -211,8 +207,10 @@
         base::Bind(&ImageManager::OnCacheImageDecoded,
                    weak_ptr_factory_.GetWeakPtr(), url, image_url, callback));
   } else {
-    image_fetcher_->StartOrQueueNetworkRequest(
-        url.spec(), image_url, base::Bind(&WrapCallback, callback),
+    image_fetcher_->FetchImage(
+        url.spec(), image_url,
+        base::BindRepeating(&ImageManager::SaveImageAndForward,
+                            base::Unretained(this), callback),
         kTrafficAnnotation);
   }
 }
@@ -229,7 +227,8 @@
   // Update the image map.
   image_map_.insert({url, encoded_data});
 
-  if (!database_ready_) return;
+  if (!database_ready_)
+    return;
 
   // Save the resulting bitmap to the database.
   ImageData data;
diff --git a/components/suggestions/image_manager.h b/components/suggestions/image_manager.h
index b48f14da..d8060ed 100644
--- a/components/suggestions/image_manager.h
+++ b/components/suggestions/image_manager.h
@@ -18,7 +18,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/task_runner.h"
 #include "base/threading/thread_checker.h"
-#include "components/image_fetcher/core/image_fetcher_delegate.h"
 #include "components/leveldb_proto/proto_database.h"
 #include "components/suggestions/proto/suggestions.pb.h"
 #include "ui/gfx/image/image_skia.h"
@@ -30,7 +29,8 @@
 
 namespace image_fetcher {
 class ImageFetcher;
-}
+struct RequestMetadata;
+}  // namespace image_fetcher
 
 namespace suggestions {
 
@@ -39,16 +39,17 @@
 
 // A class used to fetch server images asynchronously and manage the caching
 // layer (both in memory and on disk).
-class ImageManager : public image_fetcher::ImageFetcherDelegate {
+class ImageManager {
  public:
   typedef std::vector<ImageData> ImageDataVector;
-  using ImageCallback = base::Callback<void(const GURL&, const gfx::Image&)>;
+  using ImageCallback =
+      base::RepeatingCallback<void(const GURL&, const gfx::Image&)>;
 
   ImageManager(
       std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
       std::unique_ptr<leveldb_proto::ProtoDatabase<ImageData>> database,
       const base::FilePath& database_dir);
-  ~ImageManager() override;
+  virtual ~ImageManager();
 
   virtual void Initialize(const SuggestionsProfile& suggestions);
 
@@ -58,12 +59,6 @@
   // Should be called from the UI thread.
   virtual void GetImageForURL(const GURL& url, ImageCallback callback);
 
- protected:
-  // Methods inherited from image_fetcher::ImageFetcherDelegate
-
-  // Perform additional tasks when an image has been fetched.
-  void OnImageFetched(const std::string& url, const gfx::Image& image) override;
-
  private:
   friend class MockImageManager;
   friend class ImageManagerTest;
@@ -77,8 +72,7 @@
   // Used for testing.
   ImageManager();
 
-  typedef std::vector<base::Callback<void(const GURL&, const gfx::Image&)> >
-      CallbackVector;
+  typedef std::vector<ImageCallback> CallbackVector;
   typedef base::hash_map<std::string, scoped_refptr<base::RefCountedMemory>>
       ImageMap;
 
@@ -106,14 +100,14 @@
                          const GURL& image_url,
                          ImageCallback callback);
 
-  void ServeFromCacheOrNetwork(
-      const GURL& url, const GURL& image_url, ImageCallback callback);
+  void ServeFromCacheOrNetwork(const GURL& url,
+                               const GURL& image_url,
+                               ImageCallback callback);
 
-  void OnCacheImageDecoded(
-      const GURL& url,
-      const GURL& image_url,
-      const ImageCallback& callback,
-      std::unique_ptr<SkBitmap> bitmap);
+  void OnCacheImageDecoded(const GURL& url,
+                           const GURL& image_url,
+                           const ImageCallback& callback,
+                           std::unique_ptr<SkBitmap> bitmap);
 
   // Returns null if the |url| had no entry in the cache.
   scoped_refptr<base::RefCountedMemory> GetEncodedImageFromCache(
@@ -134,6 +128,11 @@
 
   void ServePendingCacheRequests();
 
+  void SaveImageAndForward(const ImageCallback& image_callback,
+                           const std::string& url,
+                           const gfx::Image& image,
+                           const image_fetcher::RequestMetadata& metadata);
+
   // Map from URL to image URL. Should be kept up to date when a new
   // SuggestionsProfile is available.
   std::map<GURL, GURL> image_url_map_;
diff --git a/components/suggestions/image_manager_unittest.cc b/components/suggestions/image_manager_unittest.cc
index e7afb37..e4a837d 100644
--- a/components/suggestions/image_manager_unittest.cc
+++ b/components/suggestions/image_manager_unittest.cc
@@ -12,7 +12,7 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "components/image_fetcher/core/image_fetcher.h"
-#include "components/image_fetcher/core/image_fetcher_delegate.h"
+#include "components/image_fetcher/core/mock_image_fetcher.h"
 #include "components/leveldb_proto/proto_database.h"
 #include "components/leveldb_proto/testing/fake_db.h"
 #include "components/suggestions/image_encoder.h"
@@ -28,8 +28,7 @@
 using ::testing::StrictMock;
 using ::testing::_;
 
-using image_fetcher::ImageFetcher;
-using image_fetcher::ImageFetcherDelegate;
+using image_fetcher::MockImageFetcher;
 
 namespace suggestions {
 
@@ -42,24 +41,9 @@
 
 typedef std::map<std::string, ImageData> EntryMap;
 
-void AddEntry(const ImageData& d, EntryMap* map) { (*map)[d.url()] = d; }
-
-class MockImageFetcher : public ImageFetcher {
- public:
-  MockImageFetcher() {}
-  virtual ~MockImageFetcher() {}
-  MOCK_METHOD4(StartOrQueueNetworkRequest,
-               void(const std::string&,
-                    const GURL&,
-                    const ImageFetcherCallback&,
-                    const net::NetworkTrafficAnnotationTag&));
-  MOCK_METHOD1(SetImageFetcherDelegate, void(ImageFetcherDelegate*));
-  MOCK_METHOD1(SetDataUseServiceName, void(DataUseServiceName));
-  MOCK_METHOD1(SetImageDownloadLimit,
-               void(base::Optional<int64_t> max_download_bytes));
-  MOCK_METHOD1(SetDesiredImageFrameSize, void(const gfx::Size&));
-  MOCK_METHOD0(GetImageDecoder, image_fetcher::ImageDecoder*());
-};
+void AddEntry(const ImageData& d, EntryMap* map) {
+  (*map)[d.url()] = d;
+}
 
 class ImageManagerTest : public testing::Test {
  public:
@@ -112,7 +96,8 @@
     return data;
   }
 
-  void OnImageAvailable(base::RunLoop* loop, const GURL& url,
+  void OnImageAvailable(base::RunLoop* loop,
+                        const GURL& url,
                         const gfx::Image& image) {
     if (!image.IsEmpty()) {
       num_callback_valid_called_++;
@@ -124,7 +109,6 @@
 
   ImageManager* CreateImageManager(FakeDB<ImageData>* fake_db) {
     mock_image_fetcher_ = new StrictMock<MockImageFetcher>();
-    EXPECT_CALL(*mock_image_fetcher_, SetImageFetcherDelegate(_));
     return new ImageManager(base::WrapUnique(mock_image_fetcher_),
                             base::WrapUnique(fake_db),
                             FakeDB<ImageData>::DirectoryForTestDB());
@@ -177,7 +161,7 @@
   InitializeDefaultImageMapAndDatabase(image_manager_.get(), fake_db_);
 
   // We expect the fetcher to go to network and call the callback.
-  EXPECT_CALL(*mock_image_fetcher_, StartOrQueueNetworkRequest(_, _, _, _));
+  EXPECT_CALL(*mock_image_fetcher_, FetchImageAndData_(_, _, _, _, _));
 
   // Fetch existing URL.
   base::RunLoop run_loop;
diff --git a/components/sync/BUILD.gn b/components/sync/BUILD.gn
index fcc1896..1d6f4d6 100644
--- a/components/sync/BUILD.gn
+++ b/components/sync/BUILD.gn
@@ -1062,6 +1062,7 @@
     deps = [
       ":test_support_proto_java_prepare",
     ]
+    generate_lite = true
   }
 
   android_library("sync_java_test_support") {
diff --git a/components/ukm/test_ukm_recorder.cc b/components/ukm/test_ukm_recorder.cc
index 954cba4..3e4c88a8 100644
--- a/components/ukm/test_ukm_recorder.cc
+++ b/components/ukm/test_ukm_recorder.cc
@@ -67,7 +67,13 @@
 };
 
 bool TestUkmRecorder::ShouldRestrictToWhitelistedSourceIds() const {
-  // In tests, we want to record all source ids (not just hose that are
+  // In tests, we want to record all source ids (not just those that are
+  // whitelisted).
+  return false;
+}
+
+bool TestUkmRecorder::ShouldRestrictToWhitelistedEntries() const {
+  // In tests, we want to record all entries (not just those that are
   // whitelisted).
   return false;
 }
diff --git a/components/ukm/test_ukm_recorder.h b/components/ukm/test_ukm_recorder.h
index d540b438..bbc6c74 100644
--- a/components/ukm/test_ukm_recorder.h
+++ b/components/ukm/test_ukm_recorder.h
@@ -29,6 +29,7 @@
   ~TestUkmRecorder() override;
 
   bool ShouldRestrictToWhitelistedSourceIds() const override;
+  bool ShouldRestrictToWhitelistedEntries() const override;
 
   size_t sources_count() const { return sources().size(); }
 
diff --git a/components/ukm/ukm_recorder_impl.cc b/components/ukm/ukm_recorder_impl.cc
index d4c91dec..25a3a6885 100644
--- a/components/ukm/ukm_recorder_impl.cc
+++ b/components/ukm/ukm_recorder_impl.cc
@@ -306,6 +306,10 @@
       kUkmFeature, "RestrictToWhitelistedSourceIds", false);
 }
 
+bool UkmRecorderImpl::ShouldRestrictToWhitelistedEntries() const {
+  return true;
+}
+
 UkmRecorderImpl::EventAggregate::EventAggregate() = default;
 UkmRecorderImpl::EventAggregate::~EventAggregate() = default;
 
@@ -371,7 +375,7 @@
     return;
   }
 
-  if (!whitelisted_entry_hashes_.empty() &&
+  if (ShouldRestrictToWhitelistedEntries() &&
       !base::ContainsKey(whitelisted_entry_hashes_, entry->event_hash)) {
     RecordDroppedEntry(DroppedDataReason::NOT_WHITELISTED);
     return;
diff --git a/components/ukm/ukm_recorder_impl.h b/components/ukm/ukm_recorder_impl.h
index f9d2b04c..618dad09 100644
--- a/components/ukm/ukm_recorder_impl.h
+++ b/components/ukm/ukm_recorder_impl.h
@@ -69,6 +69,8 @@
 
   virtual bool ShouldRestrictToWhitelistedSourceIds() const;
 
+  virtual bool ShouldRestrictToWhitelistedEntries() const;
+
  private:
   friend ::metrics::UkmBrowserTest;
   friend ::metrics::UkmEGTestHelper;
diff --git a/components/ukm/ukm_service_unittest.cc b/components/ukm/ukm_service_unittest.cc
index 101f1f7..e01283f7 100644
--- a/components/ukm/ukm_service_unittest.cc
+++ b/components/ukm/ukm_service_unittest.cc
@@ -184,6 +184,10 @@
 }
 
 TEST_F(UkmServiceTest, PersistAndPurge) {
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", "PageLoad"}});
+
   UkmService service(&prefs_, &client_);
   TestRecordingHelper recorder(&service);
   EXPECT_EQ(GetPersistedLogCount(), 0);
@@ -237,6 +241,10 @@
 }
 
 TEST_F(UkmServiceTest, EntryBuilderAndSerialization) {
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", "foo,bar"}});
+
   UkmService service(&prefs_, &client_);
   TestRecordingHelper recorder(&service);
   EXPECT_EQ(0, GetPersistedLogCount());
@@ -264,12 +272,12 @@
 
   Report proto_report = GetPersistedReport();
 
-  EXPECT_EQ(1, proto_report.sources_size());
+  ASSERT_EQ(1, proto_report.sources_size());
   const Source& proto_source = proto_report.sources(0);
   EXPECT_EQ(GURL("https://google.com/foobar").spec(), proto_source.url());
   EXPECT_EQ(id, proto_source.id());
 
-  EXPECT_EQ(2, proto_report.entries_size());
+  ASSERT_EQ(2, proto_report.entries_size());
 
   // Bar entry is the 0th entry here because bar_builder is destructed before
   // foo_builder: the reverse order as they are constructed. To have the same
@@ -278,7 +286,7 @@
   const Entry& proto_entry_bar = proto_report.entries(0);
   EXPECT_EQ(id, proto_entry_bar.source_id());
   EXPECT_EQ(base::HashMetricName("bar"), proto_entry_bar.event_hash());
-  EXPECT_EQ(2, proto_entry_bar.metrics_size());
+  ASSERT_EQ(2, proto_entry_bar.metrics_size());
   const Entry::Metric proto_entry_bar_start = proto_entry_bar.metrics(0);
   EXPECT_EQ(base::HashMetricName("bar_start"),
             proto_entry_bar_start.metric_hash());
@@ -290,7 +298,7 @@
   const Entry& proto_entry_foo = proto_report.entries(1);
   EXPECT_EQ(id, proto_entry_foo.source_id());
   EXPECT_EQ(base::HashMetricName("foo"), proto_entry_foo.event_hash());
-  EXPECT_EQ(2, proto_entry_foo.metrics_size());
+  ASSERT_EQ(2, proto_entry_foo.metrics_size());
   const Entry::Metric proto_entry_foo_start = proto_entry_foo.metrics(0);
   EXPECT_EQ(base::HashMetricName("foo_start"),
             proto_entry_foo_start.metric_hash());
@@ -301,9 +309,13 @@
 }
 
 TEST_F(UkmServiceTest, AddEntryWithEmptyMetrics) {
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", "PageLoad"}});
+
   UkmService service(&prefs_, &client_);
   TestRecordingHelper recorder(&service);
-  EXPECT_EQ(0, GetPersistedLogCount());
+  ASSERT_EQ(0, GetPersistedLogCount());
   service.Initialize();
   task_runner_->RunUntilIdle();
   service.EnableRecording(/*extensions=*/false);
@@ -314,12 +326,16 @@
 
   { ::ukm::builders::PageLoad(id).Record(&service); }
   service.Flush();
-  EXPECT_EQ(1, GetPersistedLogCount());
+  ASSERT_EQ(1, GetPersistedLogCount());
   Report proto_report = GetPersistedReport();
   EXPECT_EQ(1, proto_report.entries_size());
 }
 
 TEST_F(UkmServiceTest, MetricsProviderTest) {
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", "PageLoad"}});
+
   UkmService service(&prefs_, &client_);
   TestRecordingHelper recorder(&service);
 
@@ -395,6 +411,11 @@
 }
 
 TEST_F(UkmServiceTest, LogsUploadedOnlyWhenHavingSourcesOrEntries) {
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  // Testing two whitelisted Entries.
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", "PageLoad"}});
+
   UkmService service(&prefs_, &client_);
   TestRecordingHelper recorder(&service);
   EXPECT_EQ(GetPersistedLogCount(), 0);
@@ -493,7 +514,8 @@
     ScopedUkmFeatureParams params(
         base::FeatureList::OVERRIDE_ENABLE_FEATURE,
         {{"RestrictToWhitelistedSourceIds",
-          restrict_to_whitelisted_source_ids ? "true" : "false"}});
+          restrict_to_whitelisted_source_ids ? "true" : "false"},
+         {"WhitelistEntries", "FakeEntry"}});
 
     ClearPrefs();
     UkmService service(&prefs_, &client_);
@@ -514,9 +536,9 @@
     recorder.GetEntryBuilder(id2, "FakeEntry");
 
     service.Flush();
-    EXPECT_EQ(GetPersistedLogCount(), 1);
+    ASSERT_EQ(GetPersistedLogCount(), 1);
     Report proto_report = GetPersistedReport();
-    EXPECT_GE(proto_report.sources_size(), 1);
+    ASSERT_GE(proto_report.sources_size(), 1);
 
     // The whitelisted source should always be recorded.
     const Source& proto_source1 = proto_report.sources(0);
@@ -526,9 +548,9 @@
     // The non-whitelisted source should only be recorded if we aren't
     // restricted to whitelisted source ids.
     if (restrict_to_whitelisted_source_ids) {
-      EXPECT_EQ(1, proto_report.sources_size());
+      ASSERT_EQ(1, proto_report.sources_size());
     } else {
-      EXPECT_EQ(2, proto_report.sources_size());
+      ASSERT_EQ(2, proto_report.sources_size());
       const Source& proto_source2 = proto_report.sources(1);
       EXPECT_EQ(id2, proto_source2.id());
       EXPECT_EQ(kURL.spec(), proto_source2.url());
@@ -811,14 +833,15 @@
   };
 
   base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
-  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE, {});
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", "FakeEntry"}});
 
   for (const auto& test : test_cases) {
     ClearPrefs();
     UkmService service(&prefs_, &client_);
     TestRecordingHelper recorder(&service);
 
-    EXPECT_EQ(GetPersistedLogCount(), 0);
+    ASSERT_EQ(GetPersistedLogCount(), 0);
     service.Initialize();
     task_runner_->RunUntilIdle();
     service.EnableRecording(/*extensions=*/false);
@@ -834,21 +857,21 @@
     recorder.GetEntryBuilder(nonwhitelist_id, "FakeEntry");
 
     service.Flush();
-    EXPECT_EQ(1, GetPersistedLogCount());
+    ASSERT_EQ(1, GetPersistedLogCount());
     auto proto_report = GetPersistedReport();
 
     EXPECT_EQ(2, proto_report.source_counts().observed());
     EXPECT_EQ(1, proto_report.source_counts().navigation_sources());
     if (test.expected_kept) {
       EXPECT_EQ(0, proto_report.source_counts().unmatched_sources());
-      EXPECT_EQ(2, proto_report.sources_size());
+      ASSERT_EQ(2, proto_report.sources_size());
       EXPECT_EQ(whitelist_id, proto_report.sources(0).id());
       EXPECT_EQ(kURL, proto_report.sources(0).url());
       EXPECT_EQ(nonwhitelist_id, proto_report.sources(1).id());
       EXPECT_EQ(test.url, proto_report.sources(1).url());
     } else {
       EXPECT_EQ(1, proto_report.source_counts().unmatched_sources());
-      EXPECT_EQ(1, proto_report.sources_size());
+      ASSERT_EQ(1, proto_report.sources_size());
       EXPECT_EQ(whitelist_id, proto_report.sources(0).id());
       EXPECT_EQ(kURL, proto_report.sources(0).url());
     }
@@ -895,7 +918,8 @@
   };
 
   base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
-  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE, {});
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", "FakeEntry"}});
 
   for (const auto& test : test_cases) {
     ClearPrefs();
@@ -917,7 +941,7 @@
     recorder.UpdateSourceURL(nonwhitelist_id1, test.source1_url);
 
     service.Flush();
-    EXPECT_EQ(1, GetPersistedLogCount());
+    ASSERT_EQ(1, GetPersistedLogCount());
     auto proto_report = GetPersistedReport();
 
     EXPECT_EQ(2, proto_report.source_counts().observed());
@@ -930,7 +954,7 @@
       EXPECT_EQ(1, proto_report.source_counts().unmatched_sources());
       EXPECT_EQ(0, proto_report.source_counts().deferred_sources());
     }
-    EXPECT_EQ(1, proto_report.sources_size());
+    ASSERT_EQ(1, proto_report.sources_size());
     EXPECT_EQ(whitelist_id, proto_report.sources(0).id());
     EXPECT_EQ(kURL, proto_report.sources(0).url());
 
@@ -941,7 +965,7 @@
     recorder.GetEntryBuilder(nonwhitelist_id2, "FakeEntry");
 
     service.Flush();
-    EXPECT_EQ(2, GetPersistedLogCount());
+    ASSERT_EQ(2, GetPersistedLogCount());
     proto_report = GetPersistedReport();
 
     EXPECT_EQ(1, proto_report.source_counts().observed());
@@ -951,17 +975,17 @@
       EXPECT_FALSE(test.expect_source2);
       EXPECT_EQ(1, proto_report.source_counts().unmatched_sources());
       EXPECT_EQ(0, proto_report.source_counts().carryover_sources());
-      EXPECT_EQ(0, proto_report.sources_size());
+      ASSERT_EQ(0, proto_report.sources_size());
     } else if (!test.expect_source2) {
       EXPECT_EQ(1, proto_report.source_counts().unmatched_sources());
       EXPECT_EQ(1, proto_report.source_counts().carryover_sources());
-      EXPECT_EQ(1, proto_report.sources_size());
+      ASSERT_EQ(1, proto_report.sources_size());
       EXPECT_EQ(nonwhitelist_id1, proto_report.sources(0).id());
       EXPECT_EQ(test.source1_url, proto_report.sources(0).url());
     } else {
       EXPECT_EQ(0, proto_report.source_counts().unmatched_sources());
       EXPECT_EQ(1, proto_report.source_counts().carryover_sources());
-      EXPECT_EQ(2, proto_report.sources_size());
+      ASSERT_EQ(2, proto_report.sources_size());
       EXPECT_EQ(nonwhitelist_id1, proto_report.sources(0).id());
       EXPECT_EQ(test.source1_url, proto_report.sources(0).url());
       EXPECT_EQ(nonwhitelist_id2, proto_report.sources(1).id());
diff --git a/components/update_client/update_engine.cc b/components/update_client/update_engine.cc
index b8341ed..794639e 100644
--- a/components/update_client/update_engine.cc
+++ b/components/update_client/update_engine.cc
@@ -94,9 +94,9 @@
 
   DCHECK(result.second);
 
-  const auto it = result.first;
-  const auto& update_context = *it;
+  const auto update_context = *result.first;
   DCHECK(update_context);
+  DCHECK(!update_context->session_id.empty());
 
   // Calls out to get the corresponding CrxComponent data for the CRXs in this
   // update context.
@@ -123,16 +123,14 @@
     // Handle |kNew| state. This will transition the components to |kChecking|.
     component.Handle(
         base::BindOnce(&UpdateEngine::ComponentCheckingForUpdatesStart,
-                       base::Unretained(this), it, id));
+                       base::Unretained(this), update_context, id));
   }
 }
 
 void UpdateEngine::ComponentCheckingForUpdatesStart(
-    const UpdateContextIterator it,
+    scoped_refptr<UpdateContext> update_context,
     const std::string& id) {
   DCHECK(thread_checker_.CalledOnValidThread());
-
-  const auto& update_context = *it;
   DCHECK(update_context);
 
   DCHECK_EQ(1u, update_context->components.count(id));
@@ -142,7 +140,7 @@
   auto& component = *update_context->components.at(id);
   component.Handle(
       base::BindOnce(&UpdateEngine::ComponentCheckingForUpdatesComplete,
-                     base::Unretained(this), it));
+                     base::Unretained(this), update_context));
 
   ++update_context->num_components_ready_to_check;
   if (update_context->num_components_ready_to_check <
@@ -151,14 +149,12 @@
   }
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&UpdateEngine::DoUpdateCheck, base::Unretained(this), it));
+      FROM_HERE, base::BindOnce(&UpdateEngine::DoUpdateCheck,
+                                base::Unretained(this), update_context));
 }
 
-void UpdateEngine::DoUpdateCheck(const UpdateContextIterator it) {
+void UpdateEngine::DoUpdateCheck(scoped_refptr<UpdateContext> update_context) {
   DCHECK(thread_checker_.CalledOnValidThread());
-
-  const auto& update_context = *it;
   DCHECK(update_context);
 
   update_context->update_checker =
@@ -169,15 +165,13 @@
       update_context->components, config_->ExtraRequestParams(),
       update_context->enabled_component_updates,
       base::BindOnce(&UpdateEngine::UpdateCheckDone, base::Unretained(this),
-                     it));
+                     update_context));
 }
 
-void UpdateEngine::UpdateCheckDone(const UpdateContextIterator it,
+void UpdateEngine::UpdateCheckDone(scoped_refptr<UpdateContext> update_context,
                                    int error,
                                    int retry_after_sec) {
   DCHECK(thread_checker_.CalledOnValidThread());
-
-  const auto& update_context = *it;
   DCHECK(update_context);
 
   update_context->retry_after_sec = retry_after_sec;
@@ -207,10 +201,8 @@
 }
 
 void UpdateEngine::ComponentCheckingForUpdatesComplete(
-    const UpdateContextIterator it) {
+    scoped_refptr<UpdateContext> update_context) {
   DCHECK(thread_checker_.CalledOnValidThread());
-
-  const auto& update_context = *it;
   DCHECK(update_context);
 
   ++update_context->num_components_checked;
@@ -220,13 +212,12 @@
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(&UpdateEngine::UpdateCheckComplete,
-                                base::Unretained(this), it));
+                                base::Unretained(this), update_context));
 }
 
-void UpdateEngine::UpdateCheckComplete(const UpdateContextIterator it) {
+void UpdateEngine::UpdateCheckComplete(
+    scoped_refptr<UpdateContext> update_context) {
   DCHECK(thread_checker_.CalledOnValidThread());
-
-  const auto& update_context = *it;
   DCHECK(update_context);
 
   for (const auto& id : update_context->ids)
@@ -234,13 +225,12 @@
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
-                                base::Unretained(this), it));
+                                base::Unretained(this), update_context));
 }
 
-void UpdateEngine::HandleComponent(const UpdateContextIterator it) {
+void UpdateEngine::HandleComponent(
+    scoped_refptr<UpdateContext> update_context) {
   DCHECK(thread_checker_.CalledOnValidThread());
-
-  const auto& update_context = *it;
   DCHECK(update_context);
 
   auto& queue = update_context->component_queue;
@@ -251,8 +241,9 @@
                             : Error::NONE;
 
     base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(&UpdateEngine::UpdateComplete,
-                                  base::Unretained(this), it, error));
+        FROM_HERE,
+        base::BindOnce(&UpdateEngine::UpdateComplete, base::Unretained(this),
+                       update_context, error));
     return;
   }
 
@@ -261,12 +252,12 @@
   const auto& component = update_context->components.at(id);
   DCHECK(component);
 
-  auto& next_update_delay = (*it)->next_update_delay;
+  auto& next_update_delay = update_context->next_update_delay;
   if (!next_update_delay.is_zero() && component->IsUpdateAvailable()) {
     base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
         FROM_HERE,
         base::BindOnce(&UpdateEngine::HandleComponent, base::Unretained(this),
-                       it),
+                       update_context),
         next_update_delay);
     next_update_delay = base::TimeDelta();
 
@@ -276,13 +267,12 @@
   }
 
   component->Handle(base::BindOnce(&UpdateEngine::HandleComponentComplete,
-                                   base::Unretained(this), it));
+                                   base::Unretained(this), update_context));
 }
 
-void UpdateEngine::HandleComponentComplete(const UpdateContextIterator it) {
+void UpdateEngine::HandleComponentComplete(
+    scoped_refptr<UpdateContext> update_context) {
   DCHECK(thread_checker_.CalledOnValidThread());
-
-  const auto& update_context = *it;
   DCHECK(update_context);
 
   auto& queue = update_context->component_queue;
@@ -306,18 +296,23 @@
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
-                                base::Unretained(this), it));
+                                base::Unretained(this), update_context));
 }
 
-void UpdateEngine::UpdateComplete(const UpdateContextIterator it, Error error) {
+void UpdateEngine::UpdateComplete(scoped_refptr<UpdateContext> update_context,
+                                  Error error) {
   DCHECK(thread_checker_.CalledOnValidThread());
-
-  const auto& update_context = *it;
   DCHECK(update_context);
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(std::move(update_context->callback), error));
 
+  const auto it =
+      std::find_if(update_contexts_.begin(), update_contexts_.end(),
+                   [update_context](scoped_refptr<UpdateContext> other) {
+                     return update_context->session_id == other->session_id;
+                   });
+  DCHECK(it != update_contexts_.end());
   update_contexts_.erase(it);
 }
 
@@ -362,9 +357,7 @@
           nullptr));
 
   DCHECK(result.second);
-
-  const auto it = result.first;
-  const auto& update_context = *it;
+  const auto update_context = *result.first;
   DCHECK(update_context);
   DCHECK_EQ(1u, update_context->ids.size());
   DCHECK_EQ(1u, update_context->components.count(id));
@@ -376,7 +369,7 @@
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
-                                base::Unretained(this), it));
+                                base::Unretained(this), update_context));
 }
 
 }  // namespace update_client
diff --git a/components/update_client/update_engine.h b/components/update_client/update_engine.h
index 242a0b3..067f015 100644
--- a/components/update_client/update_engine.h
+++ b/components/update_client/update_engine.h
@@ -5,7 +5,6 @@
 #ifndef COMPONENTS_UPDATE_CLIENT_UPDATE_ENGINE_H_
 #define COMPONENTS_UPDATE_CLIENT_UPDATE_ENGINE_H_
 
-#include <iterator>
 #include <list>
 #include <map>
 #include <memory>
@@ -69,22 +68,23 @@
   ~UpdateEngine();
 
   using UpdateContexts = std::set<scoped_refptr<UpdateContext>>;
-  using UpdateContextIterator = UpdateContexts::iterator;
 
-  void UpdateComplete(UpdateContextIterator it, Error error);
+  void UpdateComplete(scoped_refptr<UpdateContext> update_context, Error error);
 
-  void ComponentCheckingForUpdatesStart(UpdateContextIterator it,
-                                        const std::string& id);
-  void ComponentCheckingForUpdatesComplete(UpdateContextIterator it);
-  void UpdateCheckComplete(UpdateContextIterator it);
+  void ComponentCheckingForUpdatesStart(
+      scoped_refptr<UpdateContext> update_context,
+      const std::string& id);
+  void ComponentCheckingForUpdatesComplete(
+      scoped_refptr<UpdateContext> update_context);
+  void UpdateCheckComplete(scoped_refptr<UpdateContext> update_context);
 
-  void DoUpdateCheck(UpdateContextIterator it);
-  void UpdateCheckDone(UpdateContextIterator it,
+  void DoUpdateCheck(scoped_refptr<UpdateContext> update_context);
+  void UpdateCheckDone(scoped_refptr<UpdateContext> update_context,
                        int error,
                        int retry_after_sec);
 
-  void HandleComponent(UpdateContextIterator it);
-  void HandleComponentComplete(UpdateContextIterator it);
+  void HandleComponent(scoped_refptr<UpdateContext> update_context);
+  void HandleComponentComplete(scoped_refptr<UpdateContext> update_context);
 
   // Returns true if the update engine rejects this update call because it
   // occurs too soon.
diff --git a/content/browser/accessibility/accessibility_action_browsertest.cc b/content/browser/accessibility/accessibility_action_browsertest.cc
index 96a46abd..cad16f8da 100644
--- a/content/browser/accessibility/accessibility_action_browsertest.cc
+++ b/content/browser/accessibility/accessibility_action_browsertest.cc
@@ -164,12 +164,12 @@
            "  c.beginPath();\n"
            "  c.moveTo(0, 0.5);\n"
            "  c.lineTo(4, 0.5);\n"
-           "  c.strokeStyle = '#ff0000';\n"
+           "  c.strokeStyle = '%23ff0000';\n"
            "  c.stroke();\n"
            "  c.beginPath();\n"
            "  c.moveTo(0, 1.5);\n"
            "  c.lineTo(4, 1.5);\n"
-           "  c.strokeStyle = '#0000ff';\n"
+           "  c.strokeStyle = '%230000ff';\n"
            "  c.stroke();\n"
            "</script>"
            "</body>");
@@ -211,9 +211,9 @@
            "<canvas aria-label='canvas' id='c' width='40' height='20'></canvas>"
            "<script>\n"
            "  var c = document.getElementById('c').getContext('2d');\n"
-           "  c.fillStyle = '#00ff00';\n"
+           "  c.fillStyle = '%2300ff00';\n"
            "  c.fillRect(0, 0, 40, 10);\n"
-           "  c.fillStyle = '#ff00ff';\n"
+           "  c.fillStyle = '%23ff00ff';\n"
            "  c.fillRect(0, 10, 40, 10);\n"
            "</script>"
            "</body>");
diff --git a/content/browser/accessibility/accessibility_win_browsertest.cc b/content/browser/accessibility/accessibility_win_browsertest.cc
index 61abcad..764e5365 100644
--- a/content/browser/accessibility/accessibility_win_browsertest.cc
+++ b/content/browser/accessibility/accessibility_win_browsertest.cc
@@ -15,6 +15,7 @@
 #include "base/process/process_handle.h"
 #include "base/strings/pattern.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/win/scoped_bstr.h"
 #include "base/win/scoped_variant.h"
@@ -119,7 +120,9 @@
   AccessibilityNotificationWaiter waiter(shell()->web_contents(),
                                          accessibility_mode,
                                          ax::mojom::Event::kLoadComplete);
-  GURL html_data_url("data:text/html," + html);
+  std::string encoded_html;
+  base::ReplaceChars(html, "#", "%23", &encoded_html);
+  GURL html_data_url("data:text/html," + encoded_html);
   NavigateToURL(shell(), html_data_url);
   waiter.WaitForNotification();
 }
diff --git a/content/browser/browser_side_navigation_browsertest.cc b/content/browser/browser_side_navigation_browsertest.cc
index e303622..7cf884e7 100644
--- a/content/browser/browser_side_navigation_browsertest.cc
+++ b/content/browser/browser_side_navigation_browsertest.cc
@@ -866,9 +866,7 @@
       shell(),
       "window.domAutomationController.send(document.body.textContent);",
       &body));
-  // TODO(arthursonzogni): This is wrong. The correct value for |body| is
-  // "body", not "body#foo". See https://crbug.com/123004.
-  EXPECT_EQ("body#foo", body);
+  EXPECT_EQ("body", body);
 
   std::string reference_fragment;
   EXPECT_TRUE(ExecuteScriptAndExtractString(
diff --git a/content/browser/devtools/devtools_session.cc b/content/browser/devtools/devtools_session.cc
index dc64ea9f..0621f7e 100644
--- a/content/browser/devtools/devtools_session.cc
+++ b/content/browser/devtools/devtools_session.cc
@@ -24,7 +24,9 @@
          method == "Debugger.setBreakpointByUrl" ||
          method == "Debugger.removeBreakpoint" ||
          method == "Debugger.setBreakpointsActive" ||
-         method == "Performance.getMetrics" || method == "Page.crash";
+         method == "Performance.getMetrics" || method == "Page.crash" ||
+         method == "Runtime.terminateExecution" ||
+         method == "Emulation.setScriptExecutionDisabled";
 }
 
 }  // namespace
diff --git a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
index 7c1c90f..d03fcdb 100644
--- a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
+++ b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -839,7 +839,7 @@
   if (base::SysInfo::IsLowEndDevice()) return;
 
   shell()->LoadURL(
-      GURL("data:text/html,<body style='background:#123456'></body>"));
+      GURL("data:text/html,<body style='background:%23123456'></body>"));
   WaitForLoadStop(shell()->web_contents());
   Attach();
   SkBitmap expected_bitmap;
@@ -862,7 +862,7 @@
     return;
 
   shell()->LoadURL(
-      GURL("data:text/html,<body style='background:#123456'></body>"));
+      GURL("data:text/html,<body style='background:%23123456'></body>"));
   WaitForLoadStop(shell()->web_contents());
   Attach();
   SkBitmap expected_bitmap;
diff --git a/content/browser/loader/mojo_async_resource_handler.cc b/content/browser/loader/mojo_async_resource_handler.cc
index 90e06e7..130a2fe 100644
--- a/content/browser/loader/mojo_async_resource_handler.cc
+++ b/content/browser/loader/mojo_async_resource_handler.cc
@@ -488,6 +488,11 @@
 
   network::URLLoaderCompletionStatus loader_status;
   loader_status.error_code = error_code;
+  if (error_code == net::ERR_QUIC_PROTOCOL_ERROR) {
+    net::NetErrorDetails details;
+    request()->PopulateNetErrorDetails(&details);
+    loader_status.extended_error_code = details.quic_connection_error;
+  }
   loader_status.exists_in_cache = request()->response_info().was_cached;
   loader_status.completion_time = base::TimeTicks::Now();
   loader_status.encoded_data_length = request()->GetTotalReceivedBytes();
diff --git a/content/browser/renderer_host/frame_token_message_queue.cc b/content/browser/renderer_host/frame_token_message_queue.cc
index 98ed2cf..e471c3a 100644
--- a/content/browser/renderer_host/frame_token_message_queue.cc
+++ b/content/browser/renderer_host/frame_token_message_queue.cc
@@ -22,49 +22,54 @@
 
   last_received_frame_token_ = frame_token;
 
-  while (queued_messages_.size() &&
-         queued_messages_.front().first <= frame_token) {
-    ProcessSwapMessages(std::move(queued_messages_.front().second));
-    queued_messages_.pop();
-  }
+  // Gets the first callback associated with a token after |frame_token| or
+  // callback_map_.end().
+  auto upper_bound = callback_map_.upper_bound(frame_token);
+
+  // std::multimap already sorts on keys, so this will process all enqueued
+  // messages up to the current frame token.
+  for (auto it = callback_map_.begin(); it != upper_bound; ++it)
+    std::move(it->second).Run();
+
+  // Clear all callbacks up to the current frame token.
+  callback_map_.erase(callback_map_.begin(), upper_bound);
 }
 
-void FrameTokenMessageQueue::OnFrameSwapMessagesReceived(
+void FrameTokenMessageQueue::EnqueueOrRunFrameTokenCallback(
     uint32_t frame_token,
-    std::vector<IPC::Message> messages) {
+    base::OnceClosure callback) {
   // Zero token is invalid.
   if (!frame_token) {
     client_->OnInvalidFrameToken(frame_token);
     return;
   }
 
-  // Frame tokens always increase.
-  if (queued_messages_.size() && frame_token <= queued_messages_.back().first) {
-    client_->OnInvalidFrameToken(frame_token);
-    return;
-  }
-
   if (frame_token <= last_received_frame_token_) {
-    ProcessSwapMessages(std::move(messages));
+    std::move(callback).Run();
     return;
   }
+  callback_map_.insert(std::make_pair(frame_token, std::move(callback)));
+}
 
-  queued_messages_.push(std::make_pair(frame_token, std::move(messages)));
+void FrameTokenMessageQueue::OnFrameSwapMessagesReceived(
+    uint32_t frame_token,
+    std::vector<IPC::Message> messages) {
+  EnqueueOrRunFrameTokenCallback(
+      frame_token, base::BindOnce(&FrameTokenMessageQueue::ProcessSwapMessages,
+                                  base::Unretained(this), std::move(messages)));
 }
 
 void FrameTokenMessageQueue::Reset() {
   last_received_frame_token_ = 0;
-  // base::queue does not contain a clear.
-  auto doomed = std::move(queued_messages_);
+  callback_map_.clear();
 }
 
 void FrameTokenMessageQueue::ProcessSwapMessages(
     std::vector<IPC::Message> messages) {
-  for (std::vector<IPC::Message>::const_iterator i = messages.begin();
-       i != messages.end(); ++i) {
-    client_->OnProcessSwapMessage(*i);
-    if (i->dispatch_error())
-      client_->OnMessageDispatchError(*i);
+  for (const IPC::Message& i : messages) {
+    client_->OnProcessSwapMessage(i);
+    if (i.dispatch_error())
+      client_->OnMessageDispatchError(i);
   }
 }
 
diff --git a/content/browser/renderer_host/frame_token_message_queue.h b/content/browser/renderer_host/frame_token_message_queue.h
index 48204ac..2090447 100644
--- a/content/browser/renderer_host/frame_token_message_queue.h
+++ b/content/browser/renderer_host/frame_token_message_queue.h
@@ -5,9 +5,10 @@
 #ifndef CONTENT_BROWSER_RENDERER_HOST_FRAME_TOKEN_MESSAGE_QUEUE_H_
 #define CONTENT_BROWSER_RENDERER_HOST_FRAME_TOKEN_MESSAGE_QUEUE_H_
 
+#include <map>
 #include <vector>
 
-#include "base/containers/queue.h"
+#include "base/callback.h"
 #include "base/macros.h"
 #include "content/common/content_export.h"
 
@@ -17,17 +18,20 @@
 
 namespace content {
 
-// The Renderer sends various IPC::Messages which are not to be processed until
-// after their associated frame has been processed. These messages are provided
-// with a FrameToken to be used for synchronizing.
+// The Renderer sends various messages which are not to be processed until after
+// their associated frame has been processed. These messages are provided with a
+// FrameToken to be used for synchronizing.
 //
 // Viz processes the frames, after which it notifies the Browser of which
 // FrameToken has completed processing.
 //
 // This enqueues all IPC::Messages associated with a FrameToken.
 //
+// Additionally other callbacks can be enqueued with
+// EnqueueOrRunFrameTokenCallback.
+//
 // Upon receipt of DidProcessFrame all IPC::Messages associated with the
-// provided FrameToken are then dispatched.
+// provided FrameToken are then dispatched, and all enqueued callbacks are ran.
 class CONTENT_EXPORT FrameTokenMessageQueue {
  public:
   // Notified of errors in processing messages, as well as of the actual
@@ -52,6 +56,12 @@
   // there are any queued messages belonging to it, they will be processed.
   void DidProcessFrame(uint32_t frame_token);
 
+  // Enqueues |callback| to be called upon the arrival of |frame_token| in
+  // DidProcessFrame. However if |frame_token| has already arrived |callback| is
+  // ran immediately.
+  void EnqueueOrRunFrameTokenCallback(uint32_t frame_token,
+                                      base::OnceClosure callback);
+
   // Enqueues the swap messages.
   void OnFrameSwapMessagesReceived(uint32_t frame_token,
                                    std::vector<IPC::Message> messages);
@@ -60,7 +70,7 @@
   // consistent incase a new renderer is created.
   void Reset();
 
-  uint32_t size() const { return queued_messages_.size(); }
+  uint32_t size() const { return callback_map_.size(); }
 
  protected:
   // Once both the frame and its swap messages arrive, we call this method to
@@ -75,9 +85,9 @@
   // having a token less than or equal to this value will be processed.
   uint32_t last_received_frame_token_ = 0;
 
-  // List of all swap messages that their corresponding frames have not arrived.
+  // Map of all callbacks for which their corresponding frame have not arrived.
   // Sorted by frame token.
-  base::queue<std::pair<uint32_t, std::vector<IPC::Message>>> queued_messages_;
+  std::multimap<uint32_t, base::OnceClosure> callback_map_;
 
   DISALLOW_COPY_AND_ASSIGN(FrameTokenMessageQueue);
 };
diff --git a/content/browser/renderer_host/frame_token_message_queue_unittest.cc b/content/browser/renderer_host/frame_token_message_queue_unittest.cc
new file mode 100644
index 0000000..96e171c
--- /dev/null
+++ b/content/browser/renderer_host/frame_token_message_queue_unittest.cc
@@ -0,0 +1,665 @@
+// 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 "content/browser/renderer_host/frame_token_message_queue.h"
+
+#include <vector>
+
+#include "base/macros.h"
+#include "ipc/ipc_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+// Test verision of FrameTokenMessageQueue::Client which tracks the number of
+// calls to client methods, and the associated input parameters.
+class TestFrameTokenMessageQueueClient : public FrameTokenMessageQueue::Client {
+ public:
+  TestFrameTokenMessageQueueClient() {}
+  ~TestFrameTokenMessageQueueClient() {}
+
+  // Resets all method counters.
+  void Reset();
+
+  // All subsequent IPC::Messages received in OnProcessSwapMessage will be
+  // marked as having a dispatch error.
+  void SetErrorOnMessageProcess();
+
+  // FrameTokenMessageQueue::Client:
+  void OnInvalidFrameToken(uint32_t frame_token) override;
+  void OnMessageDispatchError(const IPC::Message& message) override;
+  void OnProcessSwapMessage(const IPC::Message& message) override;
+
+  bool invalid_frame_token_called() const {
+    return invalid_frame_token_called_;
+  }
+  uint32_t invalid_frame_token() const { return invalid_frame_token_; }
+  int on_message_dispatch_error_count() const {
+    return on_message_dispatch_error_count_;
+  }
+  int on_process_swap_message_count() const {
+    return on_process_swap_message_count_;
+  }
+
+ private:
+  // If true the each IPC::Message received will be marked as having a dispatch
+  // error.
+  bool set_error_on_process_ = false;
+  bool invalid_frame_token_called_ = false;
+  uint32_t invalid_frame_token_ = 0u;
+  int on_message_dispatch_error_count_ = 0;
+  int on_process_swap_message_count_ = 0;
+  DISALLOW_COPY_AND_ASSIGN(TestFrameTokenMessageQueueClient);
+};
+
+void TestFrameTokenMessageQueueClient::Reset() {
+  invalid_frame_token_called_ = false;
+  invalid_frame_token_ = 0u;
+  on_message_dispatch_error_count_ = 0;
+  on_process_swap_message_count_ = 0;
+}
+
+void TestFrameTokenMessageQueueClient::SetErrorOnMessageProcess() {
+  set_error_on_process_ = true;
+}
+
+void TestFrameTokenMessageQueueClient::OnInvalidFrameToken(
+    uint32_t frame_token) {
+  invalid_frame_token_called_ = true;
+  invalid_frame_token_ = frame_token;
+}
+
+void TestFrameTokenMessageQueueClient::OnMessageDispatchError(
+    const IPC::Message& message) {
+  ++on_message_dispatch_error_count_;
+}
+
+void TestFrameTokenMessageQueueClient::OnProcessSwapMessage(
+    const IPC::Message& message) {
+  if (set_error_on_process_)
+    message.set_dispatch_error();
+  ++on_process_swap_message_count_;
+}
+
+// Test class which provides FrameTokenCallback() to be used as a closure when
+// enqueueing non-IPC callbacks. This only tracks if the callback was called.
+class TestNonIPCMessageEnqueuer {
+ public:
+  TestNonIPCMessageEnqueuer() {}
+  ~TestNonIPCMessageEnqueuer() {}
+
+  void FrameTokenCallback();
+
+  bool frame_token_callback_called() const {
+    return frame_token_callback_called_;
+  }
+
+ private:
+  bool frame_token_callback_called_ = false;
+  DISALLOW_COPY_AND_ASSIGN(TestNonIPCMessageEnqueuer);
+};
+
+void TestNonIPCMessageEnqueuer::FrameTokenCallback() {
+  frame_token_callback_called_ = true;
+}
+
+}  // namespace
+
+class FrameTokenMessageQueueTest : public testing::Test {
+ public:
+  FrameTokenMessageQueueTest();
+  ~FrameTokenMessageQueueTest() override {}
+
+  TestFrameTokenMessageQueueClient* test_client() { return &test_client_; }
+  TestNonIPCMessageEnqueuer* test_non_ipc_enqueuer() {
+    return &test_non_ipc_enqueuer_;
+  }
+  FrameTokenMessageQueue* frame_token_message_queue() {
+    return &frame_token_message_queue_;
+  }
+
+ private:
+  TestFrameTokenMessageQueueClient test_client_;
+  TestNonIPCMessageEnqueuer test_non_ipc_enqueuer_;
+  FrameTokenMessageQueue frame_token_message_queue_;
+  DISALLOW_COPY_AND_ASSIGN(FrameTokenMessageQueueTest);
+};
+
+FrameTokenMessageQueueTest::FrameTokenMessageQueueTest()
+    : frame_token_message_queue_(&test_client_) {}
+
+// Tests that if a valid IPC::Message is enqueued, that it is processed when its
+// matching frame token arrives.
+TEST_F(FrameTokenMessageQueueTest, OnlyIPCMessageCorrectFrameToken) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t frame_token = 42;
+  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
+  std::vector<IPC::Message> messages;
+  messages.push_back(msg);
+
+  // Adding to the queue with a new frame token should not cause processing.
+  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
+  EXPECT_EQ(1u, queue->size());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+
+  queue->DidProcessFrame(frame_token);
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(1, client->on_process_swap_message_count());
+  EXPECT_EQ(0, client->on_message_dispatch_error_count());
+}
+
+// Tests that if a valid IPC::Message is enqueued after its frame token has
+// arrived that it is processed immediately.
+TEST_F(FrameTokenMessageQueueTest, EnqueueAfterFrameTokenProcesses) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t frame_token = 42;
+  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
+  std::vector<IPC::Message> messages;
+  messages.push_back(msg);
+
+  queue->DidProcessFrame(frame_token);
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+  EXPECT_EQ(0, client->on_message_dispatch_error_count());
+
+  // Enqueuing after frame token arrival should immediately process.
+  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(1, client->on_process_swap_message_count());
+  EXPECT_EQ(0, client->on_message_dispatch_error_count());
+}
+
+// Tests that if a valid IPC::Message is enqueued and that subsequently a
+// non-IPC callback is enqueued, that both get called once the frame token
+// arrives.
+TEST_F(FrameTokenMessageQueueTest, EnqueueBothIPCMessageAndNonIPCCallback) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t frame_token = 42;
+  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
+  std::vector<IPC::Message> messages;
+  messages.push_back(msg);
+
+  // Adding to the queue with a new frame token should not cause processing.
+  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
+  EXPECT_EQ(1u, queue->size());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+
+  queue->EnqueueOrRunFrameTokenCallback(
+      frame_token,
+      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
+                     base::Unretained(enqueuer)));
+  EXPECT_FALSE(enqueuer->frame_token_callback_called());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+
+  queue->DidProcessFrame(frame_token);
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(1, client->on_process_swap_message_count());
+  EXPECT_EQ(0, client->on_message_dispatch_error_count());
+  EXPECT_TRUE(enqueuer->frame_token_callback_called());
+}
+
+// Tests that if a valid non-IPC callback is enqueued before an IPC::Message,
+// that both get called once the frame token arrives.
+TEST_F(FrameTokenMessageQueueTest, EnqueueNonIPCCallbackFirst) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t frame_token = 42;
+  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
+  std::vector<IPC::Message> messages;
+  messages.push_back(msg);
+
+  queue->EnqueueOrRunFrameTokenCallback(
+      frame_token,
+      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
+                     base::Unretained(enqueuer)));
+  EXPECT_FALSE(enqueuer->frame_token_callback_called());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+
+  // We should be able to enqueue even though it is for the same frame token.
+  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
+  EXPECT_EQ(2u, queue->size());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+
+  queue->DidProcessFrame(frame_token);
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(1, client->on_process_swap_message_count());
+  EXPECT_EQ(0, client->on_message_dispatch_error_count());
+  EXPECT_TRUE(enqueuer->frame_token_callback_called());
+}
+
+// Tests that if we only have a non-IPC callback enqueued that it is called once
+// the frame token arrive.
+TEST_F(FrameTokenMessageQueueTest, EnqueueOnlyNonIPC) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t frame_token = 42;
+  queue->EnqueueOrRunFrameTokenCallback(
+      frame_token,
+      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
+                     base::Unretained(enqueuer)));
+  EXPECT_FALSE(enqueuer->frame_token_callback_called());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+  EXPECT_EQ(1u, queue->size());
+
+  queue->DidProcessFrame(frame_token);
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+  EXPECT_EQ(0, client->on_message_dispatch_error_count());
+  EXPECT_TRUE(enqueuer->frame_token_callback_called());
+}
+
+// Tests that if we have messages enqueued, and receive a frame token that is
+// larger, that we still process the messages.
+TEST_F(FrameTokenMessageQueueTest, MessagesWhereFrameTokenSkipped) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t frame_token = 42;
+  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
+  std::vector<IPC::Message> messages;
+  messages.push_back(msg);
+
+  queue->EnqueueOrRunFrameTokenCallback(
+      frame_token,
+      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
+                     base::Unretained(enqueuer)));
+  EXPECT_FALSE(enqueuer->frame_token_callback_called());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+
+  // We should be able to enqueue even though it is for the same frame token.
+  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
+  EXPECT_EQ(2u, queue->size());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+
+  const uint32_t larger_frame_token = 1337;
+  queue->DidProcessFrame(larger_frame_token);
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(1, client->on_process_swap_message_count());
+  EXPECT_EQ(0, client->on_message_dispatch_error_count());
+  EXPECT_TRUE(enqueuer->frame_token_callback_called());
+}
+
+// Verifies that if there are multiple IPC::Messages that they are all
+// processed.
+TEST_F(FrameTokenMessageQueueTest, MultipleIPCMessages) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t frame_token = 42;
+  IPC::Message msg1(0, 1, IPC::Message::PRIORITY_NORMAL);
+  IPC::Message msg2(1, 2, IPC::Message::PRIORITY_LOW);
+  std::vector<IPC::Message> messages;
+  messages.push_back(msg1);
+  messages.push_back(msg2);
+
+  // Adding to the queue with a new frame token should not cause processing.
+  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
+  // All IPCs are enqueued as one.
+  EXPECT_EQ(1u, queue->size());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+
+  queue->DidProcessFrame(frame_token);
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(2, client->on_process_swap_message_count());
+  EXPECT_EQ(0, client->on_message_dispatch_error_count());
+}
+
+// Verifies that if there are multiple non-IPC messages enqueued that they are
+// all called.
+TEST_F(FrameTokenMessageQueueTest, MultipleNonIPCMessages) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t frame_token = 42;
+  queue->EnqueueOrRunFrameTokenCallback(
+      frame_token,
+      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
+                     base::Unretained(enqueuer)));
+  EXPECT_FALSE(enqueuer->frame_token_callback_called());
+  EXPECT_EQ(1u, queue->size());
+
+  // Create a second callback
+  TestNonIPCMessageEnqueuer second_enqueuer;
+  queue->EnqueueOrRunFrameTokenCallback(
+      frame_token,
+      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
+                     base::Unretained(&second_enqueuer)));
+  EXPECT_FALSE(second_enqueuer.frame_token_callback_called());
+  EXPECT_EQ(2u, queue->size());
+
+  queue->DidProcessFrame(frame_token);
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_TRUE(enqueuer->frame_token_callback_called());
+  EXPECT_TRUE(second_enqueuer.frame_token_callback_called());
+}
+
+// Tests that if a non-IPC callback is enqueued, after its frame token as been
+// received, that it is immediately processed.
+TEST_F(FrameTokenMessageQueueTest, EnqueuedAfterFrameTokenImmediatelyRuns) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t frame_token = 42;
+  queue->DidProcessFrame(frame_token);
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_FALSE(enqueuer->frame_token_callback_called());
+
+  queue->EnqueueOrRunFrameTokenCallback(
+      frame_token,
+      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
+                     base::Unretained(enqueuer)));
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_TRUE(enqueuer->frame_token_callback_called());
+}
+
+// Tests that if IPC::Messages are enqueued for different frame tokens, that
+// we only process the messages associated with the arriving token, and keep the
+// others enqueued.
+TEST_F(FrameTokenMessageQueueTest, DifferentFrameTokensEnqueuedIPC) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t frame_token_1 = 42;
+  IPC::Message msg1(0, 1, IPC::Message::PRIORITY_NORMAL);
+  std::vector<IPC::Message> messages_1;
+  messages_1.push_back(msg1);
+
+  queue->OnFrameSwapMessagesReceived(frame_token_1, std::move(messages_1));
+  EXPECT_EQ(1u, queue->size());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+
+  const uint32_t frame_token_2 = 1337;
+  IPC::Message msg2(1, 2, IPC::Message::PRIORITY_LOW);
+  std::vector<IPC::Message> messages_2;
+  messages_2.push_back(msg2);
+
+  queue->OnFrameSwapMessagesReceived(frame_token_2, std::move(messages_2));
+  // With no frame token yet the second set of IPC::Messages should be enqueud
+  // separately.
+  EXPECT_EQ(2u, queue->size());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+
+  // We should only process the first IPC::Message.
+  queue->DidProcessFrame(frame_token_1);
+  EXPECT_EQ(1u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(1, client->on_process_swap_message_count());
+  EXPECT_EQ(0, client->on_message_dispatch_error_count());
+
+  // Clear the counts from the first token.
+  client->Reset();
+
+  // The second IPC::Message should be processed.
+  queue->DidProcessFrame(frame_token_2);
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(1, client->on_process_swap_message_count());
+  EXPECT_EQ(0, client->on_message_dispatch_error_count());
+}
+
+// Test that if non-IPC callbacks are enqueued for different frame tokens, that
+// we only process the messages associated with the arriving token, and keep the
+// others enqueued.
+TEST_F(FrameTokenMessageQueueTest, DifferentFrameTokensEnqueuedNonIPC) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t frame_token_1 = 42;
+  queue->EnqueueOrRunFrameTokenCallback(
+      frame_token_1,
+      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
+                     base::Unretained(enqueuer)));
+  EXPECT_FALSE(enqueuer->frame_token_callback_called());
+  EXPECT_EQ(1u, queue->size());
+
+  // Create a second callback
+  const uint32_t frame_token_2 = 1337;
+  TestNonIPCMessageEnqueuer second_enqueuer;
+  queue->EnqueueOrRunFrameTokenCallback(
+      frame_token_2,
+      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
+                     base::Unretained(&second_enqueuer)));
+  EXPECT_FALSE(second_enqueuer.frame_token_callback_called());
+  EXPECT_EQ(2u, queue->size());
+
+  queue->DidProcessFrame(frame_token_1);
+  EXPECT_EQ(1u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_TRUE(enqueuer->frame_token_callback_called());
+  EXPECT_FALSE(second_enqueuer.frame_token_callback_called());
+
+  queue->DidProcessFrame(frame_token_2);
+  EXPECT_TRUE(second_enqueuer.frame_token_callback_called());
+}
+
+// An empty frame token is considered invalid, so this tests that attempting to
+// enqueue for that is rejected.
+TEST_F(FrameTokenMessageQueueTest, EmptyTokenForIPCMessageIsRejected) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t invalid_frame_token = 0;
+  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
+  std::vector<IPC::Message> messages;
+  messages.push_back(msg);
+
+  // Adding to the queue with a new frame token should not cause processing.
+  queue->OnFrameSwapMessagesReceived(invalid_frame_token, std::move(messages));
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_TRUE(client->invalid_frame_token_called());
+  EXPECT_EQ(invalid_frame_token, client->invalid_frame_token());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+}
+
+// Tests that when adding an IPC::Message for an earlier frame token, that it is
+// enqueued.
+TEST_F(FrameTokenMessageQueueTest, EarlierTokenForIPCMessageIsNotRejected) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t valid_frame_token = 42;
+  IPC::Message msg1(0, 1, IPC::Message::PRIORITY_NORMAL);
+  std::vector<IPC::Message> messages_1;
+  messages_1.push_back(msg1);
+
+  // Adding to the queue with a new frame token should not cause processing.
+  queue->OnFrameSwapMessagesReceived(valid_frame_token, std::move(messages_1));
+  EXPECT_EQ(1u, queue->size());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+
+  const uint32_t earlier_frame_token = 1;
+  IPC::Message msg2(1, 2, IPC::Message::PRIORITY_NORMAL);
+  std::vector<IPC::Message> messages_2;
+  messages_2.push_back(msg1);
+
+  // Adding an earlier frame token should be enqueued.
+  queue->OnFrameSwapMessagesReceived(earlier_frame_token,
+                                     std::move(messages_2));
+  EXPECT_EQ(2u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+}
+
+// Tests that if DidProcessFrame is called with an invalid token, that it is
+// rejected, and that no callbacks are processed.
+TEST_F(FrameTokenMessageQueueTest, InvalidDidProcessFrameTokenNotProcessed) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t frame_token = 42;
+  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
+  std::vector<IPC::Message> messages;
+  messages.push_back(msg);
+
+  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
+  EXPECT_EQ(1u, queue->size());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+
+  queue->EnqueueOrRunFrameTokenCallback(
+      frame_token,
+      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
+                     base::Unretained(enqueuer)));
+  EXPECT_FALSE(enqueuer->frame_token_callback_called());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+
+  // Empty token should be invalid even with no process frames processed.
+  const uint32_t invalid_frame_token = 0;
+  queue->DidProcessFrame(invalid_frame_token);
+  EXPECT_EQ(2u, queue->size());
+  EXPECT_TRUE(client->invalid_frame_token_called());
+  EXPECT_EQ(invalid_frame_token, client->invalid_frame_token());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+  EXPECT_EQ(0, client->on_message_dispatch_error_count());
+  EXPECT_FALSE(enqueuer->frame_token_callback_called());
+}
+
+// Test that if DidProcessFrame is called with an earlier frame token, that it
+// is rejected, and that no callbacks are processed.
+TEST_F(FrameTokenMessageQueueTest, EarlierTokenForDidProcessFrameRejected) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer();
+  ASSERT_EQ(0u, queue->size());
+
+  // Settings a low value frame token will not block enqueueing.
+  const uint32_t earlier_frame_token = 42;
+  queue->DidProcessFrame(earlier_frame_token);
+
+  const uint32_t frame_token = 1337;
+  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
+  std::vector<IPC::Message> messages;
+  messages.push_back(msg);
+
+  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
+  EXPECT_EQ(1u, queue->size());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+
+  queue->EnqueueOrRunFrameTokenCallback(
+      frame_token,
+      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
+                     base::Unretained(enqueuer)));
+  EXPECT_FALSE(enqueuer->frame_token_callback_called());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+
+  // Using a frame token that is earlier than the last received should be
+  // rejected.
+  const uint32_t invalid_frame_token = earlier_frame_token - 1;
+  queue->DidProcessFrame(invalid_frame_token);
+  EXPECT_EQ(2u, queue->size());
+  EXPECT_TRUE(client->invalid_frame_token_called());
+  EXPECT_EQ(invalid_frame_token, client->invalid_frame_token());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+  EXPECT_EQ(0, client->on_message_dispatch_error_count());
+  EXPECT_FALSE(enqueuer->frame_token_callback_called());
+}
+
+// Tests that if an IPC::Message has a dispatch error that the client is
+// notified.
+TEST_F(FrameTokenMessageQueueTest, DispatchError) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t frame_token = 42;
+  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
+  std::vector<IPC::Message> messages;
+  messages.push_back(msg);
+
+  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
+  EXPECT_EQ(1u, queue->size());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+
+  // Dispatch error should be notified during processing.
+  client->SetErrorOnMessageProcess();
+  queue->DidProcessFrame(frame_token);
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(1, client->on_process_swap_message_count());
+  EXPECT_EQ(1, client->on_message_dispatch_error_count());
+}
+
+// Tests that if we have already enqueued a callback for a frame token, that if
+// a request for an earlier frame token arrives, that it is still enqueued. Then
+// once the large frame token arrives, both are processed.
+TEST_F(FrameTokenMessageQueueTest, OutOfOrderFrameTokensEnqueue) {
+  FrameTokenMessageQueue* queue = frame_token_message_queue();
+  TestFrameTokenMessageQueueClient* client = test_client();
+  TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer();
+  ASSERT_EQ(0u, queue->size());
+
+  const uint32_t larger_frame_token = 1337;
+  queue->EnqueueOrRunFrameTokenCallback(
+      larger_frame_token,
+      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
+                     base::Unretained(enqueuer)));
+  EXPECT_EQ(1u, queue->size());
+  EXPECT_FALSE(enqueuer->frame_token_callback_called());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+
+  const uint32_t smaller_frame_token = 42;
+  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
+  std::vector<IPC::Message> messages;
+  messages.push_back(msg);
+
+  // Enqueuing for a smaller token, which has not yet arrived, should still
+  // enqueue.
+  queue->OnFrameSwapMessagesReceived(smaller_frame_token, std::move(messages));
+  EXPECT_EQ(2u, queue->size());
+  EXPECT_EQ(0, client->on_process_swap_message_count());
+
+  // Process both with the larger frame token arriving.
+  queue->DidProcessFrame(larger_frame_token);
+  EXPECT_EQ(0u, queue->size());
+  EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(1, client->on_process_swap_message_count());
+  EXPECT_EQ(0, client->on_message_dispatch_error_count());
+  EXPECT_TRUE(enqueuer->frame_token_callback_called());
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/input/composited_scrolling_browsertest.cc b/content/browser/renderer_host/input/composited_scrolling_browsertest.cc
index 556aace..2d8b653 100644
--- a/content/browser/renderer_host/input/composited_scrolling_browsertest.cc
+++ b/content/browser/renderer_host/input/composited_scrolling_browsertest.cc
@@ -32,14 +32,14 @@
     "<!DOCTYPE html>"
     "<meta name='viewport' content='width=device-width'/>"
     "<style>"
-    "#scroller {"
+    "%23scroller {"
     "  width:500px;"
     "  height:500px;"
     "  overflow:scroll;"
     "  transform: rotateX(-30deg);"
     "}"
 
-    "#content {"
+    "%23content {"
     "  background-color:red;"
     "  width:1000px;"
     "  height:1000px;"
diff --git a/content/browser/renderer_host/input/touch_action_browsertest.cc b/content/browser/renderer_host/input/touch_action_browsertest.cc
index e0a773e..d4e9b48 100644
--- a/content/browser/renderer_host/input/touch_action_browsertest.cc
+++ b/content/browser/renderer_host/input/touch_action_browsertest.cc
@@ -91,7 +91,7 @@
     NavigateToURL(shell(), data_url);
 
     RenderWidgetHostImpl* host = GetWidgetHost();
-    RenderFrameSubmissionObserver frame_observer(
+    frame_observer_ = std::make_unique<RenderFrameSubmissionObserver>(
         host->render_frame_metadata_provider());
     host->GetView()->SetSize(gfx::Size(400, 400));
 
@@ -103,7 +103,7 @@
     // otherwise the injection of the synthetic gestures may get
     // dropped because of MainThread/Impl thread sync of touch event
     // regions.
-    frame_observer.WaitForAnyFrameSubmission();
+    frame_observer_->WaitForAnyFrameSubmission();
   }
 
   // ContentBrowserTest:
@@ -115,6 +115,13 @@
     cmd->AppendSwitch(switches::kEnableExperimentalWebPlatformFeatures);
   }
 
+  // ContentBrowserTest:
+  void PostRunTestOnMainThread() override {
+    // Delete this before the WebContents is destroyed.
+    frame_observer_.reset();
+    ContentBrowserTest::PostRunTestOnMainThread();
+  }
+
   int ExecuteScriptAndExtractInt(const std::string& script) {
     int value = 0;
     EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
@@ -145,9 +152,6 @@
         ExecuteScriptAndExtractInt("document.documentElement.scrollHeight");
     EXPECT_EQ(10200, scroll_height);
 
-    RenderFrameSubmissionObserver frame_metadata_observer(
-        GetWidgetHost()->render_frame_metadata_provider());
-
     SyntheticSmoothScrollGestureParams params;
     params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
     params.anchor = gfx::PointF(point);
@@ -170,10 +174,10 @@
     // main thread was in a busy loop.
     gfx::Vector2dF default_scroll_offset;
     while (wait_until_scrolled &&
-           frame_metadata_observer.LastRenderFrameMetadata()
+           frame_observer_->LastRenderFrameMetadata()
                    .root_scroll_offset.value_or(default_scroll_offset)
                    .y() < (distance.y() / 2)) {
-      frame_metadata_observer.WaitForMetadataChange();
+      frame_observer_->WaitForMetadataChange();
     }
 
     // Check the scroll offset
@@ -187,6 +191,7 @@
   }
 
  private:
+  std::unique_ptr<RenderFrameSubmissionObserver> frame_observer_;
   scoped_refptr<MessageLoopRunner> runner_;
 
   DISALLOW_COPY_AND_ASSIGN(TouchActionBrowserTest);
diff --git a/content/browser/renderer_host/input/touch_input_browsertest.cc b/content/browser/renderer_host/input/touch_input_browsertest.cc
index 206e524..a97270ef 100644
--- a/content/browser/renderer_host/input/touch_input_browsertest.cc
+++ b/content/browser/renderer_host/input/touch_input_browsertest.cc
@@ -47,7 +47,7 @@
     "<body onload='setup();'>"
     "<div id='first'></div><div id='second'></div><div id='third'></div>"
     "<style>"
-    "  #first {"
+    "  %23first {"
     "    position: absolute;"
     "    width: 100px;"
     "    height: 100px;"
@@ -56,7 +56,7 @@
     "    background-color: green;"
     "    -webkit-transform: translate3d(0, 0, 0);"
     "  }"
-    "  #second {"
+    "  %23second {"
     "    position: absolute;"
     "    width: 100px;"
     "    height: 100px;"
@@ -65,7 +65,7 @@
     "    background-color: blue;"
     "    -webkit-transform: translate3d(0, 0, 0);"
     "  }"
-    "  #third {"
+    "  %23third {"
     "    position: absolute;"
     "    width: 100px;"
     "    height: 100px;"
diff --git a/content/browser/renderer_host/input/wheel_scroll_latching_browsertest.cc b/content/browser/renderer_host/input/wheel_scroll_latching_browsertest.cc
index fbd77cb..36b88b51 100644
--- a/content/browser/renderer_host/input/wheel_scroll_latching_browsertest.cc
+++ b/content/browser/renderer_host/input/wheel_scroll_latching_browsertest.cc
@@ -37,7 +37,7 @@
     "body {"
     " height: 10000px;"
     "}"
-    "#scrollableDiv {"
+    "%23scrollableDiv {"
     " position: absolute;"
     " left: 50px;"
     " top: 100px;"
@@ -46,7 +46,7 @@
     " overflow: scroll;"
     " background: red;"
     "}"
-    "#nestedDiv {"
+    "%23nestedDiv {"
     " width: 200px;"
     " height: 8000px;"
     " opacity: 0;"
diff --git a/content/browser/renderer_host/render_frame_metadata_provider_impl.cc b/content/browser/renderer_host/render_frame_metadata_provider_impl.cc
index 9e6168f6..1850d56 100644
--- a/content/browser/renderer_host/render_frame_metadata_provider_impl.cc
+++ b/content/browser/renderer_host/render_frame_metadata_provider_impl.cc
@@ -4,10 +4,16 @@
 
 #include "content/browser/renderer_host/render_frame_metadata_provider_impl.h"
 
+#include "base/bind.h"
+#include "content/browser/renderer_host/frame_token_message_queue.h"
+
 namespace content {
 
-RenderFrameMetadataProviderImpl::RenderFrameMetadataProviderImpl()
-    : render_frame_metadata_observer_client_binding_(this) {}
+RenderFrameMetadataProviderImpl::RenderFrameMetadataProviderImpl(
+    FrameTokenMessageQueue* frame_token_message_queue)
+    : frame_token_message_queue_(frame_token_message_queue),
+      render_frame_metadata_observer_client_binding_(this),
+      weak_factory_(this) {}
 
 RenderFrameMetadataProviderImpl::~RenderFrameMetadataProviderImpl() = default;
 
@@ -40,16 +46,37 @@
   return last_render_frame_metadata_;
 }
 
-void RenderFrameMetadataProviderImpl::OnRenderFrameMetadataChanged(
-    const cc::RenderFrameMetadata& metadata) {
-  last_render_frame_metadata_ = metadata;
+void RenderFrameMetadataProviderImpl::OnFrameTokenRenderFrameMetadataChanged(
+    cc::RenderFrameMetadata metadata) {
+  last_render_frame_metadata_ = std::move(metadata);
   for (Observer& observer : observers_)
     observer.OnRenderFrameMetadataChanged();
 }
 
-void RenderFrameMetadataProviderImpl::OnFrameSubmissionForTesting() {
+void RenderFrameMetadataProviderImpl::OnFrameTokenFrameSubmissionForTesting() {
   for (Observer& observer : observers_)
     observer.OnRenderFrameSubmission();
 }
 
+void RenderFrameMetadataProviderImpl::OnRenderFrameMetadataChanged(
+    uint32_t frame_token,
+    const cc::RenderFrameMetadata& metadata) {
+  // Both RenderFrameMetadataProviderImpl and FrameTokenMessageQueue are owned
+  // by the same RenderWidgetHostImpl. During shutdown the queue is cleared
+  // without running the callbacks.
+  frame_token_message_queue_->EnqueueOrRunFrameTokenCallback(
+      frame_token,
+      base::BindOnce(&RenderFrameMetadataProviderImpl::
+                         OnFrameTokenRenderFrameMetadataChanged,
+                     weak_factory_.GetWeakPtr(), std::move(metadata)));
+}
+
+void RenderFrameMetadataProviderImpl::OnFrameSubmissionForTesting(
+    uint32_t frame_token) {
+  frame_token_message_queue_->EnqueueOrRunFrameTokenCallback(
+      frame_token, base::BindOnce(&RenderFrameMetadataProviderImpl::
+                                      OnFrameTokenFrameSubmissionForTesting,
+                                  base::Unretained(this)));
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/render_frame_metadata_provider_impl.h b/content/browser/renderer_host/render_frame_metadata_provider_impl.h
index 371f8d5d..b59187e 100644
--- a/content/browser/renderer_host/render_frame_metadata_provider_impl.h
+++ b/content/browser/renderer_host/render_frame_metadata_provider_impl.h
@@ -6,12 +6,14 @@
 #define CONTENT_BROWSER_RENDERER_HOST_RENDER_FRAME_METADATA_PROVIDER_IMPL_H_
 
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "content/common/render_frame_metadata.mojom.h"
 #include "content/public/browser/render_frame_metadata_provider.h"
 #include "mojo/public/cpp/bindings/binding.h"
 
 namespace content {
+class FrameTokenMessageQueue;
 
 // Observes RenderFrameMetadata associated with the submission of a frame for a
 // given RenderWidgetHost. The renderer will notify this when sumitting a
@@ -25,7 +27,8 @@
     : public RenderFrameMetadataProvider,
       public mojom::RenderFrameMetadataObserverClient {
  public:
-  RenderFrameMetadataProviderImpl();
+  explicit RenderFrameMetadataProviderImpl(
+      FrameTokenMessageQueue* frame_token_message_queue);
   ~RenderFrameMetadataProviderImpl() override;
 
   void AddObserver(Observer* observer) override;
@@ -41,19 +44,32 @@
   const cc::RenderFrameMetadata& LastRenderFrameMetadata() const override;
 
  private:
+  // Paired with the mojom::RenderFrameMetadataObserverClient overrides, these
+  // methods are enqueued in |frame_token_message_queue_|. They are invoked when
+  // the browser process receives their associated frame tokens. These then
+  // notify any |observers_|.
+  void OnFrameTokenRenderFrameMetadataChanged(cc::RenderFrameMetadata metadata);
+  void OnFrameTokenFrameSubmissionForTesting();
+
   // mojom::RenderFrameMetadataObserverClient:
   void OnRenderFrameMetadataChanged(
+      uint32_t frame_token,
       const cc::RenderFrameMetadata& metadata) override;
-  void OnFrameSubmissionForTesting() override;
+  void OnFrameSubmissionForTesting(uint32_t frame_token) override;
 
   base::ObserverList<Observer> observers_;
 
   cc::RenderFrameMetadata last_render_frame_metadata_;
 
+  // Not owned.
+  FrameTokenMessageQueue* const frame_token_message_queue_;
+
   mojo::Binding<mojom::RenderFrameMetadataObserverClient>
       render_frame_metadata_observer_client_binding_;
   mojom::RenderFrameMetadataObserverPtr render_frame_metadata_observer_ptr_;
 
+  base::WeakPtrFactory<RenderFrameMetadataProviderImpl> weak_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(RenderFrameMetadataProviderImpl);
 };
 
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index e704b4cd..f0950ec 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -371,6 +371,7 @@
       compositor_frame_sink_binding_(this),
       frame_token_message_queue_(
           std::make_unique<FrameTokenMessageQueue>(this)),
+      render_frame_metadata_provider_(frame_token_message_queue_.get()),
       frame_sink_id_(base::checked_cast<uint32_t>(process_->GetID()),
                      base::checked_cast<uint32_t>(routing_id_)),
       weak_factory_(this) {
diff --git a/content/browser/renderer_host/render_widget_host_view_browsertest.cc b/content/browser/renderer_host/render_widget_host_view_browsertest.cc
index 26f9a5b..6f40ec3 100644
--- a/content/browser/renderer_host/render_widget_host_view_browsertest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_browsertest.cc
@@ -453,13 +453,13 @@
         "<style>"
         "body { padding: 0; margin: 0; }"
         ".left { position: absolute;"
-        "        background: #0ff;"
+        "        background: %%230ff;"
         "        width: %dpx;"
         "        height: %dpx;"
         "}"
         ".right { position: absolute;"
         "         left: %dpx;"
-        "         background: #ff0;"
+        "         background: %%23ff0;"
         "         width: %dpx;"
         "         height: %dpx;"
         "}"
@@ -748,7 +748,7 @@
                          "<style>"
                          "body { padding: 0; margin: 0; }"
                          ".box { position: absolute;"
-                         "        background: #0ff;"
+                         "        background: %%230ff;"
                          "        width: 100%%;"
                          "        height: %dpx;"
                          "}"
diff --git a/content/common/render_frame_metadata.mojom b/content/common/render_frame_metadata.mojom
index 71215a118..ffc97dc 100644
--- a/content/common/render_frame_metadata.mojom
+++ b/content/common/render_frame_metadata.mojom
@@ -26,8 +26,8 @@
 // RenderFrameMetadataObserver::ReportAllFrameSubmissionsForTesting.
 interface RenderFrameMetadataObserverClient {
   // Notified when RenderFrameMetadata has changed.
-  OnRenderFrameMetadataChanged(RenderFrameMetadata metadata);
+  OnRenderFrameMetadataChanged(uint32 frame_token, RenderFrameMetadata metadata);
 
   // Notified on all frame submissions.
-  OnFrameSubmissionForTesting();
+  OnFrameSubmissionForTesting(uint32 frame_token);
 };
diff --git a/content/public/test/render_view_test.cc b/content/public/test/render_view_test.cc
index 178c5f1..59ecb07 100644
--- a/content/public/test/render_view_test.cc
+++ b/content/public/test/render_view_test.cc
@@ -38,6 +38,7 @@
 #include "content/test/mock_render_process.h"
 #include "content/test/test_content_client.h"
 #include "content/test/test_render_frame.h"
+#include "net/base/escape.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "third_party/WebKit/public/platform/WebGestureEvent.h"
@@ -195,7 +196,7 @@
 
 void RenderViewTest::LoadHTML(const char* html) {
   std::string url_string = "data:text/html;charset=utf-8,";
-  url_string.append(html);
+  url_string.append(net::EscapeQueryParamValue(html, false));
   GURL url(url_string);
   WebURLRequest request(url);
   request.SetCheckForBrowserSideNavigation(false);
diff --git a/content/renderer/loader/sync_load_context.cc b/content/renderer/loader/sync_load_context.cc
index b8d9453..4ad31b2 100644
--- a/content/renderer/loader/sync_load_context.cc
+++ b/content/renderer/loader/sync_load_context.cc
@@ -107,6 +107,7 @@
 void SyncLoadContext::OnCompletedRequest(
     const network::URLLoaderCompletionStatus& status) {
   response_->error_code = status.error_code;
+  response_->extended_error_code = status.extended_error_code;
   if (status.cors_error_status)
     response_->cors_error = status.cors_error_status->cors_error;
   response_->encoded_data_length = status.encoded_data_length;
diff --git a/content/renderer/loader/sync_load_response.h b/content/renderer/loader/sync_load_response.h
index 049f2a28..03b26fef 100644
--- a/content/renderer/loader/sync_load_response.h
+++ b/content/renderer/loader/sync_load_response.h
@@ -24,6 +24,9 @@
   // The response error code.
   int error_code = 0;
 
+  // The response extended error code.
+  int extended_error_code = 0;
+
   // Optional CORS error details.
   base::Optional<network::mojom::CORSError> cors_error;
 
diff --git a/content/renderer/loader/web_url_loader_impl.cc b/content/renderer/loader/web_url_loader_impl.cc
index 414a24f..3f91217 100644
--- a/content/renderer/loader/web_url_loader_impl.cc
+++ b/content/renderer/loader/web_url_loader_impl.cc
@@ -968,7 +968,8 @@
       client_->DidFail(
           status.cors_error_status
               ? WebURLError(*status.cors_error_status, has_copy_in_cache, url_)
-              : WebURLError(status.error_code, has_copy_in_cache,
+              : WebURLError(status.error_code, status.extended_error_code,
+                            has_copy_in_cache,
                             WebURLError::IsWebSecurityViolation::kFalse, url_),
           total_transfer_size, encoded_body_size, status.decoded_body_length);
     } else {
@@ -1326,7 +1327,8 @@
           error_code == net::ERR_ABORTED
               ? WebURLError::IsWebSecurityViolation::kTrue
               : WebURLError::IsWebSecurityViolation::kFalse;
-      error = WebURLError(error_code, WebURLError::HasCopyInCache::kFalse,
+      error = WebURLError(error_code, sync_load_response.extended_error_code,
+                          WebURLError::HasCopyInCache::kFalse,
                           is_web_security_violation, final_url);
     }
     return;
diff --git a/content/renderer/media/webrtc/rtc_rtp_sender_unittest.cc b/content/renderer/media/webrtc/rtc_rtp_sender_unittest.cc
index 1be22d6..c67040fe 100644
--- a/content/renderer/media/webrtc/rtc_rtp_sender_unittest.cc
+++ b/content/renderer/media/webrtc/rtc_rtp_sender_unittest.cc
@@ -207,7 +207,8 @@
   std::move(replaceTrackRunLoopAndGetResult).Run();
 }
 
-TEST_F(RTCRtpSenderTest, CopiedSenderSharesInternalStates) {
+// TODO(crbug.com/812296): Disabled since flaky.
+TEST_F(RTCRtpSenderTest, DISABLED_CopiedSenderSharesInternalStates) {
   auto web_track = CreateWebTrack("track_id");
   sender_ = CreateSender(web_track);
   auto copy = std::make_unique<RTCRtpSender>(*sender_);
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 00f0670..6fce527 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -3261,7 +3261,7 @@
 
   // Send the provisional load failure.
   WebURLError error(
-      error_code,
+      error_code, 0,
       has_stale_copy_in_cache ? WebURLError::HasCopyInCache::kTrue
                               : WebURLError::HasCopyInCache::kFalse,
       WebURLError::IsWebSecurityViolation::kFalse, common_params.url);
diff --git a/content/renderer/render_frame_metadata_observer_impl.cc b/content/renderer/render_frame_metadata_observer_impl.cc
index 10015f4..3230f7d 100644
--- a/content/renderer/render_frame_metadata_observer_impl.cc
+++ b/content/renderer/render_frame_metadata_observer_impl.cc
@@ -35,7 +35,8 @@
   // these can be reported while testing is enabled.
   bool send_metadata = false;
   if (report_all_frame_submissions_for_testing_enabled_) {
-    render_frame_metadata_observer_client_->OnFrameSubmissionForTesting();
+    render_frame_metadata_observer_client_->OnFrameSubmissionForTesting(
+        frame_token_allocator_->GetOrAllocateFrameToken());
     send_metadata = last_render_frame_metadata_ != metadata;
   } else {
     // Sending |root_scroll_offset| outside of tests would leave the browser
@@ -48,7 +49,7 @@
 
   if (send_metadata) {
     render_frame_metadata_observer_client_->OnRenderFrameMetadataChanged(
-        metadata);
+        frame_token_allocator_->GetOrAllocateFrameToken(), metadata);
   }
 
   last_render_frame_metadata_ = metadata;
diff --git a/content/renderer/service_worker/service_worker_context_client.cc b/content/renderer/service_worker/service_worker_context_client.cc
index ea22c33..edc0bba 100644
--- a/content/renderer/service_worker/service_worker_context_client.cc
+++ b/content/renderer/service_worker/service_worker_context_client.cc
@@ -924,7 +924,7 @@
   TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("ServiceWorker", "EVALUATE_SCRIPT", this);
 }
 
-void ServiceWorkerContextClient::DidEvaluateWorkerScript(bool success) {
+void ServiceWorkerContextClient::DidEvaluateClassicScript(bool success) {
   DCHECK(worker_task_runner_->RunsTasksInCurrentSequence());
   (*instance_host_)->OnScriptEvaluated(success);
 
diff --git a/content/renderer/service_worker/service_worker_context_client.h b/content/renderer/service_worker/service_worker_context_client.h
index 7915ad80..d290e36a 100644
--- a/content/renderer/service_worker/service_worker_context_client.h
+++ b/content/renderer/service_worker/service_worker_context_client.h
@@ -132,7 +132,7 @@
   void WorkerScriptLoaded() override;
   void WorkerContextStarted(
       blink::WebServiceWorkerContextProxy* proxy) override;
-  void DidEvaluateWorkerScript(bool success) override;
+  void DidEvaluateClassicScript(bool success) override;
   void DidInitializeWorkerContext(v8::Local<v8::Context> context) override;
   void WillDestroyWorkerContext(v8::Local<v8::Context> context) override;
   void WorkerContextDestroyed() override;
diff --git a/content/renderer/service_worker/service_worker_context_client_unittest.cc b/content/renderer/service_worker/service_worker_context_client_unittest.cc
index 3fd5bed..71e5978 100644
--- a/content/renderer/service_worker/service_worker_context_client_unittest.cc
+++ b/content/renderer/service_worker/service_worker_context_client_unittest.cc
@@ -287,7 +287,7 @@
   std::unique_ptr<ServiceWorkerContextClient> context_client;
   context_client = CreateContextClient(&pipes);
   context_client->WorkerContextStarted(&mock_proxy);
-  context_client->DidEvaluateWorkerScript(true /* success */);
+  context_client->DidEvaluateClassicScript(true /* success */);
   task_runner()->RunUntilIdle();
   EXPECT_TRUE(mock_proxy.fetch_events().empty());
 
@@ -318,7 +318,7 @@
       CreateContextClient(&pipes);
   MockWebServiceWorkerContextProxy mock_proxy;
   context_client->WorkerContextStarted(&mock_proxy);
-  context_client->DidEvaluateWorkerScript(true /* success */);
+  context_client->DidEvaluateClassicScript(true /* success */);
   task_runner()->RunUntilIdle();
   EXPECT_TRUE(mock_proxy.fetch_events().empty());
 
@@ -357,7 +357,7 @@
       CreateContextClient(&pipes);
   MockWebServiceWorkerContextProxy mock_proxy;
   context_client->WorkerContextStarted(&mock_proxy);
-  context_client->DidEvaluateWorkerScript(true /* success */);
+  context_client->DidEvaluateClassicScript(true /* success */);
   task_runner()->RunUntilIdle();
   EXPECT_TRUE(mock_proxy.fetch_events().empty());
 
@@ -406,7 +406,7 @@
       CreateContextClient(&pipes);
   MockWebServiceWorkerContextProxy mock_proxy;
   context_client->WorkerContextStarted(&mock_proxy);
-  context_client->DidEvaluateWorkerScript(true /* success */);
+  context_client->DidEvaluateClassicScript(true /* success */);
   task_runner()->RunUntilIdle();
   EXPECT_TRUE(mock_proxy.fetch_events().empty());
   bool is_idle = false;
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 9fa23b3c..0aece684 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1390,6 +1390,7 @@
     "../browser/renderer_host/compositor_resize_lock_unittest.cc",
     "../browser/renderer_host/cursor_manager_unittest.cc",
     "../browser/renderer_host/dwrite_font_proxy_message_filter_win_unittest.cc",
+    "../browser/renderer_host/frame_token_message_queue_unittest.cc",
     "../browser/renderer_host/input/fling_controller_unittest.cc",
     "../browser/renderer_host/input/gesture_event_queue_unittest.cc",
     "../browser/renderer_host/input/input_router_impl_unittest.cc",
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 4cb499b8a..ef0538f 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -226,14 +226,7 @@
         'blitframebuffer-srgb-and-linear-drawbuffers.html',
         ['win', 'nvidia', 'opengl'], bug=2355) # ANGLE bug ID
 
-    self.Flaky('deqp/functional/gles3/transformfeedback/' +
-        'basic_types_interleaved_points.html',
-        ['win', ('nvidia', 0x1cb3), 'opengl'], bug=822733)
-    self.Flaky('deqp/functional/gles3/transformfeedback/' +
-        'basic_types_interleaved_lines.html',
-        ['win', ('nvidia', 0x1cb3), 'opengl'], bug=822733)
-    self.Flaky('deqp/functional/gles3/transformfeedback/' +
-        'basic_types_interleaved_triangles.html',
+    self.Flaky('deqp/functional/gles3/transformfeedback/*',
         ['win', ('nvidia', 0x1cb3), 'opengl'], bug=822733)
 
     # Win / AMD
diff --git a/device/bluetooth/bluetooth_adapter_unittest.cc b/device/bluetooth/bluetooth_adapter_unittest.cc
index 1e92bda..a5a3966 100644
--- a/device/bluetooth/bluetooth_adapter_unittest.cc
+++ b/device/bluetooth/bluetooth_adapter_unittest.cc
@@ -433,7 +433,11 @@
 
 // TODO(scheib): Enable BluetoothTest fixture tests on all platforms.
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
-TEST_F(BluetoothTest, ConstructDefaultAdapter) {
+#define MAYBE_ConstructDefaultAdapter ConstructDefaultAdapter
+#else
+#define MAYBE_ConstructDefaultAdapter DISABLED_ConstructDefaultAdapter
+#endif
+TEST_F(BluetoothTest, MAYBE_ConstructDefaultAdapter) {
   InitWithDefaultAdapter();
   if (!adapter_->IsPresent()) {
     LOG(WARNING) << "Bluetooth adapter not present; skipping unit test.";
@@ -456,11 +460,15 @@
   EXPECT_FALSE(adapter_->IsDiscoverable());
   EXPECT_FALSE(adapter_->IsDiscovering());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 // TODO(scheib): Enable BluetoothTest fixture tests on all platforms.
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
-TEST_F(BluetoothTest, ConstructWithoutDefaultAdapter) {
+#define MAYBE_ConstructWithoutDefaultAdapter ConstructWithoutDefaultAdapter
+#else
+#define MAYBE_ConstructWithoutDefaultAdapter \
+  DISABLED_ConstructWithoutDefaultAdapter
+#endif
+TEST_F(BluetoothTest, MAYBE_ConstructWithoutDefaultAdapter) {
   InitWithoutDefaultAdapter();
   EXPECT_EQ(adapter_->GetAddress(), "");
   EXPECT_EQ(adapter_->GetName(), "");
@@ -469,11 +477,14 @@
   EXPECT_FALSE(adapter_->IsDiscoverable());
   EXPECT_FALSE(adapter_->IsDiscovering());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 // TODO(scheib): Enable BluetoothTest fixture tests on all platforms.
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
-TEST_F(BluetoothTest, ConstructFakeAdapter) {
+#define MAYBE_ConstructFakeAdapter ConstructFakeAdapter
+#else
+#define MAYBE_ConstructFakeAdapter DISABLED_ConstructFakeAdapter
+#endif
+TEST_F(BluetoothTest, MAYBE_ConstructFakeAdapter) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -486,12 +497,15 @@
   EXPECT_FALSE(adapter_->IsDiscoverable());
   EXPECT_FALSE(adapter_->IsDiscovering());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 // TODO(scheib): Enable BluetoothTest fixture tests on all platforms.
 #if defined(OS_ANDROID)
+#define MAYBE_DiscoverySession DiscoverySession
+#else
+#define MAYBE_DiscoverySession DISABLED_DiscoverySession
+#endif
 // Starts and Stops a discovery session.
-TEST_F(BluetoothTest, DiscoverySession) {
+TEST_F(BluetoothTest, MAYBE_DiscoverySession) {
   InitWithFakeAdapter();
   EXPECT_FALSE(adapter_->IsDiscovering());
 
@@ -508,7 +522,6 @@
   EXPECT_FALSE(adapter_->IsDiscovering());
   EXPECT_FALSE(discovery_sessions_[0]->IsActive());
 }
-#endif  // defined(OS_ANDROID)
 
 // Android only: this test is specific for Android and should not be
 // enabled for other platforms.
@@ -548,8 +561,12 @@
 #endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_NoPermissions NoPermissions
+#else
+#define MAYBE_NoPermissions DISABLED_NoPermissions
+#endif
 // Checks that discovery fails (instead of hanging) when permissions are denied.
-TEST_F(BluetoothTest, NoPermissions) {
+TEST_F(BluetoothTest, MAYBE_NoPermissions) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -567,7 +584,6 @@
   EXPECT_EQ(0, callback_count_);
   EXPECT_EQ(1, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 // Android-only: Only Android requires location services to be turned on to scan
 // for Bluetooth devices.
@@ -592,8 +608,12 @@
 #endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_DiscoverLowEnergyDevice DiscoverLowEnergyDevice
+#else
+#define MAYBE_DiscoverLowEnergyDevice DISABLED_DiscoverLowEnergyDevice
+#endif
 // Discovers a device.
-TEST_F(BluetoothTest, DiscoverLowEnergyDevice) {
+TEST_F(BluetoothTest, MAYBE_DiscoverLowEnergyDevice) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -608,11 +628,14 @@
   BluetoothDevice* device = adapter_->GetDevice(observer.last_device_address());
   EXPECT_TRUE(device);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_DiscoverLowEnergyDeviceTwice DiscoverLowEnergyDeviceTwice
+#else
+#define MAYBE_DiscoverLowEnergyDeviceTwice DISABLED_DiscoverLowEnergyDeviceTwice
+#endif
 // Discovers the same device multiple times.
-TEST_F(BluetoothTest, DiscoverLowEnergyDeviceTwice) {
+TEST_F(BluetoothTest, MAYBE_DiscoverLowEnergyDeviceTwice) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -634,13 +657,18 @@
   EXPECT_EQ(0, observer.device_added_count());
   EXPECT_EQ(1u, adapter_->GetDevices().size());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_DiscoverLowEnergyDeviceWithUpdatedUUIDs \
+  DiscoverLowEnergyDeviceWithUpdatedUUIDs
+#else
+#define MAYBE_DiscoverLowEnergyDeviceWithUpdatedUUIDs \
+  DISABLED_DiscoverLowEnergyDeviceWithUpdatedUUIDs
+#endif
 // Discovers a device, and then again with new Service UUIDs.
 // Makes sure we don't create another device when we've found the
 // device in the past.
-TEST_F(BluetoothTest, DiscoverLowEnergyDeviceWithUpdatedUUIDs) {
+TEST_F(BluetoothTest, MAYBE_DiscoverLowEnergyDeviceWithUpdatedUUIDs) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -667,11 +695,15 @@
   EXPECT_EQ(1, observer.device_changed_count());
   EXPECT_EQ(1u, adapter_->GetDevices().size());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_DiscoverMultipleLowEnergyDevices DiscoverMultipleLowEnergyDevices
+#else
+#define MAYBE_DiscoverMultipleLowEnergyDevices \
+  DISABLED_DiscoverMultipleLowEnergyDevices
+#endif
 // Discovers multiple devices when addresses vary.
-TEST_F(BluetoothTest, DiscoverMultipleLowEnergyDevices) {
+TEST_F(BluetoothTest, MAYBE_DiscoverMultipleLowEnergyDevices) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -686,7 +718,6 @@
   EXPECT_EQ(2, observer.device_added_count());
   EXPECT_EQ(2u, adapter_->GetDevices().size());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 // TODO(https://crbug.com/804356): Enable this test on Windows as well.
 #if defined(OS_WIN)
@@ -725,10 +756,14 @@
 }
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_TogglePowerFakeAdapter_Twice TogglePowerFakeAdapter_Twice
+#else
+#define MAYBE_TogglePowerFakeAdapter_Twice DISABLED_TogglePowerFakeAdapter_Twice
+#endif
 // These tests are not relevant for BlueZ and Windows. On these platforms the
 // corresponding system APIs are blocking or use callbacks, so that it is not
 // necessary to store pending callbacks and wait for the appropriate events.
-TEST_F(BluetoothTest, TogglePowerFakeAdapter_Twice) {
+TEST_F(BluetoothTest, MAYBE_TogglePowerFakeAdapter_Twice) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -762,7 +797,14 @@
   EXPECT_EQ(2, observer.powered_changed_count());
 }
 
-TEST_F(BluetoothTest, TogglePowerFakeAdapter_WithinCallback_On_Off) {
+#if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_TogglePowerFakeAdapter_WithinCallback_On_Off \
+  TogglePowerFakeAdapter_WithinCallback_On_Off
+#else
+#define MAYBE_TogglePowerFakeAdapter_WithinCallback_On_Off \
+  DISABLED_TogglePowerFakeAdapter_WithinCallback_On_Off
+#endif
+TEST_F(BluetoothTest, MAYBE_TogglePowerFakeAdapter_WithinCallback_On_Off) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -787,7 +829,14 @@
   EXPECT_EQ(2, observer.powered_changed_count());
 }
 
-TEST_F(BluetoothTest, TogglePowerFakeAdapter_WithinCallback_Off_On) {
+#if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_TogglePowerFakeAdapter_WithinCallback_Off_On \
+  TogglePowerFakeAdapter_WithinCallback_Off_On
+#else
+#define MAYBE_TogglePowerFakeAdapter_WithinCallback_Off_On \
+  DISABLED_TogglePowerFakeAdapter_WithinCallback_Off_On
+#endif
+TEST_F(BluetoothTest, MAYBE_TogglePowerFakeAdapter_WithinCallback_Off_On) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -819,7 +868,14 @@
   EXPECT_EQ(3, observer.powered_changed_count());
 }
 
-TEST_F(BluetoothTest, TogglePowerFakeAdapter_DestroyWithPending) {
+#if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_TogglePowerFakeAdapter_DestroyWithPending \
+  TogglePowerFakeAdapter_DestroyWithPending
+#else
+#define MAYBE_TogglePowerFakeAdapter_DestroyWithPending \
+  DISABLED_TogglePowerFakeAdapter_DestroyWithPending
+#endif
+TEST_F(BluetoothTest, MAYBE_TogglePowerFakeAdapter_DestroyWithPending) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -858,10 +914,13 @@
   scoped_task_environment_.RunUntilIdle();
   EXPECT_TRUE(error_callback_called);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID)
-TEST_F(BluetoothTest, TogglePowerBeforeScan) {
+#define MAYBE_TogglePowerBeforeScan TogglePowerBeforeScan
+#else
+#define MAYBE_TogglePowerBeforeScan DISABLED_TogglePowerBeforeScan
+#endif
+TEST_F(BluetoothTest, MAYBE_TogglePowerBeforeScan) {
   InitWithFakeAdapter();
   TestBluetoothAdapterObserver observer(adapter_);
 
@@ -895,11 +954,16 @@
   ASSERT_EQ((size_t)1, discovery_sessions_.size());
   EXPECT_TRUE(discovery_sessions_[0]->IsActive());
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_MACOSX)
+#define MAYBE_TurnOffAdapterWithConnectedDevice \
+  TurnOffAdapterWithConnectedDevice
+#else
+#define MAYBE_TurnOffAdapterWithConnectedDevice \
+  DISABLED_TurnOffAdapterWithConnectedDevice
+#endif
 // TODO(crbug.com/725270): Enable on relevant platforms.
-TEST_F(BluetoothTest, TurnOffAdapterWithConnectedDevice) {
+TEST_F(BluetoothTest, MAYBE_TurnOffAdapterWithConnectedDevice) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -925,10 +989,13 @@
   EXPECT_FALSE(device->IsConnected());
   EXPECT_FALSE(device->IsGattConnected());
 }
-#endif  // defined(OS_MACOSX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
-TEST_F(BluetoothTest, RegisterLocalGattServices) {
+#define MAYBE_RegisterLocalGattServices RegisterLocalGattServices
+#else
+#define MAYBE_RegisterLocalGattServices DISABLED_RegisterLocalGattServices
+#endif
+TEST_F(BluetoothTest, MAYBE_RegisterLocalGattServices) {
   InitWithFakeAdapter();
   base::WeakPtr<BluetoothLocalGattService> service =
       BluetoothLocalGattService::Create(
@@ -963,13 +1030,16 @@
   service->Unregister(GetCallback(Call::NOT_EXPECTED),
                       GetGattErrorCallback(Call::EXPECTED));
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 // This test should only be enabled for platforms that uses the
 // BluetoothAdapter#RemoveOutdatedDevices function to purge outdated
 // devices.
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
-TEST_F(BluetoothTest, EnsureUpdatedTimestamps) {
+#define MAYBE_EnsureUpdatedTimestamps EnsureUpdatedTimestamps
+#else
+#define MAYBE_EnsureUpdatedTimestamps DISABLED_EnsureUpdatedTimestamps
+#endif
+TEST_F(BluetoothTest, MAYBE_EnsureUpdatedTimestamps) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -999,13 +1069,16 @@
   base::Time third_timestamp = device->GetLastUpdateTime();
   EXPECT_TRUE(second_timestamp == third_timestamp);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 // This test should only be enabled for platforms that uses the
 // BluetoothAdapter#RemoveOutdatedDevices function to purge outdated
 // devices.
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
-TEST_F(BluetoothTest, RemoveOutdatedDevices) {
+#define MAYBE_RemoveOutdatedDevices RemoveOutdatedDevices
+#else
+#define MAYBE_RemoveOutdatedDevices DISABLED_RemoveOutdatedDevices
+#endif
+TEST_F(BluetoothTest, MAYBE_RemoveOutdatedDevices) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1025,13 +1098,17 @@
   EXPECT_EQ(1u, adapter_->GetDevices().size());
   EXPECT_EQ(adapter_->GetDevices()[0]->GetAddress(), device2->GetAddress());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 // This test should only be enabled for platforms that uses the
 // BluetoothAdapter#RemoveOutdatedDevices function to purge outdated
 // devices.
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
-TEST_F(BluetoothTest, RemoveOutdatedDeviceGattConnect) {
+#define MAYBE_RemoveOutdatedDeviceGattConnect RemoveOutdatedDeviceGattConnect
+#else
+#define MAYBE_RemoveOutdatedDeviceGattConnect \
+  DISABLED_RemoveOutdatedDeviceGattConnect
+#endif
+TEST_F(BluetoothTest, MAYBE_RemoveOutdatedDeviceGattConnect) {
   // Test that a device with GATT connection isn't removed.
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
@@ -1051,7 +1128,6 @@
   EXPECT_EQ(0, observer.device_removed_count());
   EXPECT_EQ(1u, adapter_->GetDevices().size());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_MACOSX)
 // Simulate two devices being connected before calling
diff --git a/device/bluetooth/bluetooth_device_unittest.cc b/device/bluetooth/bluetooth_device_unittest.cc
index 5d62ca3..a84eef0 100644
--- a/device/bluetooth/bluetooth_device_unittest.cc
+++ b/device/bluetooth/bluetooth_device_unittest.cc
@@ -27,7 +27,6 @@
 
 namespace device {
 
-#if defined(OS_ANDROID) || defined(OS_MACOSX)
 
 namespace {
 
@@ -41,7 +40,6 @@
 
 }  // namespace
 
-#endif
 
 using UUIDSet = BluetoothDevice::UUIDSet;
 using ServiceDataMap = BluetoothDevice::ServiceDataMap;
@@ -177,7 +175,13 @@
 
 #if defined(OS_MACOSX) || defined(OS_CHROMEOS) || defined(OS_LINUX) || \
     defined(OS_ANDROID)
-TEST_F(BluetoothTest, GetServiceDataUUIDs_GetServiceDataForUUID) {
+#define MAYBE_GetServiceDataUUIDs_GetServiceDataForUUID \
+  GetServiceDataUUIDs_GetServiceDataForUUID
+#else
+#define MAYBE_GetServiceDataUUIDs_GetServiceDataForUUID \
+  DISABLED_GetServiceDataUUIDs_GetServiceDataForUUID
+#endif
+TEST_F(BluetoothTest, MAYBE_GetServiceDataUUIDs_GetServiceDataForUUID) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -265,13 +269,15 @@
                          BluetoothUUID(kTestUUIDImmediateAlert)));
 #endif  // !defined(OS_LINUX) && !defined(OS_CHROMEOS)
 }
-#endif  // defined(OS_MACOSX) || defined(OS_CHROMEOS) || defined(OS_LINUX) ||
-        // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_AdvertisementData_Discovery AdvertisementData_Discovery
+#else
+#define MAYBE_AdvertisementData_Discovery DISABLED_AdvertisementData_Discovery
+#endif
 // Tests that the Advertisement Data fields are correctly updated during
 // discovery.
-TEST_F(BluetoothTest, AdvertisementData_Discovery) {
+TEST_F(BluetoothTest, MAYBE_AdvertisementData_Discovery) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -379,11 +385,14 @@
             device->GetManufacturerData());
   EXPECT_EQ(ToInt8(TestTxPower::LOWEST), device->GetInquiryTxPower().value());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_GetUUIDs_Connection GetUUIDs_Connection
+#else
+#define MAYBE_GetUUIDs_Connection DISABLED_GetUUIDs_Connection
+#endif
 // Tests Advertisement Data is updated correctly during a connection.
-TEST_F(BluetoothTest, GetUUIDs_Connection) {
+TEST_F(BluetoothTest, MAYBE_GetUUIDs_Connection) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -463,7 +472,6 @@
   EXPECT_EQ(1, observer.device_changed_count());
   EXPECT_TRUE(device->GetUUIDs().empty());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_MACOSX)
 // Tests that receiving 2 notifications in a row from macOS that services has
@@ -564,12 +572,18 @@
 
   EXPECT_EQ(1u, device->GetGattServices().size());
 }
-#endif  // defined(OS_MACOSX)
+#endif
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_AdvertisementData_DiscoveryDuringConnection \
+  AdvertisementData_DiscoveryDuringConnection
+#else
+#define MAYBE_AdvertisementData_DiscoveryDuringConnection \
+  DISABLED_AdvertisementData_DiscoveryDuringConnection
+#endif
 // Tests Advertisement Data is updated correctly when we start discovery
 // during a connection.
-TEST_F(BluetoothTest, AdvertisementData_DiscoveryDuringConnection) {
+TEST_F(BluetoothTest, MAYBE_AdvertisementData_DiscoveryDuringConnection) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -691,10 +705,15 @@
 
   EXPECT_TRUE(device->GetUUIDs().empty());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
-TEST_F(BluetoothTest, AdvertisementData_ConnectionDuringDiscovery) {
+#define MAYBE_AdvertisementData_ConnectionDuringDiscovery \
+  AdvertisementData_ConnectionDuringDiscovery
+#else
+#define MAYBE_AdvertisementData_ConnectionDuringDiscovery \
+  DISABLED_AdvertisementData_ConnectionDuringDiscovery
+#endif
+TEST_F(BluetoothTest, MAYBE_AdvertisementData_ConnectionDuringDiscovery) {
   // Tests that the Advertisement Data is correctly updated when
   // the device connects during discovery.
   if (!PlatformSupportsLowEnergy()) {
@@ -835,12 +854,15 @@
 #endif  // defined(OS_MACOSX)  || defined(OS_ANDROID)
   EXPECT_FALSE(device->GetInquiryTxPower());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_CHROMEOS) || defined(OS_MACOSX) || \
     defined(OS_LINUX)
+#define MAYBE_GetName_NullName GetName_NullName
+#else
+#define MAYBE_GetName_NullName DISABLED_GetName_NullName
+#endif
 // GetName for Device with no name.
-TEST_F(BluetoothTest, GetName_NullName) {
+TEST_F(BluetoothTest, MAYBE_GetName_NullName) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -858,8 +880,6 @@
   BluetoothDevice* device = SimulateLowEnergyDevice(5);
   EXPECT_FALSE(device->GetName());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_CHROMEOS)  || defined(OS_MACOSX) ||
-        // defined(OS_LINUX)
 
 // TODO(506415): Test GetNameForDisplay with a device with no name.
 // BluetoothDevice::GetAddressWithLocalizedDeviceTypeName() will run, which
@@ -869,8 +889,12 @@
 // file.
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_CreateGattConnection CreateGattConnection
+#else
+#define MAYBE_CreateGattConnection DISABLED_CreateGattConnection
+#endif
 // Basic CreateGattConnection test.
-TEST_F(BluetoothTest, CreateGattConnection) {
+TEST_F(BluetoothTest, MAYBE_CreateGattConnection) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -889,10 +913,15 @@
   EXPECT_TRUE(device->IsGattConnected());
   EXPECT_TRUE(gatt_connections_[0]->IsConnected());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
-TEST_F(BluetoothTest, DisconnectionNotifiesDeviceChanged) {
+#define MAYBE_DisconnectionNotifiesDeviceChanged \
+  DisconnectionNotifiesDeviceChanged
+#else
+#define MAYBE_DisconnectionNotifiesDeviceChanged \
+  DISABLED_DisconnectionNotifiesDeviceChanged
+#endif
+TEST_F(BluetoothTest, MAYBE_DisconnectionNotifiesDeviceChanged) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -916,12 +945,15 @@
   EXPECT_FALSE(device->IsConnected());
   EXPECT_FALSE(device->IsGattConnected());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_BluetoothGattConnection BluetoothGattConnection
+#else
+#define MAYBE_BluetoothGattConnection DISABLED_BluetoothGattConnection
+#endif
 // Creates BluetoothGattConnection instances and tests that the interface
 // functions even when some Disconnect and the BluetoothDevice is destroyed.
-TEST_F(BluetoothTest, BluetoothGattConnection) {
+TEST_F(BluetoothTest, MAYBE_BluetoothGattConnection) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -984,12 +1016,17 @@
   EXPECT_EQ(device_address, gatt_connections_[0]->GetDeviceAddress());
   EXPECT_EQ(device_address, gatt_connections_[1]->GetDeviceAddress());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_BluetoothGattConnection_ConnectWithMultipleOSConnections \
+  BluetoothGattConnection_ConnectWithMultipleOSConnections
+#else
+#define MAYBE_BluetoothGattConnection_ConnectWithMultipleOSConnections \
+  DISABLED_BluetoothGattConnection_ConnectWithMultipleOSConnections
+#endif
 // Calls CreateGattConnection then simulates multiple connections from platform.
 TEST_F(BluetoothTest,
-       BluetoothGattConnection_ConnectWithMultipleOSConnections) {
+       MAYBE_BluetoothGattConnection_ConnectWithMultipleOSConnections) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1028,11 +1065,16 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(gatt_connections_[0]->IsConnected());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_BluetoothGattConnection_AlreadyConnected \
+  BluetoothGattConnection_AlreadyConnected
+#else
+#define MAYBE_BluetoothGattConnection_AlreadyConnected \
+  DISABLED_BluetoothGattConnection_AlreadyConnected
+#endif
 // Calls CreateGattConnection after already connected.
-TEST_F(BluetoothTest, BluetoothGattConnection_AlreadyConnected) {
+TEST_F(BluetoothTest, MAYBE_BluetoothGattConnection_AlreadyConnected) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1055,12 +1097,17 @@
   EXPECT_EQ(0, gatt_connection_attempts_);
   EXPECT_TRUE(gatt_connections_[1]->IsConnected());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_BluetoothGattConnection_NewConnectionLeavesPreviousDisconnected \
+  BluetoothGattConnection_NewConnectionLeavesPreviousDisconnected
+#else
+#define MAYBE_BluetoothGattConnection_NewConnectionLeavesPreviousDisconnected \
+  DISABLED_BluetoothGattConnection_NewConnectionLeavesPreviousDisconnected
+#endif
 // Creates BluetoothGattConnection after one exists that has disconnected.
 TEST_F(BluetoothTest,
-       BluetoothGattConnection_NewConnectionLeavesPreviousDisconnected) {
+       MAYBE_BluetoothGattConnection_NewConnectionLeavesPreviousDisconnected) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1091,11 +1138,17 @@
          "connection is created.";
   EXPECT_TRUE(gatt_connections_[1]->IsConnected());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_BluetoothGattConnection_DisconnectWhenObjectsDestroyed \
+  BluetoothGattConnection_DisconnectWhenObjectsDestroyed
+#else
+#define MAYBE_BluetoothGattConnection_DisconnectWhenObjectsDestroyed \
+  DISABLED_BluetoothGattConnection_DisconnectWhenObjectsDestroyed
+#endif
 // Deletes BluetoothGattConnection causing disconnection.
-TEST_F(BluetoothTest, BluetoothGattConnection_DisconnectWhenObjectsDestroyed) {
+TEST_F(BluetoothTest,
+       MAYBE_BluetoothGattConnection_DisconnectWhenObjectsDestroyed) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1117,11 +1170,16 @@
   gatt_connections_.clear();
   EXPECT_EQ(1, gatt_disconnection_attempts_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_BluetoothGattConnection_DisconnectInProgress \
+  BluetoothGattConnection_DisconnectInProgress
+#else
+#define MAYBE_BluetoothGattConnection_DisconnectInProgress \
+  DISABLED_BluetoothGattConnection_DisconnectInProgress
+#endif
 // Starts process of disconnecting and then calls BluetoothGattConnection.
-TEST_F(BluetoothTest, BluetoothGattConnection_DisconnectInProgress) {
+TEST_F(BluetoothTest, MAYBE_BluetoothGattConnection_DisconnectInProgress) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1160,12 +1218,17 @@
   for (const auto& connection : gatt_connections_)
     EXPECT_FALSE(connection->IsConnected());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_BluetoothGattConnection_SimulateDisconnect \
+  BluetoothGattConnection_SimulateDisconnect
+#else
+#define MAYBE_BluetoothGattConnection_SimulateDisconnect \
+  DISABLED_BluetoothGattConnection_SimulateDisconnect
+#endif
 // Calls CreateGattConnection but receives notice that the device disconnected
 // before it ever connects.
-TEST_F(BluetoothTest, BluetoothGattConnection_SimulateDisconnect) {
+TEST_F(BluetoothTest, MAYBE_BluetoothGattConnection_SimulateDisconnect) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1184,11 +1247,17 @@
   for (const auto& connection : gatt_connections_)
     EXPECT_FALSE(connection->IsConnected());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_BluetoothGattConnection_DisconnectGatt_SimulateConnect \
+  BluetoothGattConnection_DisconnectGatt_SimulateConnect
+#else
+#define MAYBE_BluetoothGattConnection_DisconnectGatt_SimulateConnect \
+  DISABLED_BluetoothGattConnection_DisconnectGatt_SimulateConnect
+#endif
 // Calls CreateGattConnection & DisconnectGatt, then simulates connection.
-TEST_F(BluetoothTest, BluetoothGattConnection_DisconnectGatt_SimulateConnect) {
+TEST_F(BluetoothTest,
+       MAYBE_BluetoothGattConnection_DisconnectGatt_SimulateConnect) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1216,12 +1285,17 @@
   EXPECT_EQ(0, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_BluetoothGattConnection_DisconnectGatt_SimulateDisconnect \
+  BluetoothGattConnection_DisconnectGatt_SimulateDisconnect
+#else
+#define MAYBE_BluetoothGattConnection_DisconnectGatt_SimulateDisconnect \
+  DISABLED_BluetoothGattConnection_DisconnectGatt_SimulateDisconnect
+#endif
 // Calls CreateGattConnection & DisconnectGatt, then simulates disconnection.
 TEST_F(BluetoothTest,
-       BluetoothGattConnection_DisconnectGatt_SimulateDisconnect) {
+       MAYBE_BluetoothGattConnection_DisconnectGatt_SimulateDisconnect) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1242,12 +1316,17 @@
   for (const auto& connection : gatt_connections_)
     EXPECT_FALSE(connection->IsConnected());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_BluetoothGattConnection_DisconnectGatt_Cleanup \
+  BluetoothGattConnection_DisconnectGatt_Cleanup
+#else
+#define MAYBE_BluetoothGattConnection_DisconnectGatt_Cleanup \
+  DISABLED_BluetoothGattConnection_DisconnectGatt_Cleanup
+#endif
 // Calls CreateGattConnection & DisconnectGatt, then checks that gatt services
 // have been cleaned up.
-TEST_F(BluetoothTest, BluetoothGattConnection_DisconnectGatt_Cleanup) {
+TEST_F(BluetoothTest, MAYBE_BluetoothGattConnection_DisconnectGatt_Cleanup) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1298,12 +1377,17 @@
   EXPECT_EQ(1u, device->GetGattServices().size());
   EXPECT_EQ(2, observer.gatt_services_discovered_count());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_BluetoothGattConnection_ErrorAfterConnection \
+  BluetoothGattConnection_ErrorAfterConnection
+#else
+#define MAYBE_BluetoothGattConnection_ErrorAfterConnection \
+  DISABLED_BluetoothGattConnection_ErrorAfterConnection
+#endif
 // Calls CreateGattConnection, but simulate errors connecting. Also, verifies
 // multiple errors should only invoke callbacks once.
-TEST_F(BluetoothTest, BluetoothGattConnection_ErrorAfterConnection) {
+TEST_F(BluetoothTest, MAYBE_BluetoothGattConnection_ErrorAfterConnection) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1331,10 +1415,13 @@
   for (const auto& connection : gatt_connections_)
     EXPECT_FALSE(connection->IsConnected());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MACOSX)
-TEST_F(BluetoothTest, GattServices_ObserversCalls) {
+#define MAYBE_GattServices_ObserversCalls GattServices_ObserversCalls
+#else
+#define MAYBE_GattServices_ObserversCalls DISABLED_GattServices_ObserversCalls
+#endif
+TEST_F(BluetoothTest, MAYBE_GattServices_ObserversCalls) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1357,10 +1444,14 @@
 
   EXPECT_EQ(1, observer.gatt_services_discovered_count());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MACOSX)
-TEST_F(BluetoothTest, GattServicesDiscovered_Success) {
+#define MAYBE_GattServicesDiscovered_Success GattServicesDiscovered_Success
+#else
+#define MAYBE_GattServicesDiscovered_Success \
+  DISABLED_GattServicesDiscovered_Success
+#endif
+TEST_F(BluetoothTest, MAYBE_GattServicesDiscovered_Success) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1386,13 +1477,18 @@
   EXPECT_EQ(1, observer.gatt_services_discovered_count());
   EXPECT_EQ(2u, device->GetGattServices().size());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_WIN)
+#define MAYBE_GattServicesDiscovered_AfterDeleted \
+  GattServicesDiscovered_AfterDeleted
+#else
+#define MAYBE_GattServicesDiscovered_AfterDeleted \
+  DISABLED_GattServicesDiscovered_AfterDeleted
+#endif
 // macOS: Not applicable: This can never happen because when
 // the device gets destroyed the CBPeripheralDelegate is also destroyed
 // and no more events are dispatched.
-TEST_F(BluetoothTest, GattServicesDiscovered_AfterDeleted) {
+TEST_F(BluetoothTest, MAYBE_GattServicesDiscovered_AfterDeleted) {
   // Tests that we don't crash if services are discovered after
   // the device object is deleted.
   if (!PlatformSupportsLowEnergy()) {
@@ -1417,13 +1513,18 @@
       std::vector<std::string>({kTestUUIDGenericAccess, kTestUUIDHeartRate}));
   base::RunLoop().RunUntilIdle();
 }
-#endif  // defined(OS_ANDROID) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_WIN)
+#define MAYBE_GattServicesDiscoveredError_AfterDeleted \
+  GattServicesDiscoveredError_AfterDeleted
+#else
+#define MAYBE_GattServicesDiscoveredError_AfterDeleted \
+  DISABLED_GattServicesDiscoveredError_AfterDeleted
+#endif
 // macOS: Not applicable: This can never happen because when
 // the device gets destroyed the CBPeripheralDelegate is also destroyed
 // and no more events are dispatched.
-TEST_F(BluetoothTest, GattServicesDiscoveredError_AfterDeleted) {
+TEST_F(BluetoothTest, MAYBE_GattServicesDiscoveredError_AfterDeleted) {
   // Tests that we don't crash if there was an error discoverying services
   // after the device object is deleted.
   if (!PlatformSupportsLowEnergy()) {
@@ -1446,11 +1547,16 @@
   SimulateGattServicesDiscoveryError(nullptr /* use remembered device */);
   base::RunLoop().RunUntilIdle();
 }
-#endif  // defined(OS_ANDROID) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_GattServicesDiscovered_AfterDisconnection \
+  GattServicesDiscovered_AfterDisconnection
+#else
+#define MAYBE_GattServicesDiscovered_AfterDisconnection \
+  DISABLED_GattServicesDiscovered_AfterDisconnection
+#endif
 // Windows does not support disconnection.
-TEST_F(BluetoothTest, GattServicesDiscovered_AfterDisconnection) {
+TEST_F(BluetoothTest, MAYBE_GattServicesDiscovered_AfterDisconnection) {
   // Tests that we don't crash if there was an error discovering services after
   // the device disconnects.
   if (!PlatformSupportsLowEnergy()) {
@@ -1478,11 +1584,16 @@
   EXPECT_FALSE(device->IsGattServicesDiscoveryComplete());
   EXPECT_EQ(0u, device->GetGattServices().size());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_GattServicesDiscoveredError_AfterDisconnection \
+  GattServicesDiscoveredError_AfterDisconnection
+#else
+#define MAYBE_GattServicesDiscoveredError_AfterDisconnection \
+  DISABLED_GattServicesDiscoveredError_AfterDisconnection
+#endif
 // Windows does not support disconnecting.
-TEST_F(BluetoothTest, GattServicesDiscoveredError_AfterDisconnection) {
+TEST_F(BluetoothTest, MAYBE_GattServicesDiscoveredError_AfterDisconnection) {
   // Tests that we don't crash if services are discovered after
   // the device disconnects.
   if (!PlatformSupportsLowEnergy()) {
@@ -1507,10 +1618,15 @@
   EXPECT_FALSE(device->IsGattServicesDiscoveryComplete());
   EXPECT_EQ(0u, device->GetGattServices().size());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MACOSX)
-TEST_F(BluetoothTest, GetGattServices_and_GetGattService) {
+#define MAYBE_GetGattServices_and_GetGattService \
+  GetGattServices_and_GetGattService
+#else
+#define MAYBE_GetGattServices_and_GetGattService \
+  DISABLED_GetGattServices_and_GetGattService
+#endif
+TEST_F(BluetoothTest, MAYBE_GetGattServices_and_GetGattService) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1541,10 +1657,14 @@
   EXPECT_TRUE(device->GetGattService(service_id2));
   EXPECT_TRUE(device->GetGattService(service_id3));
 }
-#endif  // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
-TEST_F(BluetoothTest, GetGattServices_DiscoveryError) {
+#define MAYBE_GetGattServices_DiscoveryError GetGattServices_DiscoveryError
+#else
+#define MAYBE_GetGattServices_DiscoveryError \
+  DISABLED_GetGattServices_DiscoveryError
+#endif
+TEST_F(BluetoothTest, MAYBE_GetGattServices_DiscoveryError) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1563,7 +1683,6 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(0u, device->GetGattServices().size());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
 TEST_F(BluetoothTest, GetDeviceTransportType) {
@@ -1584,7 +1703,11 @@
 #endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
-TEST_F(BluetoothGetServiceTest, GetPrimaryServices) {
+#define MAYBE_GetPrimaryServices GetPrimaryServices
+#else
+#define MAYBE_GetPrimaryServices DISABLED_GetPrimaryServices
+#endif
+TEST_F(BluetoothGetServiceTest, MAYBE_GetPrimaryServices) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1594,7 +1717,12 @@
   EXPECT_EQ(3u, device_->GetPrimaryServices().size());
 }
 
-TEST_F(BluetoothGetServiceTest, GetPrimaryServicesByUUID) {
+#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_GetPrimaryServicesByUUID GetPrimaryServicesByUUID
+#else
+#define MAYBE_GetPrimaryServicesByUUID DISABLED_GetPrimaryServicesByUUID
+#endif
+TEST_F(BluetoothGetServiceTest, MAYBE_GetPrimaryServicesByUUID) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1623,6 +1751,5 @@
     EXPECT_NE(services[0]->GetIdentifier(), services[1]->GetIdentifier());
   }
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_local_gatt_characteristic_unittest.cc b/device/bluetooth/bluetooth_local_gatt_characteristic_unittest.cc
index 6cebf602..6259fb3 100644
--- a/device/bluetooth/bluetooth_local_gatt_characteristic_unittest.cc
+++ b/device/bluetooth/bluetooth_local_gatt_characteristic_unittest.cc
@@ -55,7 +55,12 @@
 };
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
-TEST_F(BluetoothLocalGattCharacteristicTest, ReadLocalCharacteristicValue) {
+#define MAYBE_ReadLocalCharacteristicValue ReadLocalCharacteristicValue
+#else
+#define MAYBE_ReadLocalCharacteristicValue DISABLED_ReadLocalCharacteristicValue
+#endif
+TEST_F(BluetoothLocalGattCharacteristicTest,
+       MAYBE_ReadLocalCharacteristicValue) {
   delegate_->value_to_write_ = 0x1337;
   SimulateLocalGattCharacteristicValueReadRequest(
       device_, read_characteristic_.get(), GetReadValueCallback(Call::EXPECTED),
@@ -64,10 +69,15 @@
   EXPECT_EQ(delegate_->value_to_write_, GetInteger(last_read_value_));
   EXPECT_EQ(device_->GetIdentifier(), delegate_->last_seen_device_);
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
-TEST_F(BluetoothLocalGattCharacteristicTest, WriteLocalCharacteristicValue) {
+#define MAYBE_WriteLocalCharacteristicValue WriteLocalCharacteristicValue
+#else
+#define MAYBE_WriteLocalCharacteristicValue \
+  DISABLED_WriteLocalCharacteristicValue
+#endif
+TEST_F(BluetoothLocalGattCharacteristicTest,
+       MAYBE_WriteLocalCharacteristicValue) {
   const uint64_t kValueToWrite = 0x7331ul;
   SimulateLocalGattCharacteristicValueWriteRequest(
       device_, write_characteristic_.get(), GetValue(kValueToWrite),
@@ -76,10 +86,15 @@
   EXPECT_EQ(kValueToWrite, delegate_->last_written_value_);
   EXPECT_EQ(device_->GetIdentifier(), delegate_->last_seen_device_);
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
-TEST_F(BluetoothLocalGattCharacteristicTest, ReadLocalCharacteristicValueFail) {
+#define MAYBE_ReadLocalCharacteristicValueFail ReadLocalCharacteristicValueFail
+#else
+#define MAYBE_ReadLocalCharacteristicValueFail \
+  DISABLED_ReadLocalCharacteristicValueFail
+#endif
+TEST_F(BluetoothLocalGattCharacteristicTest,
+       MAYBE_ReadLocalCharacteristicValueFail) {
   delegate_->value_to_write_ = 0x1337;
   delegate_->should_fail_ = true;
   SimulateLocalGattCharacteristicValueReadRequest(
@@ -89,11 +104,16 @@
   EXPECT_NE(delegate_->value_to_write_, GetInteger(last_read_value_));
   EXPECT_NE(device_->GetIdentifier(), delegate_->last_seen_device_);
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
+#define MAYBE_ReadLocalCharacteristicValueWrongPermission \
+  ReadLocalCharacteristicValueWrongPermission
+#else
+#define MAYBE_ReadLocalCharacteristicValueWrongPermission \
+  DISABLED_ReadLocalCharacteristicValueWrongPermission
+#endif
 TEST_F(BluetoothLocalGattCharacteristicTest,
-       ReadLocalCharacteristicValueWrongPermission) {
+       MAYBE_ReadLocalCharacteristicValueWrongPermission) {
   delegate_->value_to_write_ = 0x1337;
   SimulateLocalGattCharacteristicValueReadRequest(
       device_, write_characteristic_.get(),
@@ -102,11 +122,16 @@
   EXPECT_NE(delegate_->value_to_write_, GetInteger(last_read_value_));
   EXPECT_NE(device_->GetIdentifier(), delegate_->last_seen_device_);
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
+#define MAYBE_WriteLocalCharacteristicValueFail \
+  WriteLocalCharacteristicValueFail
+#else
+#define MAYBE_WriteLocalCharacteristicValueFail \
+  DISABLED_WriteLocalCharacteristicValueFail
+#endif
 TEST_F(BluetoothLocalGattCharacteristicTest,
-       WriteLocalCharacteristicValueFail) {
+       MAYBE_WriteLocalCharacteristicValueFail) {
   const uint64_t kValueToWrite = 0x7331ul;
   delegate_->should_fail_ = true;
   SimulateLocalGattCharacteristicValueWriteRequest(
@@ -116,11 +141,16 @@
   EXPECT_NE(kValueToWrite, delegate_->last_written_value_);
   EXPECT_NE(device_->GetIdentifier(), delegate_->last_seen_device_);
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
+#define MAYBE_WriteLocalCharacteristicValueWrongPermission \
+  WriteLocalCharacteristicValueWrongPermission
+#else
+#define MAYBE_WriteLocalCharacteristicValueWrongPermission \
+  DISABLED_WriteLocalCharacteristicValueWrongPermission
+#endif
 TEST_F(BluetoothLocalGattCharacteristicTest,
-       WriteLocalCharacteristicValueWrongPermission) {
+       MAYBE_WriteLocalCharacteristicValueWrongPermission) {
   const uint64_t kValueToWrite = 0x7331ul;
   SimulateLocalGattCharacteristicValueWriteRequest(
       device_, read_characteristic_.get(), GetValue(kValueToWrite),
@@ -129,10 +159,13 @@
   EXPECT_NE(kValueToWrite, delegate_->last_written_value_);
   EXPECT_NE(device_->GetIdentifier(), delegate_->last_seen_device_);
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
-TEST_F(BluetoothLocalGattCharacteristicTest, StartAndStopNotifications) {
+#define MAYBE_StartAndStopNotifications StartAndStopNotifications
+#else
+#define MAYBE_StartAndStopNotifications DISABLED_StartAndStopNotifications
+#endif
+TEST_F(BluetoothLocalGattCharacteristicTest, MAYBE_StartAndStopNotifications) {
   EXPECT_FALSE(SimulateLocalGattCharacteristicNotificationsRequest(
       read_characteristic_.get(), true));
   EXPECT_FALSE(delegate_->NotificationStatusForCharacteristic(
@@ -153,10 +186,13 @@
   EXPECT_FALSE(delegate_->NotificationStatusForCharacteristic(
       notify_characteristic_.get()));
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
-TEST_F(BluetoothLocalGattCharacteristicTest, SendNotifications) {
+#define MAYBE_SendNotifications SendNotifications
+#else
+#define MAYBE_SendNotifications DISABLED_SendNotifications
+#endif
+TEST_F(BluetoothLocalGattCharacteristicTest, MAYBE_SendNotifications) {
   const uint64_t kNotifyValue = 0x7331ul;
   EXPECT_EQ(BluetoothLocalGattCharacteristic::NOTIFICATION_SUCCESS,
             notify_characteristic_->NotifyValueChanged(
@@ -171,10 +207,15 @@
   EXPECT_EQ(kIndicateValue, GetInteger(LastNotifactionValueForCharacteristic(
                                 indicate_characteristic_.get())));
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
-TEST_F(BluetoothLocalGattCharacteristicTest, SendNotificationsWrongProperties) {
+#define MAYBE_SendNotificationsWrongProperties SendNotificationsWrongProperties
+#else
+#define MAYBE_SendNotificationsWrongProperties \
+  DISABLED_SendNotificationsWrongProperties
+#endif
+TEST_F(BluetoothLocalGattCharacteristicTest,
+       MAYBE_SendNotificationsWrongProperties) {
   const uint64_t kNewValue = 0x3334ul;
   EXPECT_EQ(BluetoothLocalGattCharacteristic::NOTIFY_PROPERTY_NOT_SET,
             read_characteristic_->NotifyValueChanged(
@@ -202,11 +243,16 @@
   EXPECT_NE(kIndicateValue, GetInteger(LastNotifactionValueForCharacteristic(
                                 indicate_characteristic_.get())));
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
+#define MAYBE_SendNotificationsServiceNotRegistered \
+  SendNotificationsServiceNotRegistered
+#else
+#define MAYBE_SendNotificationsServiceNotRegistered \
+  DISABLED_SendNotificationsServiceNotRegistered
+#endif
 TEST_F(BluetoothLocalGattCharacteristicTest,
-       SendNotificationsServiceNotRegistered) {
+       MAYBE_SendNotificationsServiceNotRegistered) {
   service_->Unregister(GetCallback(Call::EXPECTED),
                        GetGattErrorCallback(Call::NOT_EXPECTED));
   const uint64_t kNotifyValue = 0x7331ul;
@@ -216,6 +262,5 @@
   EXPECT_NE(kNotifyValue, GetInteger(LastNotifactionValueForCharacteristic(
                               notify_characteristic_.get())));
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_local_gatt_descriptor_unittest.cc b/device/bluetooth/bluetooth_local_gatt_descriptor_unittest.cc
index 1f379504..2d1c02f 100644
--- a/device/bluetooth/bluetooth_local_gatt_descriptor_unittest.cc
+++ b/device/bluetooth/bluetooth_local_gatt_descriptor_unittest.cc
@@ -47,7 +47,11 @@
 };
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
-TEST_F(BluetoothLocalGattDescriptorTest, ReadLocalDescriptorValue) {
+#define MAYBE_ReadLocalDescriptorValue ReadLocalDescriptorValue
+#else
+#define MAYBE_ReadLocalDescriptorValue DISABLED_ReadLocalDescriptorValue
+#endif
+TEST_F(BluetoothLocalGattDescriptorTest, MAYBE_ReadLocalDescriptorValue) {
   delegate_->value_to_write_ = 0x1337;
   SimulateLocalGattDescriptorValueReadRequest(
       device_, read_descriptor_.get(), GetReadValueCallback(Call::EXPECTED),
@@ -56,10 +60,13 @@
   EXPECT_EQ(delegate_->value_to_write_, GetInteger(last_read_value_));
   EXPECT_EQ(device_->GetIdentifier(), delegate_->last_seen_device_);
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
-TEST_F(BluetoothLocalGattDescriptorTest, WriteLocalDescriptorValue) {
+#define MAYBE_WriteLocalDescriptorValue WriteLocalDescriptorValue
+#else
+#define MAYBE_WriteLocalDescriptorValue DISABLED_WriteLocalDescriptorValue
+#endif
+TEST_F(BluetoothLocalGattDescriptorTest, MAYBE_WriteLocalDescriptorValue) {
   const uint64_t kValueToWrite = 0x7331ul;
   SimulateLocalGattDescriptorValueWriteRequest(
       device_, write_descriptor_.get(), GetValue(kValueToWrite),
@@ -68,10 +75,13 @@
   EXPECT_EQ(kValueToWrite, delegate_->last_written_value_);
   EXPECT_EQ(device_->GetIdentifier(), delegate_->last_seen_device_);
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
-TEST_F(BluetoothLocalGattDescriptorTest, ReadLocalDescriptorValueFail) {
+#define MAYBE_ReadLocalDescriptorValueFail ReadLocalDescriptorValueFail
+#else
+#define MAYBE_ReadLocalDescriptorValueFail DISABLED_ReadLocalDescriptorValueFail
+#endif
+TEST_F(BluetoothLocalGattDescriptorTest, MAYBE_ReadLocalDescriptorValueFail) {
   delegate_->value_to_write_ = 0x1337;
   delegate_->should_fail_ = true;
   SimulateLocalGattDescriptorValueReadRequest(
@@ -81,10 +91,14 @@
   EXPECT_NE(delegate_->value_to_write_, GetInteger(last_read_value_));
   EXPECT_NE(device_->GetIdentifier(), delegate_->last_seen_device_);
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
-TEST_F(BluetoothLocalGattDescriptorTest, WriteLocalDescriptorValueFail) {
+#define MAYBE_WriteLocalDescriptorValueFail WriteLocalDescriptorValueFail
+#else
+#define MAYBE_WriteLocalDescriptorValueFail \
+  DISABLED_WriteLocalDescriptorValueFail
+#endif
+TEST_F(BluetoothLocalGattDescriptorTest, MAYBE_WriteLocalDescriptorValueFail) {
   const uint64_t kValueToWrite = 0x7331ul;
   delegate_->should_fail_ = true;
   SimulateLocalGattDescriptorValueWriteRequest(
@@ -94,11 +108,16 @@
   EXPECT_NE(kValueToWrite, delegate_->last_written_value_);
   EXPECT_NE(device_->GetIdentifier(), delegate_->last_seen_device_);
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
+#define MAYBE_ReadLocalDescriptorValueWrongPermissions \
+  ReadLocalDescriptorValueWrongPermissions
+#else
+#define MAYBE_ReadLocalDescriptorValueWrongPermissions \
+  DISABLED_ReadLocalDescriptorValueWrongPermissions
+#endif
 TEST_F(BluetoothLocalGattDescriptorTest,
-       ReadLocalDescriptorValueWrongPermissions) {
+       MAYBE_ReadLocalDescriptorValueWrongPermissions) {
   delegate_->value_to_write_ = 0x1337;
   SimulateLocalGattDescriptorValueReadRequest(
       device_, write_descriptor_.get(),
@@ -107,11 +126,16 @@
   EXPECT_NE(delegate_->value_to_write_, GetInteger(last_read_value_));
   EXPECT_NE(device_->GetIdentifier(), delegate_->last_seen_device_);
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
+#define MAYBE_WriteLocalDescriptorValueWrongPermissions \
+  WriteLocalDescriptorValueWrongPermissions
+#else
+#define MAYBE_WriteLocalDescriptorValueWrongPermissions \
+  DISABLED_WriteLocalDescriptorValueWrongPermissions
+#endif
 TEST_F(BluetoothLocalGattDescriptorTest,
-       WriteLocalDescriptorValueWrongPermissions) {
+       MAYBE_WriteLocalDescriptorValueWrongPermissions) {
   const uint64_t kValueToWrite = 0x7331ul;
   SimulateLocalGattDescriptorValueWriteRequest(
       device_, read_descriptor_.get(), GetValue(kValueToWrite),
@@ -120,6 +144,5 @@
   EXPECT_NE(kValueToWrite, delegate_->last_written_value_);
   EXPECT_NE(device_->GetIdentifier(), delegate_->last_seen_device_);
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_local_gatt_service_unittest.cc b/device/bluetooth/bluetooth_local_gatt_service_unittest.cc
index eca2a3a..3efc46796 100644
--- a/device/bluetooth/bluetooth_local_gatt_service_unittest.cc
+++ b/device/bluetooth/bluetooth_local_gatt_service_unittest.cc
@@ -41,7 +41,11 @@
 };
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
-TEST_F(BluetoothLocalGattServiceTest, RegisterMultipleServices) {
+#define MAYBE_RegisterMultipleServices RegisterMultipleServices
+#else
+#define MAYBE_RegisterMultipleServices DISABLED_RegisterMultipleServices
+#endif
+TEST_F(BluetoothLocalGattServiceTest, MAYBE_RegisterMultipleServices) {
   base::WeakPtr<BluetoothLocalGattService> service2 =
       BluetoothLocalGattService::Create(
           adapter_.get(), BluetoothUUID(kTestUUIDGenericAttribute), true,
@@ -85,10 +89,13 @@
                        GetGattErrorCallback(Call::NOT_EXPECTED));
   EXPECT_TRUE(ServiceSetsEqual(RegisteredGattServices(), {}));
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
-TEST_F(BluetoothLocalGattServiceTest, DeleteServices) {
+#define MAYBE_DeleteServices DeleteServices
+#else
+#define MAYBE_DeleteServices DISABLED_DeleteServices
+#endif
+TEST_F(BluetoothLocalGattServiceTest, MAYBE_DeleteServices) {
   base::WeakPtr<BluetoothLocalGattService> service2 =
       BluetoothLocalGattService::Create(
           adapter_.get(), BluetoothUUID(kTestUUIDGenericAttribute), true,
@@ -128,6 +135,5 @@
   service4->Delete();
   EXPECT_TRUE(ServiceSetsEqual(RegisteredGattServices(), {}));
 }
-#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
 
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc b/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
index 2d70795bd..4c5b1df 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
@@ -21,11 +21,12 @@
 #include "device/bluetooth/test/bluetooth_test_mac.h"
 #elif defined(OS_WIN)
 #include "device/bluetooth/test/bluetooth_test_win.h"
+#elif defined(OS_CHROMEOS) || defined(OS_LINUX)
+#include "device/bluetooth/test/bluetooth_test_bluez.h"
 #endif
 
 namespace device {
 
-#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 class BluetoothRemoteGattCharacteristicTest : public BluetoothTest {
  public:
   // Creates adapter_, device_, service_, characteristic1_, & characteristic2_.
@@ -133,10 +134,13 @@
   BluetoothRemoteGattCharacteristic* characteristic1_ = nullptr;
   BluetoothRemoteGattCharacteristic* characteristic2_ = nullptr;
 };
-#endif
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
-TEST_F(BluetoothRemoteGattCharacteristicTest, GetIdentifier) {
+#define MAYBE_GetIdentifier GetIdentifier
+#else
+#define MAYBE_GetIdentifier DISABLED_GetIdentifier
+#endif
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetIdentifier) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -203,10 +207,13 @@
 
   EXPECT_NE(char5->GetIdentifier(), char6->GetIdentifier());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
-TEST_F(BluetoothRemoteGattCharacteristicTest, GetUUID) {
+#define MAYBE_GetUUID GetUUID
+#else
+#define MAYBE_GetUUID DISABLED_GetUUID
+#endif
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetUUID) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -244,10 +251,13 @@
   EXPECT_EQ(uuid2, char2->GetUUID());
   EXPECT_EQ(uuid2, char3->GetUUID());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
-TEST_F(BluetoothRemoteGattCharacteristicTest, GetProperties) {
+#define MAYBE_GetProperties GetProperties
+#else
+#define MAYBE_GetProperties DISABLED_GetProperties
+#endif
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetProperties) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -276,11 +286,14 @@
   EXPECT_EQ(0, properties1);
   EXPECT_EQ(7, properties2);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_GetService GetService
+#else
+#define MAYBE_GetService DISABLED_GetService
+#endif
 // Tests GetService.
-TEST_F(BluetoothRemoteGattCharacteristicTest, GetService) {
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetService) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -290,11 +303,16 @@
   EXPECT_EQ(service_, characteristic1_->GetService());
   EXPECT_EQ(service_, characteristic2_->GetService());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_ReadRemoteCharacteristic_Empty ReadRemoteCharacteristic_Empty
+#else
+#define MAYBE_ReadRemoteCharacteristic_Empty \
+  DISABLED_ReadRemoteCharacteristic_Empty
+#endif
 // Tests ReadRemoteCharacteristic and GetValue with empty value buffer.
-TEST_F(BluetoothRemoteGattCharacteristicTest, ReadRemoteCharacteristic_Empty) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_ReadRemoteCharacteristic_Empty) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -317,11 +335,16 @@
   EXPECT_EQ(empty_vector, last_read_value_);
   EXPECT_EQ(empty_vector, characteristic1_->GetValue());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_WriteRemoteCharacteristic_Empty WriteRemoteCharacteristic_Empty
+#else
+#define MAYBE_WriteRemoteCharacteristic_Empty \
+  DISABLED_WriteRemoteCharacteristic_Empty
+#endif
 // Tests WriteRemoteCharacteristic with empty value buffer.
-TEST_F(BluetoothRemoteGattCharacteristicTest, WriteRemoteCharacteristic_Empty) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_WriteRemoteCharacteristic_Empty) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -343,14 +366,19 @@
 
   EXPECT_EQ(empty_vector, last_write_value_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_WIN)
+#define MAYBE_ReadRemoteCharacteristic_AfterDeleted \
+  ReadRemoteCharacteristic_AfterDeleted
+#else
+#define MAYBE_ReadRemoteCharacteristic_AfterDeleted \
+  DISABLED_ReadRemoteCharacteristic_AfterDeleted
+#endif
 // Tests ReadRemoteCharacteristic completing after Chrome objects are deleted.
 // macOS: Not applicable: This can never happen if CBPeripheral delegate is set
 // to nil.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       ReadRemoteCharacteristic_AfterDeleted) {
+       MAYBE_ReadRemoteCharacteristic_AfterDeleted) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -371,13 +399,18 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE("Did not crash!");
 }
-#endif  // defined(OS_ANDROID) || defined(OS_WIN)
 
 // TODO(crbug.com/663131): Enable test on windows when disconnection is
 // implemented.
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_ReadRemoteCharacteristic_Disconnected \
+  ReadRemoteCharacteristic_Disconnected
+#else
+#define MAYBE_ReadRemoteCharacteristic_Disconnected \
+  DISABLED_ReadRemoteCharacteristic_Disconnected
+#endif
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       ReadRemoteCharacteristic_Disconnected) {
+       MAYBE_ReadRemoteCharacteristic_Disconnected) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -413,14 +446,19 @@
   base::RunLoop().RunUntilIdle();
 #endif
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_WIN)
+#define MAYBE_WriteRemoteCharacteristic_AfterDeleted \
+  WriteRemoteCharacteristic_AfterDeleted
+#else
+#define MAYBE_WriteRemoteCharacteristic_AfterDeleted \
+  DISABLED_WriteRemoteCharacteristic_AfterDeleted
+#endif
 // Tests WriteRemoteCharacteristic completing after Chrome objects are deleted.
 // macOS: Not applicable: This can never happen if CBPeripheral delegate is set
 // to nil.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       WriteRemoteCharacteristic_AfterDeleted) {
+       MAYBE_WriteRemoteCharacteristic_AfterDeleted) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -440,13 +478,18 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE("Did not crash!");
 }
-#endif  // defined(OS_ANDROID) || defined(OS_WIN)
 
 // TODO(crbug.com/663131): Enable test on windows when disconnection is
 // implemented.
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_WriteRemoteCharacteristic_Disconnected \
+  WriteRemoteCharacteristic_Disconnected
+#else
+#define MAYBE_WriteRemoteCharacteristic_Disconnected \
+  DISABLED_WriteRemoteCharacteristic_Disconnected
+#endif
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       WriteRemoteCharacteristic_Disconnected) {
+       MAYBE_WriteRemoteCharacteristic_Disconnected) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -481,11 +524,14 @@
   base::RunLoop().RunUntilIdle();
 #endif  // !defined(OS_MACOSX)
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_ReadRemoteCharacteristic ReadRemoteCharacteristic
+#else
+#define MAYBE_ReadRemoteCharacteristic DISABLED_ReadRemoteCharacteristic
+#endif
 // Tests ReadRemoteCharacteristic and GetValue with non-empty value buffer.
-TEST_F(BluetoothRemoteGattCharacteristicTest, ReadRemoteCharacteristic) {
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadRemoteCharacteristic) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -510,9 +556,7 @@
   EXPECT_EQ(test_vector, last_read_value_);
   EXPECT_EQ(test_vector, characteristic1_->GetValue());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
-#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 // Callback that make sure GattCharacteristicValueChanged has been called
 // before the callback runs.
 static void test_callback(
@@ -523,10 +567,17 @@
   callback.Run(value);
 }
 
+#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_ReadRemoteCharacteristic_GattCharacteristicValueChangedNotCalled \
+  ReadRemoteCharacteristic_GattCharacteristicValueChangedNotCalled
+#else
+#define MAYBE_ReadRemoteCharacteristic_GattCharacteristicValueChangedNotCalled \
+  DISABLED_ReadRemoteCharacteristic_GattCharacteristicValueChangedNotCalled
+#endif
 // Tests that ReadRemoteCharacteristic doesn't result in a
 // GattCharacteristicValueChanged call.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       ReadRemoteCharacteristic_GattCharacteristicValueChangedNotCalled) {
+       MAYBE_ReadRemoteCharacteristic_GattCharacteristicValueChangedNotCalled) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -557,11 +608,14 @@
 #endif  // defined(OS_WIN)
   EXPECT_TRUE(observer.last_changed_characteristic_value().empty());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_WriteRemoteCharacteristic WriteRemoteCharacteristic
+#else
+#define MAYBE_WriteRemoteCharacteristic DISABLED_WriteRemoteCharacteristic
+#endif
 // Tests WriteRemoteCharacteristic with non-empty value buffer.
-TEST_F(BluetoothRemoteGattCharacteristicTest, WriteRemoteCharacteristic) {
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteRemoteCharacteristic) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -587,11 +641,16 @@
 #endif
   EXPECT_EQ(test_vector, last_write_value_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_ReadRemoteCharacteristic_Twice ReadRemoteCharacteristic_Twice
+#else
+#define MAYBE_ReadRemoteCharacteristic_Twice \
+  DISABLED_ReadRemoteCharacteristic_Twice
+#endif
 // Tests ReadRemoteCharacteristic and GetValue multiple times.
-TEST_F(BluetoothRemoteGattCharacteristicTest, ReadRemoteCharacteristic_Twice) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_ReadRemoteCharacteristic_Twice) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -627,11 +686,16 @@
   EXPECT_EQ(empty_vector, last_read_value_);
   EXPECT_EQ(empty_vector, characteristic1_->GetValue());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_WriteRemoteCharacteristic_Twice WriteRemoteCharacteristic_Twice
+#else
+#define MAYBE_WriteRemoteCharacteristic_Twice \
+  DISABLEDWriteRemoteCharacteristic_Twice
+#endif
 // Tests WriteRemoteCharacteristic multiple times.
-TEST_F(BluetoothRemoteGattCharacteristicTest, WriteRemoteCharacteristic_Twice) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_WriteRemoteCharacteristic_Twice) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -666,12 +730,17 @@
   EXPECT_EQ(0, error_callback_count_);
   EXPECT_EQ(empty_vector, last_write_value_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_ReadRemoteCharacteristic_MultipleCharacteristics \
+  ReadRemoteCharacteristic_MultipleCharacteristics
+#else
+#define MAYBE_ReadRemoteCharacteristic_MultipleCharacteristics \
+  DISABLED_ReadRemoteCharacteristic_MultipleCharacteristics
+#endif
 // Tests ReadRemoteCharacteristic on two characteristics.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       ReadRemoteCharacteristic_MultipleCharacteristics) {
+       MAYBE_ReadRemoteCharacteristic_MultipleCharacteristics) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -706,12 +775,17 @@
   EXPECT_EQ(test_vector1, characteristic1_->GetValue());
   EXPECT_EQ(test_vector2, characteristic2_->GetValue());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_WriteRemoteCharacteristic_MultipleCharacteristics \
+  WriteRemoteCharacteristic_MultipleCharacteristics
+#else
+#define MAYBE_WriteRemoteCharacteristic_MultipleCharacteristics \
+  DISABLED_WriteRemoteCharacteristic_MultipleCharacteristics
+#endif
 // Tests WriteRemoteCharacteristic on two characteristics.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       WriteRemoteCharacteristic_MultipleCharacteristics) {
+       MAYBE_WriteRemoteCharacteristic_MultipleCharacteristics) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -758,13 +832,18 @@
 
   // TODO(591740): Remove if define for OS_ANDROID in this test.
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_RemoteCharacteristic_Nested_Read_Read \
+  RemoteCharacteristic_Nested_Read_Read
+#else
+#define MAYBE_RemoteCharacteristic_Nested_Read_Read \
+  DISABLED_RemoteCharacteristic_Nested_Read_Read
+#endif
 // Tests a nested ReadRemoteCharacteristic from within another
 // ReadRemoteCharacteristic.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       RemoteCharacteristic_Nested_Read_Read) {
+       MAYBE_RemoteCharacteristic_Nested_Read_Read) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -800,13 +879,18 @@
   EXPECT_EQ(test_vector_2, last_read_value_);
   EXPECT_EQ(test_vector_2, characteristic1_->GetValue());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_RemoteCharacteristic_Nested_Write_Write \
+  RemoteCharacteristic_Nested_Write_Write
+#else
+#define MAYBE_RemoteCharacteristic_Nested_Write_Write \
+  DISABLED_RemoteCharacteristic_Nested_Write_Write
+#endif
 // Tests a nested WriteRemoteCharacteristic from within another
 // WriteRemoteCharacteristic.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       RemoteCharacteristic_Nested_Write_Write) {
+       MAYBE_RemoteCharacteristic_Nested_Write_Write) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -841,13 +925,18 @@
   EXPECT_EQ(0, error_callback_count_);
   EXPECT_EQ(test_vector_2, last_write_value_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_RemoteCharacteristic_Nested_Read_Write \
+  RemoteCharacteristic_Nested_Read_Write
+#else
+#define MAYBE_RemoteCharacteristic_Nested_Read_Write \
+  DISABLED_RemoteCharacteristic_Nested_Read_Write
+#endif
 // Tests a nested WriteRemoteCharacteristic from within a
 // ReadRemoteCharacteristic.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       RemoteCharacteristic_Nested_Read_Write) {
+       MAYBE_RemoteCharacteristic_Nested_Read_Write) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -885,13 +974,18 @@
   EXPECT_EQ(0, error_callback_count_);
   EXPECT_EQ(test_vector_2, last_write_value_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_RemoteCharacteristic_Nested_Write_Read \
+  RemoteCharacteristic_Nested_Write_Read
+#else
+#define MAYBE_RemoteCharacteristic_Nested_Write_Read \
+  DISABLED_RemoteCharacteristic_Nested_Write_Read
+#endif
 // Tests a nested ReadRemoteCharacteristic from within a
 // WriteRemoteCharacteristic.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       RemoteCharacteristic_Nested_Write_Read) {
+       MAYBE_RemoteCharacteristic_Nested_Write_Read) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -929,11 +1023,14 @@
   EXPECT_EQ(test_vector_2, last_read_value_);
   EXPECT_EQ(test_vector_2, characteristic1_->GetValue());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_ReadError ReadError
+#else
+#define MAYBE_ReadError DISABLED_ReadError
+#endif
 // Tests ReadRemoteCharacteristic asynchronous error.
-TEST_F(BluetoothRemoteGattCharacteristicTest, ReadError) {
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadError) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -955,11 +1052,14 @@
             last_gatt_error_code_);
   EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_WriteError WriteError
+#else
+#define MAYBE_WriteError DISABLED_WriteError
+#endif
 // Tests WriteRemoteCharacteristic asynchronous error.
-TEST_F(BluetoothRemoteGattCharacteristicTest, WriteError) {
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteError) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -980,13 +1080,16 @@
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_INVALID_LENGTH,
             last_gatt_error_code_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID)
+#define MAYBE_ReadSynchronousError ReadSynchronousError
+#else
+#define MAYBE_ReadSynchronousError DISABLED_ReadSynchronousError
+#endif
 // Tests ReadRemoteCharacteristic synchronous error.
 // Test not relevant for macOS since characteristic read cannot generate
 // synchronous error.
-TEST_F(BluetoothRemoteGattCharacteristicTest, ReadSynchronousError) {
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadSynchronousError) {
   ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate());
 
   SimulateGattCharacteristicReadWillFailSynchronouslyOnce(characteristic1_);
@@ -1012,12 +1115,15 @@
   EXPECT_EQ(1, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID)
+#define MAYBE_WriteSynchronousError WriteSynchronousError
+#else
+#define MAYBE_WriteSynchronousError DISABLED_WriteSynchronousError
+#endif
 // Tests WriteRemoteCharacteristic synchronous error.
 // This test doesn't apply to macOS synchronous API does exist.
-TEST_F(BluetoothRemoteGattCharacteristicTest, WriteSynchronousError) {
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteSynchronousError) {
   ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate());
 
   SimulateGattCharacteristicWriteWillFailSynchronouslyOnce(characteristic1_);
@@ -1043,12 +1149,17 @@
   EXPECT_EQ(1, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_ReadRemoteCharacteristic_ReadPending \
+  ReadRemoteCharacteristic_ReadPending
+#else
+#define MAYBE_ReadRemoteCharacteristic_ReadPending \
+  DISABLED_ReadRemoteCharacteristic_ReadPending
+#endif
 // Tests ReadRemoteCharacteristic error with a pending read operation.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       ReadRemoteCharacteristic_ReadPending) {
+       MAYBE_ReadRemoteCharacteristic_ReadPending) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1078,12 +1189,17 @@
   EXPECT_EQ(1, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_WriteRemoteCharacteristic_WritePending \
+  WriteRemoteCharacteristic_WritePending
+#else
+#define MAYBE_WriteRemoteCharacteristic_WritePending \
+  DISABLED_WriteRemoteCharacteristic_WritePending
+#endif
 // Tests WriteRemoteCharacteristic error with a pending write operation.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       WriteRemoteCharacteristic_WritePending) {
+       MAYBE_WriteRemoteCharacteristic_WritePending) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1113,12 +1229,17 @@
   EXPECT_EQ(1, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_ReadRemoteCharacteristic_WritePending \
+  ReadRemoteCharacteristic_WritePending
+#else
+#define MAYBE_ReadRemoteCharacteristic_WritePending \
+  DISABLED_ReadRemoteCharacteristic_WritePending
+#endif
 // Tests ReadRemoteCharacteristic error with a pending write operation.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       ReadRemoteCharacteristic_WritePending) {
+       MAYBE_ReadRemoteCharacteristic_WritePending) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1149,12 +1270,17 @@
   EXPECT_EQ(1, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_WriteRemoteCharacteristic_ReadPending \
+  WriteRemoteCharacteristic_ReadPending
+#else
+#define MAYBE_WriteRemoteCharacteristic_ReadPending \
+  DISABLED_WriteRemoteCharacteristic_ReadPending
+#endif
 // Tests WriteRemoteCharacteristic error with a pending Read operation.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       WriteRemoteCharacteristic_ReadPending) {
+       MAYBE_WriteRemoteCharacteristic_ReadPending) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1184,15 +1310,20 @@
   EXPECT_EQ(1, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_Notification_During_ReadRemoteCharacteristic \
+  Notification_During_ReadRemoteCharacteristic
+#else
+#define MAYBE_Notification_During_ReadRemoteCharacteristic \
+  DISABLED_Notification_During_ReadRemoteCharacteristic
+#endif
 // TODO(crbug.com/713991): Enable on windows once it better matches
 // how other platforms set global variables.
 // Tests that a notification arriving during a pending read doesn't
 // cause a crash.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       Notification_During_ReadRemoteCharacteristic) {
+       MAYBE_Notification_During_ReadRemoteCharacteristic) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1241,13 +1372,18 @@
   EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count());
 #endif  // defined(OS_MACOSX)
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_Notification_During_WriteRemoteCharacteristic \
+  Notification_During_WriteRemoteCharacteristic
+#else
+#define MAYBE_Notification_During_WriteRemoteCharacteristic \
+  DISABLED_Notification_During_WriteRemoteCharacteristic
+#endif
 // Tests that a notification arriving during a pending write doesn't
 // cause a crash.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       Notification_During_WriteRemoteCharacteristic) {
+       MAYBE_Notification_During_WriteRemoteCharacteristic) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1277,13 +1413,18 @@
 
   EXPECT_EQ(write_value, last_write_value_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_StartNotifySession_NoNotifyOrIndicate \
+  StartNotifySession_NoNotifyOrIndicate
+#else
+#define MAYBE_StartNotifySession_NoNotifyOrIndicate \
+  DISABLED_StartNotifySession_NoNotifyOrIndicate
+#endif
 // StartNotifySession fails if characteristic doesn't have Notify or Indicate
 // property.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StartNotifySession_NoNotifyOrIndicate) {
+       MAYBE_StartNotifySession_NoNotifyOrIndicate) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1301,13 +1442,18 @@
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_NOT_SUPPORTED,
             last_gatt_error_code_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_StartNotifySession_NoConfigDescriptor \
+  StartNotifySession_NoConfigDescriptor
+#else
+#define MAYBE_StartNotifySession_NoConfigDescriptor \
+  DISABLED_StartNotifySession_NoConfigDescriptor
+#endif
 // StartNotifySession fails if the characteristic is missing the Client
 // Characteristic Configuration descriptor.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StartNotifySession_NoConfigDescriptor) {
+       MAYBE_StartNotifySession_NoConfigDescriptor) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1325,13 +1471,18 @@
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_NOT_SUPPORTED,
             last_gatt_error_code_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_StartNotifySession_MultipleConfigDescriptor \
+  StartNotifySession_MultipleConfigDescriptor
+#else
+#define MAYBE_StartNotifySession_MultipleConfigDescriptor \
+  DISABLED_StartNotifySession_MultipleConfigDescriptor
+#endif
 // StartNotifySession fails if the characteristic has multiple Client
 // Characteristic Configuration descriptors.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StartNotifySession_MultipleConfigDescriptor) {
+       MAYBE_StartNotifySession_MultipleConfigDescriptor) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1349,9 +1500,14 @@
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED,
             last_gatt_error_code_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID)
+#define MAYBE_StartNotifySession_FailToSetCharacteristicNotification \
+  StartNotifySession_FailToSetCharacteristicNotification
+#else
+#define MAYBE_StartNotifySession_FailToSetCharacteristicNotification \
+  DISABLED_StartNotifySession_FailToSetCharacteristicNotification
+#endif
 // StartNotifySession fails synchronously when failing to set a characteristic
 // to enable notifications.
 // Android: This is mBluetoothGatt.setCharacteristicNotification failing.
@@ -1359,7 +1515,7 @@
 // Windows: Synchronous Test Not Applicable: OS calls are all made
 // asynchronously from BluetoothTaskManagerWin.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StartNotifySession_FailToSetCharacteristicNotification) {
+       MAYBE_StartNotifySession_FailToSetCharacteristicNotification) {
   ASSERT_NO_FATAL_FAILURE(StartNotifyBoilerplate(
       /* properties: NOTIFY */ 0x10, NotifyValueState::NOTIFY,
       StartNotifySetupError::SET_NOTIFY));
@@ -1372,16 +1528,21 @@
   ExpectedChangeNotifyValueAttempts(0);
   ASSERT_EQ(0u, notify_sessions_.size());
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID)
+#define MAYBE_StartNotifySession_WriteDescriptorSynchronousError \
+  StartNotifySession_WriteDescriptorSynchronousError
+#else
+#define MAYBE_StartNotifySession_WriteDescriptorSynchronousError \
+  DISABLED_StartNotifySession_WriteDescriptorSynchronousError
+#endif
 // Tests StartNotifySession descriptor write synchronous failure.
 // macOS: Not applicable: No need to write to the descriptor manually.
 // -[CBPeripheral setNotifyValue:forCharacteristic:] takes care of it.
 // Windows: Synchronous Test Not Applicable: OS calls are all made
 // asynchronously from BluetoothTaskManagerWin.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StartNotifySession_WriteDescriptorSynchronousError) {
+       MAYBE_StartNotifySession_WriteDescriptorSynchronousError) {
   ASSERT_NO_FATAL_FAILURE(StartNotifyBoilerplate(
       /* properties: NOTIFY */ 0x10, NotifyValueState::NOTIFY,
       StartNotifySetupError::WRITE_DESCRIPTOR));
@@ -1396,11 +1557,14 @@
   ASSERT_EQ(0u, last_write_value_.size());
   ASSERT_EQ(0u, notify_sessions_.size());
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_StartNotifySession StartNotifySession
+#else
+#define MAYBE_StartNotifySession DISABLED_StartNotifySession
+#endif
 // Tests StartNotifySession success on a characteristic that enabled Notify.
-TEST_F(BluetoothRemoteGattCharacteristicTest, StartNotifySession) {
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StartNotifySession) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1408,11 +1572,16 @@
   ASSERT_NO_FATAL_FAILURE(StartNotifyBoilerplate(
       /* properties: NOTIFY */ 0x10, NotifyValueState::NOTIFY));
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_StartNotifySession_OnIndicate StartNotifySession_OnIndicate
+#else
+#define MAYBE_StartNotifySession_OnIndicate \
+  DISABLED_StartNotifySession_OnIndicate
+#endif
 // Tests StartNotifySession success on a characteristic that enabled Indicate.
-TEST_F(BluetoothRemoteGattCharacteristicTest, StartNotifySession_OnIndicate) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_StartNotifySession_OnIndicate) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1420,13 +1589,18 @@
   ASSERT_NO_FATAL_FAILURE(StartNotifyBoilerplate(
       /* properties: INDICATE */ 0x20, NotifyValueState::INDICATE));
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_StartNotifySession_OnNotifyAndIndicate \
+  StartNotifySession_OnNotifyAndIndicate
+#else
+#define MAYBE_StartNotifySession_OnNotifyAndIndicate \
+  DISABLED_StartNotifySession_OnNotifyAndIndicate
+#endif
 // Tests StartNotifySession success on a characteristic that enabled Notify &
 // Indicate.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StartNotifySession_OnNotifyAndIndicate) {
+       MAYBE_StartNotifySession_OnNotifyAndIndicate) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1435,11 +1609,15 @@
       /* properties: NOTIFY and INDICATE bits set */ 0x30,
       NotifyValueState::NOTIFY));
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_StartNotifySession_Multiple StartNotifySession_Multiple
+#else
+#define MAYBE_StartNotifySession_Multiple DISABLED_StartNotifySession_Multiple
+#endif
 // Tests multiple StartNotifySession success.
-TEST_F(BluetoothRemoteGattCharacteristicTest, StartNotifySession_Multiple) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_StartNotifySession_Multiple) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1475,12 +1653,16 @@
   EXPECT_TRUE(notify_sessions_[0]->IsActive());
   EXPECT_TRUE(notify_sessions_[1]->IsActive());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StartNotifySessionError_Multiple StartNotifySessionError_Multiple
+#else
+#define MAYBE_StartNotifySessionError_Multiple \
+  DISABLED_StartNotifySessionError_Multiple
+#endif
 // Tests multiple StartNotifySessions pending and then an error.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StartNotifySessionError_Multiple) {
+       MAYBE_StartNotifySessionError_Multiple) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1509,13 +1691,18 @@
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED,
             last_gatt_error_code_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID)
+#define MAYBE_StartNotifySession_AfterDeleted StartNotifySession_AfterDeleted
+#else
+#define MAYBE_StartNotifySession_AfterDeleted \
+  DISABLED_StartNotifySession_AfterDeleted
+#endif
 // Tests StartNotifySession completing after chrome objects are deleted.
 // macOS: Not applicable: This can never happen if CBPeripheral delegate is set
 // to nil.
-TEST_F(BluetoothRemoteGattCharacteristicTest, StartNotifySession_AfterDeleted) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_StartNotifySession_AfterDeleted) {
   ASSERT_NO_FATAL_FAILURE(
       FakeCharacteristicBoilerplate(/* properties: NOTIFY */ 0x10));
   SimulateGattDescriptor(
@@ -1542,12 +1729,16 @@
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED,
             last_gatt_error_code_);
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StartNotifySession_BeforeDeleted StartNotifySession_BeforeDeleted
+#else
+#define MAYBE_StartNotifySession_BeforeDeleted \
+  DISABLED_StartNotifySession_BeforeDeleted
+#endif
 // Tests StartNotifySession completing before chrome objects are deleted.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StartNotifySession_BeforeDeleted) {
+       MAYBE_StartNotifySession_BeforeDeleted) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1587,13 +1778,18 @@
             notify_sessions_[0]->GetCharacteristicIdentifier());
   EXPECT_FALSE(notify_sessions_[0]->IsActive());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_StartNotifySession_Reentrant_Success_Success \
+  StartNotifySession_Reentrant_Success_Success
+#else
+#define MAYBE_StartNotifySession_Reentrant_Success_Success \
+  DISABLED_StartNotifySession_Reentrant_Success_Success
+#endif
 // Tests StartNotifySession reentrant in start notify session success callback
 // and the reentrant start notify session success.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StartNotifySession_Reentrant_Success_Success) {
+       MAYBE_StartNotifySession_Reentrant_Success_Success) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1631,13 +1827,18 @@
     EXPECT_TRUE(notify_sessions_[i]->IsActive());
   }
 }
-#endif  // defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_WIN)
+#define MAYBE_StartNotifySession_Reentrant_Error_Error \
+  StartNotifySession_Reentrant_Error_Error
+#else
+#define MAYBE_StartNotifySession_Reentrant_Error_Error \
+  DISABLED_StartNotifySession_Reentrant_Error_Error
+#endif
 // Tests StartNotifySession reentrant in start notify session error callback
 // and the reentrant start notify session error.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StartNotifySession_Reentrant_Error_Error) {
+       MAYBE_StartNotifySession_Reentrant_Error_Error) {
   ASSERT_NO_FATAL_FAILURE(
       FakeCharacteristicBoilerplate(/* properties: NOTIFY */ 0x10));
   SimulateGattDescriptor(
@@ -1667,11 +1868,14 @@
   EXPECT_EQ(2, error_callback_count_);
   ASSERT_EQ(0u, notify_sessions_.size());
 }
-#endif  // defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession StopNotifySession
+#else
+#define MAYBE_StopNotifySession DISABLED_StopNotifySession
+#endif
 // Tests StopNotifySession success on a characteristic that enabled Notify.
-TEST_F(BluetoothRemoteGattCharacteristicTest, StopNotifySession) {
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1692,12 +1896,16 @@
   EXPECT_FALSE(notify_sessions_[0]->IsActive());
   EXPECT_FALSE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_SessionDeleted StopNotifySession_SessionDeleted
+#else
+#define MAYBE_StopNotifySession_SessionDeleted \
+  DISABLED_StopNotifySession_SessionDeleted
+#endif
 // Tests that deleted sessions are stopped.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StopNotifySession_SessionDeleted) {
+       MAYBE_StopNotifySession_SessionDeleted) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1717,13 +1925,18 @@
   // Check that the notify session is inactive.
   EXPECT_FALSE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_SessionDeleted2 \
+  StopNotifySession_SessionDeleted2
+#else
+#define MAYBE_StopNotifySession_SessionDeleted2 \
+  DISABLED_StopNotifySession_SessionDeleted2
+#endif
 // Tests that deleting the sessions before the stop callbacks have been
 // invoked does not cause problems.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StopNotifySession_SessionDeleted2) {
+       MAYBE_StopNotifySession_SessionDeleted2) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1776,13 +1989,17 @@
   EXPECT_TRUE("Did not crash!");
   EXPECT_FALSE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_Cancelled StopNotifySession_Cancelled
+#else
+#define MAYBE_StopNotifySession_Cancelled DISABLED_StopNotifySession_Cancelled
+#endif
 // Tests that cancelling StopNotifySession works.
 // TODO(crbug.com/636270): Enable on Windows when SubscribeToNotifications is
 // implemented.
-TEST_F(BluetoothRemoteGattCharacteristicTest, StopNotifySession_Cancelled) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_StopNotifySession_Cancelled) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1803,11 +2020,16 @@
   // Cancel Stop by deleting the device before Stop finishes.
   DeleteDevice(device_);  // TODO(576906) delete only the characteristic.
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_AfterDeleted StopNotifySession_AfterDeleted
+#else
+#define MAYBE_StopNotifySession_AfterDeleted \
+  DISABLED_StopNotifySession_AfterDeleted
+#endif
 // Tests that deleted sessions are stopped.
-TEST_F(BluetoothRemoteGattCharacteristicTest, StopNotifySession_AfterDeleted) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_StopNotifySession_AfterDeleted) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1840,11 +2062,15 @@
             notify_sessions_[0]->GetCharacteristicIdentifier());
   EXPECT_FALSE(notify_sessions_[0]->IsActive());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_OnIndicate StopNotifySession_OnIndicate
+#else
+#define MAYBE_StopNotifySession_OnIndicate DISABLED_StopNotifySession_OnIndicate
+#endif
 // Tests StopNotifySession success on a characteristic that enabled Indicate.
-TEST_F(BluetoothRemoteGattCharacteristicTest, StopNotifySession_OnIndicate) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_StopNotifySession_OnIndicate) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1865,13 +2091,18 @@
   EXPECT_FALSE(notify_sessions_[0]->IsActive());
   EXPECT_FALSE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_OnNotifyAndIndicate \
+  StopNotifySession_OnNotifyAndIndicate
+#else
+#define MAYBE_StopNotifySession_OnNotifyAndIndicate \
+  DISABLED_StopNotifySession_OnNotifyAndIndicate
+#endif
 // Tests StopNotifySession success on a characteristic that enabled Notify &
 // Indicate.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StopNotifySession_OnNotifyAndIndicate) {
+       MAYBE_StopNotifySession_OnNotifyAndIndicate) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1893,11 +2124,14 @@
   EXPECT_FALSE(notify_sessions_[0]->IsActive());
   EXPECT_FALSE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_Error StopNotifySession_Error
+#else
+#define MAYBE_StopNotifySession_Error DISABLED_StopNotifySession_Error
+#endif
 // Tests StopNotifySession error
-TEST_F(BluetoothRemoteGattCharacteristicTest, StopNotifySession_Error) {
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_Error) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1921,11 +2155,15 @@
   EXPECT_FALSE(notify_sessions_[0]->IsActive());
   EXPECT_FALSE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_Multiple1 StopNotifySession_Multiple1
+#else
+#define MAYBE_StopNotifySession_Multiple1 DISABLED_StopNotifySession_Multiple1
+#endif
 // Tests multiple StopNotifySession calls for a single session.
-TEST_F(BluetoothRemoteGattCharacteristicTest, StopNotifySession_Multiple1) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_StopNotifySession_Multiple1) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -1967,11 +2205,15 @@
   EXPECT_FALSE(notify_sessions_[0]->IsActive());
   EXPECT_FALSE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_Multiple2 StopNotifySession_Multiple2
+#else
+#define MAYBE_StopNotifySession_Multiple2 DISABLED_StopNotifySession_Multiple2
+#endif
 // Tests multiple StartNotifySession calls and multiple StopNotifySession calls.
-TEST_F(BluetoothRemoteGattCharacteristicTest, StopNotifySession_Multiple2) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_StopNotifySession_Multiple2) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2024,12 +2266,16 @@
   EXPECT_FALSE(notify_sessions_[1]->IsActive());
   EXPECT_FALSE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_StopStart StopNotifySession_StopStart
+#else
+#define MAYBE_StopNotifySession_StopStart DISABLED_StopNotifySession_StopStart
+#endif
 // Tests starting a new notify session before the previous stop request
 // resolves.
-TEST_F(BluetoothRemoteGattCharacteristicTest, StopNotifySession_StopStart) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_StopNotifySession_StopStart) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2074,11 +2320,15 @@
   EXPECT_TRUE(notify_sessions_[1]->IsActive());
   EXPECT_TRUE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_StartStopStart StopNotifySession_StartStopStart
+#else
+#define MAYBE_StopNotifySession_StartStopStart \
+  DISABLED_StopNotifySession_StartStopStart
+#endif
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StopNotifySession_StartStopStart) {
+       MAYBE_StopNotifySession_StartStopStart) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2129,12 +2379,17 @@
 
   EXPECT_TRUE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_StopStopStart StopNotifySession_StopStopStart
+#else
+#define MAYBE_StopNotifySession_StopStopStart \
+  DISABLED_StopNotifySession_StopStopStart
+#endif
 // Tests starting a new notify session before the previous stop requests
 // resolve.
-TEST_F(BluetoothRemoteGattCharacteristicTest, StopNotifySession_StopStopStart) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_StopNotifySession_StopStopStart) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2193,11 +2448,16 @@
   EXPECT_TRUE(notify_sessions_[1]->IsActive());
   EXPECT_TRUE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_Reentrant_Success_Stop \
+  StopNotifySession_Reentrant_Success_Stop
+#else
+#define MAYBE_StopNotifySession_Reentrant_Success_Stop \
+  DISABLED_StopNotifySession_Reentrant_Success_Stop
+#endif
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StopNotifySession_Reentrant_Success_Stop) {
+       MAYBE_StopNotifySession_Reentrant_Success_Stop) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2237,11 +2497,16 @@
   EXPECT_FALSE(notify_sessions_[0]->IsActive());
   EXPECT_FALSE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_Reentrant_Stop_StartSuccess \
+  StopNotifySession_Reentrant_Stop_StartSuccess
+#else
+#define MAYBE_StopNotifySession_Reentrant_Stop_StartSuccess \
+  DISABLED_StopNotifySession_Reentrant_Stop_StartSuccess
+#endif
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StopNotifySession_Reentrant_Stop_StartSuccess) {
+       MAYBE_StopNotifySession_Reentrant_Stop_StartSuccess) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2282,11 +2547,16 @@
   EXPECT_TRUE(notify_sessions_[1]->IsActive());
   EXPECT_TRUE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_StopNotifySession_Reentrant_Stop_StartError \
+  StopNotifySession_Reentrant_Stop_StartError
+#else
+#define MAYBE_StopNotifySession_Reentrant_Stop_StartError \
+  DISABLED_StopNotifySession_Reentrant_Stop_StartError
+#endif
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StopNotifySession_Reentrant_Stop_StartError) {
+       MAYBE_StopNotifySession_Reentrant_Stop_StartError) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2325,12 +2595,15 @@
   EXPECT_FALSE(notify_sessions_[0]->IsActive());
   EXPECT_FALSE(characteristic1_->IsNotifying());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_GattCharacteristicAdded GattCharacteristicAdded
+#else
+#define MAYBE_GattCharacteristicAdded DISABLED_GattCharacteristicAdded
+#endif
 // TODO(786473) Android should report that services are discovered when a
 // characteristic is added, but currently does not.
-TEST_F(BluetoothRemoteGattCharacteristicTest, GattCharacteristicAdded) {
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GattCharacteristicAdded) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2342,11 +2615,16 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, observer.gatt_services_discovered_count());
 }
-#endif  // defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_GattCharacteristicValueChanged GattCharacteristicValueChanged
+#else
+#define MAYBE_GattCharacteristicValueChanged \
+  DISABLED_GattCharacteristicValueChanged
+#endif
 // Tests Characteristic Value changes during a Notify Session.
-TEST_F(BluetoothRemoteGattCharacteristicTest, GattCharacteristicValueChanged) {
+TEST_F(BluetoothRemoteGattCharacteristicTest,
+       MAYBE_GattCharacteristicValueChanged) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2370,15 +2648,20 @@
   EXPECT_EQ(2, observer.gatt_characteristic_value_changed_count());
   EXPECT_EQ(test_vector2, characteristic1_->GetValue());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID)
+#define MAYBE_TwoGattCharacteristicValueChanges \
+  TwoGattCharacteristicValueChanges
+#else
+#define MAYBE_TwoGattCharacteristicValueChanges \
+  DISABLED_TwoGattCharacteristicValueChanges
+#endif
 // Tests that Characteristic value changes arriving consecutively result in
 // two notifications with correct values.
 // macOS: Does not apply. All events arrive on the UI Thread.
 // TODO(crbug.com/694102): Enable this test on Windows.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       TwoGattCharacteristicValueChanges) {
+       MAYBE_TwoGattCharacteristicValueChanges) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2400,15 +2683,20 @@
   EXPECT_EQ(std::vector<std::vector<uint8_t>>({test_vector1, test_vector2}),
             observer.previous_characteristic_value_changed_values());
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID) || defined(OS_WIN)
+#define MAYBE_GattCharacteristicValueChanged_AfterDeleted \
+  GattCharacteristicValueChanged_AfterDeleted
+#else
+#define MAYBE_GattCharacteristicValueChanged_AfterDeleted \
+  DISABLED_GattCharacteristicValueChanged_AfterDeleted
+#endif
 // Tests Characteristic Value changing after a Notify Session and objects being
 // destroyed.
 // macOS: Not applicable: This can never happen if CBPeripheral delegate is set
 // to nil.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       GattCharacteristicValueChanged_AfterDeleted) {
+       MAYBE_GattCharacteristicValueChanged_AfterDeleted) {
   ASSERT_NO_FATAL_FAILURE(StartNotifyBoilerplate(
       /* properties: NOTIFY */ 0x10, NotifyValueState::NOTIFY));
   TestBluetoothAdapterObserver observer(adapter_);
@@ -2423,19 +2711,26 @@
   EXPECT_TRUE("Did not crash!");
   EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_WIN)
-TEST_F(BluetoothRemoteGattCharacteristicTest, GetDescriptors_FindNone) {
+#define MAYBE_GetDescriptors_FindNone GetDescriptors_FindNone
+#else
+#define MAYBE_GetDescriptors_FindNone DISABLED_GetDescriptors_FindNone
+#endif
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetDescriptors_FindNone) {
   ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate());
 
   EXPECT_EQ(0u, characteristic1_->GetDescriptors().size());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_WIN)
+#define MAYBE_GetDescriptors_and_GetDescriptor GetDescriptors_and_GetDescriptor
+#else
+#define MAYBE_GetDescriptors_and_GetDescriptor \
+  DISABLED_GetDescriptors_and_GetDescriptor
+#endif
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       GetDescriptors_and_GetDescriptor) {
+       MAYBE_GetDescriptors_and_GetDescriptor) {
   ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate());
 
   // Add several Descriptors:
@@ -2477,10 +2772,13 @@
   // ... but not uuid 3
   EXPECT_FALSE(c1_uuid1 == uuid3 || c1_uuid2 == uuid3);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_WIN)
-TEST_F(BluetoothRemoteGattCharacteristicTest, GetDescriptorsByUUID) {
+#define MAYBE_GetDescriptorsByUUID GetDescriptorsByUUID
+#else
+#define MAYBE_GetDescriptorsByUUID DISABLED_GetDescriptorsByUUID
+#endif
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetDescriptorsByUUID) {
   ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate());
 
   // Add several Descriptors:
@@ -2507,14 +2805,17 @@
   EXPECT_EQ(0u, characteristic2_->GetDescriptorsByUUID(id2).size());
   EXPECT_EQ(0u, characteristic1_->GetDescriptorsByUUID(id3).size());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_WIN)
 
 #if defined(OS_ANDROID)
+#define MAYBE_ReadDuringDisconnect ReadDuringDisconnect
+#else
+#define MAYBE_ReadDuringDisconnect DISABLED_ReadDuringDisconnect
+#endif
 // Tests that read requests after a device disconnects but before the disconnect
 // task has a chance to run result in an error.
 // macOS: Does not apply. All events arrive on the UI Thread.
 // TODO(crbug.com/694102): Enable this test on Windows.
-TEST_F(BluetoothRemoteGattCharacteristicTest, ReadDuringDisconnect) {
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadDuringDisconnect) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2532,14 +2833,17 @@
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED,
             last_gatt_error_code_);
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID)
+#define MAYBE_WriteDuringDisconnect WriteDuringDisconnect
+#else
+#define MAYBE_WriteDuringDisconnect DISABLED_WriteDuringDisconnect
+#endif
 // Tests that write requests after a device disconnects but before the
 // disconnect task runs result in an error.
 // macOS: Does not apply. All events arrive on the UI Thread.
 // TODO(crbug.com/694102): Enable this test on Windows.
-TEST_F(BluetoothRemoteGattCharacteristicTest, WriteDuringDisconnect) {
+TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteDuringDisconnect) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2557,15 +2861,20 @@
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED,
             last_gatt_error_code_);
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_MACOSX)
+#define MAYBE_WriteWithoutResponseDuringDisconnect \
+  WriteWithoutResponseDuringDisconnect
+#else
+#define MAYBE_WriteWithoutResponseDuringDisconnect \
+  DISABLED_WriteWithoutResponseDuringDisconnect
+#endif
 // Tests that writing without response during a disconnect results in an error.
 // Only applies to macOS whose events arrive all on the UI thread. See other
 // *DuringDisconnect tests for Android and Windows whose events arrive on a
 // different thread.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       WriteWithoutResponseDuringDisconnect) {
+       MAYBE_WriteWithoutResponseDuringDisconnect) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2582,15 +2891,20 @@
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED,
             last_gatt_error_code_);
 }
-#endif  // defined(OS_MACOSX)
 
 #if defined(OS_MACOSX)
+#define MAYBE_DisconnectCalledDuringWriteWithoutResponse \
+  DisconnectCalledDuringWriteWithoutResponse
+#else
+#define MAYBE_DisconnectCalledDuringWriteWithoutResponse \
+  DISABLED_DisconnectCalledDuringWriteWithoutResponse
+#endif
 // Tests that disconnecting right after a write without response results in an
 // error.
 // TODO(crbug.com/726534): Enable on other platforms depending on the resolution
 // of crbug.com/726534.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       DisconnectCalledDuringWriteWithoutResponse) {
+       MAYBE_DisconnectCalledDuringWriteWithoutResponse) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2610,15 +2924,20 @@
   SimulateGattDisconnection(device_);
   base::RunLoop().RunUntilIdle();
 }
-#endif  // defined(OS_MACOSX)
 
 #if defined(OS_MACOSX)
+#define MAYBE_DisconnectCalledBeforeWriteWithoutResponse \
+  DisconnectCalledBeforeWriteWithoutResponse
+#else
+#define MAYBE_DisconnectCalledBeforeWriteWithoutResponse \
+  DISABLED_DisconnectCalledBeforeWriteWithoutResponse
+#endif
 // Tests that disconnecting right before a write without response results in an
 // error.
 // TODO(crbug.com/726534): Enable on other platforms depending on the resolution
 // of crbug.com/726534.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       DisconnectCalledBeforeWriteWithoutResponse) {
+       MAYBE_DisconnectCalledBeforeWriteWithoutResponse) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2638,15 +2957,20 @@
   SimulateGattDisconnection(device_);
   base::RunLoop().RunUntilIdle();
 }
-#endif  // defined(OS_MACOSX)
 
 #if defined(OS_ANDROID)
+#define MAYBE_StartNotifySessionDuringDisconnect \
+  StartNotifySessionDuringDisconnect
+#else
+#define MAYBE_StartNotifySessionDuringDisconnect \
+  DISABLED_StartNotifySessionDuringDisconnect
+#endif
 // Tests that start notifications requests after a device disconnects but before
 // the disconnect task runs result in an error.
 // macOS: Does not apply. All events arrive on the UI Thread.
 // TODO(crbug.com/694102): Enable this test on Windows.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StartNotifySessionDuringDisconnect) {
+       MAYBE_StartNotifySessionDuringDisconnect) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2667,15 +2991,20 @@
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED,
             last_gatt_error_code_);
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID)
+#define MAYBE_StopNotifySessionDuringDisconnect \
+  StopNotifySessionDuringDisconnect
+#else
+#define MAYBE_StopNotifySessionDuringDisconnect \
+  DISABLED_StopNotifySessionDuringDisconnect
+#endif
 // Tests that stop notifications requests after a device disconnects but before
 // the disconnect task runs do not result in a crash.
 // macOS: Does not apply. All events arrive on the UI Thread.
 // TODO(crbug.com/694102): Enable this test on Windows.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       StopNotifySessionDuringDisconnect) {
+       MAYBE_StopNotifySessionDuringDisconnect) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2688,15 +3017,20 @@
   notify_sessions_[0]->Stop(GetStopNotifyCallback(Call::EXPECTED));
   base::RunLoop().RunUntilIdle();
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID)
+#define MAYBE_DeleteNotifySessionDuringDisconnect \
+  DeleteNotifySessionDuringDisconnect
+#else
+#define MAYBE_DeleteNotifySessionDuringDisconnect \
+  DISABLED_DeleteNotifySessionDuringDisconnect
+#endif
 // Tests that deleting notify sessions after a device disconnects but before the
 // disconnect task runs do not result in a crash.
 // macOS: Does not apply. All events arrive on the UI Thread.
 // TODO(crbug.com/694102): Enable this test on Windows.
 TEST_F(BluetoothRemoteGattCharacteristicTest,
-       DeleteNotifySessionDuringDisconnect) {
+       MAYBE_DeleteNotifySessionDuringDisconnect) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -2709,7 +3043,6 @@
   notify_sessions_.clear();
   base::RunLoop().RunUntilIdle();
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_MACOSX)
 // Tests to receive a services changed notification from macOS, while
@@ -2826,4 +3159,5 @@
   EXPECT_EQ(2, observer.device_changed_count());
 }
 #endif  // defined(OS_MACOSX)
+
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc b/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc
index 6a500a8..46a0703 100644
--- a/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/run_loop.h"
+#include "build/build_config.h"
 #include "device/bluetooth/bluetooth_remote_gatt_characteristic.h"
 #include "device/bluetooth/bluetooth_remote_gatt_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -11,11 +12,14 @@
 #include "device/bluetooth/test/bluetooth_test_android.h"
 #elif defined(OS_MACOSX)
 #include "device/bluetooth/test/bluetooth_test_mac.h"
+#elif defined(OS_WIN)
+#include "device/bluetooth/test/bluetooth_test_win.h"
+#elif defined(OS_CHROMEOS) || defined(OS_LINUX)
+#include "device/bluetooth/test/bluetooth_test_bluez.h"
 #endif
 
 namespace device {
 
-#if defined(OS_ANDROID) || defined(OS_MACOSX)
 class BluetoothRemoteGattDescriptorTest : public BluetoothTest {
  public:
   // Creates adapter_, device_, service_, characteristic_,
@@ -52,10 +56,13 @@
   BluetoothRemoteGattDescriptor* descriptor1_ = nullptr;
   BluetoothRemoteGattDescriptor* descriptor2_ = nullptr;
 };
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
-TEST_F(BluetoothRemoteGattDescriptorTest, GetIdentifier) {
+#define MAYBE_GetIdentifier GetIdentifier
+#else
+#define MAYBE_GetIdentifier DISABLED_GetIdentifier
+#endif
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_GetIdentifier) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -136,10 +143,13 @@
 
   EXPECT_NE(desc5->GetIdentifier(), desc6->GetIdentifier());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
-TEST_F(BluetoothRemoteGattDescriptorTest, GetUUID) {
+#define MAYBE_GetUUID GetUUID
+#else
+#define MAYBE_GetUUID DISABLED_GetUUID
+#endif
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_GetUUID) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -182,11 +192,14 @@
   EXPECT_EQ(uuid1, descriptor1->GetUUID());
   EXPECT_EQ(uuid2, descriptor2->GetUUID());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_ReadRemoteDescriptor_Empty ReadRemoteDescriptor_Empty
+#else
+#define MAYBE_ReadRemoteDescriptor_Empty DISABLED_ReadRemoteDescriptor_Empty
+#endif
 // Tests ReadRemoteDescriptor and GetValue with empty value buffer.
-TEST_F(BluetoothRemoteGattDescriptorTest, ReadRemoteDescriptor_Empty) {
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadRemoteDescriptor_Empty) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -207,11 +220,14 @@
   EXPECT_EQ(empty_vector, last_read_value_);
   EXPECT_EQ(empty_vector, descriptor1_->GetValue());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_WriteRemoteDescriptor_Empty WriteRemoteDescriptor_Empty
+#else
+#define MAYBE_WriteRemoteDescriptor_Empty DISABLED_WriteRemoteDescriptor_Empty
+#endif
 // Tests WriteRemoteDescriptor with empty value buffer.
-TEST_F(BluetoothRemoteGattDescriptorTest, WriteRemoteDescriptor_Empty) {
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteRemoteDescriptor_Empty) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -231,13 +247,19 @@
 
   EXPECT_EQ(empty_vector, last_write_value_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID)
+#define MAYBE_ReadRemoteDescriptor_AfterDeleted \
+  ReadRemoteDescriptor_AfterDeleted
+#else
+#define MAYBE_ReadRemoteDescriptor_AfterDeleted \
+  DISABLED_ReadRemoteDescriptor_AfterDeleted
+#endif
 // Tests ReadRemoteDescriptor completing after Chrome objects are deleted.
 // macOS: Not applicable: This can never happen if CBPeripheral delegate is set
 // to nil.
-TEST_F(BluetoothRemoteGattDescriptorTest, ReadRemoteDescriptor_AfterDeleted) {
+TEST_F(BluetoothRemoteGattDescriptorTest,
+       MAYBE_ReadRemoteDescriptor_AfterDeleted) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -256,13 +278,19 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE("Did not crash!");
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID)
+#define MAYBE_WriteRemoteDescriptor_AfterDeleted \
+  WriteRemoteDescriptor_AfterDeleted
+#else
+#define MAYBE_WriteRemoteDescriptor_AfterDeleted \
+  DISABLED_WriteRemoteDescriptor_AfterDeleted
+#endif
 // Tests WriteRemoteDescriptor completing after Chrome objects are deleted.
 // macOS: Not applicable: This can never happen if CBPeripheral delegate is set
 // to nil.
-TEST_F(BluetoothRemoteGattDescriptorTest, WriteRemoteDescriptor_AfterDeleted) {
+TEST_F(BluetoothRemoteGattDescriptorTest,
+       MAYBE_WriteRemoteDescriptor_AfterDeleted) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -281,11 +309,14 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE("Did not crash!");
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_ReadRemoteDescriptor ReadRemoteDescriptor
+#else
+#define MAYBE_ReadRemoteDescriptor DISABLED_ReadRemoteDescriptor
+#endif
 // Tests ReadRemoteDescriptor and GetValue with non-empty value buffer.
-TEST_F(BluetoothRemoteGattDescriptorTest, ReadRemoteDescriptor) {
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadRemoteDescriptor) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -309,11 +340,14 @@
   EXPECT_EQ(test_vector, last_read_value_);
   EXPECT_EQ(test_vector, descriptor1_->GetValue());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_WriteRemoteDescriptor WriteRemoteDescriptor
+#else
+#define MAYBE_WriteRemoteDescriptor DISABLED_WriteRemoteDescriptor
+#endif
 // Tests WriteRemoteDescriptor with non-empty value buffer.
-TEST_F(BluetoothRemoteGattDescriptorTest, WriteRemoteDescriptor) {
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteRemoteDescriptor) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -331,11 +365,14 @@
 
   EXPECT_EQ(test_vector, last_write_value_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_ReadRemoteDescriptor_Twice ReadRemoteDescriptor_Twice
+#else
+#define MAYBE_ReadRemoteDescriptor_Twice DISABLED_ReadRemoteDescriptor_Twice
+#endif
 // Tests ReadRemoteDescriptor and GetValue multiple times.
-TEST_F(BluetoothRemoteGattDescriptorTest, ReadRemoteDescriptor_Twice) {
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadRemoteDescriptor_Twice) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -368,11 +405,14 @@
   EXPECT_EQ(empty_vector, last_read_value_);
   EXPECT_EQ(empty_vector, descriptor1_->GetValue());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_WriteRemoteDescriptor_Twice WriteRemoteDescriptor_Twice
+#else
+#define MAYBE_WriteRemoteDescriptor_Twice DISABLED_WriteRemoteDescriptor_Twice
+#endif
 // Tests WriteRemoteDescriptor multiple times.
-TEST_F(BluetoothRemoteGattDescriptorTest, WriteRemoteDescriptor_Twice) {
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteRemoteDescriptor_Twice) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -403,12 +443,17 @@
   EXPECT_EQ(0, error_callback_count_);
   EXPECT_EQ(empty_vector, last_write_value_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_ReadRemoteDescriptor_MultipleDescriptors \
+  ReadRemoteDescriptor_MultipleDescriptors
+#else
+#define MAYBE_ReadRemoteDescriptor_MultipleDescriptors \
+  DISABLED_ReadRemoteDescriptor_MultipleDescriptors
+#endif
 // Tests ReadRemoteDescriptor on two descriptors.
 TEST_F(BluetoothRemoteGattDescriptorTest,
-       ReadRemoteDescriptor_MultipleDescriptors) {
+       MAYBE_ReadRemoteDescriptor_MultipleDescriptors) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -440,12 +485,17 @@
   EXPECT_EQ(test_vector1, descriptor1_->GetValue());
   EXPECT_EQ(test_vector2, descriptor2_->GetValue());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_WriteRemoteDescriptor_MultipleDescriptors \
+  WriteRemoteDescriptor_MultipleDescriptors
+#else
+#define MAYBE_WriteRemoteDescriptor_MultipleDescriptors \
+  DISABLED_WriteRemoteDescriptor_MultipleDescriptors
+#endif
 // Tests WriteRemoteDescriptor on two descriptors.
 TEST_F(BluetoothRemoteGattDescriptorTest,
-       WriteRemoteDescriptor_MultipleDescriptors) {
+       MAYBE_WriteRemoteDescriptor_MultipleDescriptors) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -475,11 +525,14 @@
   EXPECT_EQ(2, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_ReadError ReadError
+#else
+#define MAYBE_ReadError DISABLED_ReadError
+#endif
 // Tests ReadRemoteDescriptor asynchronous error.
-TEST_F(BluetoothRemoteGattDescriptorTest, ReadError) {
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadError) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -496,11 +549,14 @@
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_INVALID_LENGTH,
             last_gatt_error_code_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_WriteError WriteError
+#else
+#define MAYBE_WriteError DISABLED_WriteError
+#endif
 // Tests WriteRemoteDescriptor asynchronous error.
-TEST_F(BluetoothRemoteGattDescriptorTest, WriteError) {
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteError) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -520,13 +576,16 @@
   EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_INVALID_LENGTH,
             last_gatt_error_code_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID)
+#define MAYBE_ReadSynchronousError ReadSynchronousError
+#else
+#define MAYBE_ReadSynchronousError DISABLED_ReadSynchronousError
+#endif
 // Tests ReadRemoteDescriptor synchronous error.
 // Test not relevant for macOS since descriptor read cannot generate
 // synchronous error.
-TEST_F(BluetoothRemoteGattDescriptorTest, ReadSynchronousError) {
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadSynchronousError) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -554,13 +613,16 @@
   EXPECT_EQ(1, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID)
+#define MAYBE_WriteSynchronousError WriteSynchronousError
+#else
+#define MAYBE_WriteSynchronousError DISABLED_WriteSynchronousError
+#endif
 // Tests WriteRemoteDescriptor synchronous error.
 // Test not relevant for macOS since descriptor write cannot generate
 // synchronous error.
-TEST_F(BluetoothRemoteGattDescriptorTest, WriteSynchronousError) {
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteSynchronousError) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -589,11 +651,16 @@
   EXPECT_EQ(1, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_ReadRemoteDescriptor_ReadPending ReadRemoteDescriptor_ReadPending
+#else
+#define MAYBE_ReadRemoteDescriptor_ReadPending \
+  DISABLED_ReadRemoteDescriptor_ReadPending
+#endif
 // Tests ReadRemoteDescriptor error with a pending read operation.
-TEST_F(BluetoothRemoteGattDescriptorTest, ReadRemoteDescriptor_ReadPending) {
+TEST_F(BluetoothRemoteGattDescriptorTest,
+       MAYBE_ReadRemoteDescriptor_ReadPending) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -620,11 +687,17 @@
   EXPECT_EQ(1, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_WriteRemoteDescriptor_WritePending \
+  WriteRemoteDescriptor_WritePending
+#else
+#define MAYBE_WriteRemoteDescriptor_WritePending \
+  DISABLED_WriteRemoteDescriptor_WritePending
+#endif
 // Tests WriteRemoteDescriptor error with a pending write operation.
-TEST_F(BluetoothRemoteGattDescriptorTest, WriteRemoteDescriptor_WritePending) {
+TEST_F(BluetoothRemoteGattDescriptorTest,
+       MAYBE_WriteRemoteDescriptor_WritePending) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -652,11 +725,17 @@
   EXPECT_EQ(1, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_ReadRemoteDescriptor_WritePending \
+  ReadRemoteDescriptor_WritePending
+#else
+#define MAYBE_ReadRemoteDescriptor_WritePending \
+  DISABLED_ReadRemoteDescriptor_WritePending
+#endif
 // Tests ReadRemoteDescriptor error with a pending write operation.
-TEST_F(BluetoothRemoteGattDescriptorTest, ReadRemoteDescriptor_WritePending) {
+TEST_F(BluetoothRemoteGattDescriptorTest,
+       MAYBE_ReadRemoteDescriptor_WritePending) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -683,11 +762,17 @@
   EXPECT_EQ(1, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX)
+#define MAYBE_WriteRemoteDescriptor_ReadPending \
+  WriteRemoteDescriptor_ReadPending
+#else
+#define MAYBE_WriteRemoteDescriptor_ReadPending \
+  DISABLED_WriteRemoteDescriptor_ReadPending
+#endif
 // Tests WriteRemoteDescriptor error with a pending Read operation.
-TEST_F(BluetoothRemoteGattDescriptorTest, WriteRemoteDescriptor_ReadPending) {
+TEST_F(BluetoothRemoteGattDescriptorTest,
+       MAYBE_WriteRemoteDescriptor_ReadPending) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -714,14 +799,17 @@
   EXPECT_EQ(1, callback_count_);
   EXPECT_EQ(0, error_callback_count_);
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID)
+#define MAYBE_ReadDuringDisconnect ReadDuringDisconnect
+#else
+#define MAYBE_ReadDuringDisconnect DISABLED_ReadDuringDisconnect
+#endif
 // Tests that read requests after a device disconnects but before the
 // disconnect task runs do not result in a crash.
 // macOS: Does not apply. All events arrive on the UI Thread.
 // TODO(crbug.com/694102): Enable this test on Windows.
-TEST_F(BluetoothRemoteGattDescriptorTest, ReadDuringDisconnect) {
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadDuringDisconnect) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -738,14 +826,17 @@
   base::RunLoop().RunUntilIdle();
   // TODO(crbug.com/621901): Test error callback was called.
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID)
+#define MAYBE_WriteDuringDisconnect WriteDuringDisconnect
+#else
+#define MAYBE_WriteDuringDisconnect DISABLED_WriteDuringDisconnect
+#endif
 // Tests that write requests after a device disconnects but before the
 // disconnect task runs do not result in a crash.
 // macOS: Does not apply. All events arrive on the UI Thread.
 // TODO(crbug.com/694102): Enable this test on Windows.
-TEST_F(BluetoothRemoteGattDescriptorTest, WriteDuringDisconnect) {
+TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteDuringDisconnect) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -763,7 +854,6 @@
   base::RunLoop().RunUntilIdle();
   // TODO(crbug.com/621901): Test that an error was returned.
 }
-#endif  // defined(OS_ANDROID)
 
 #if defined(OS_MACOSX)
 // Tests NSString for descriptor value for macOS.
diff --git a/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc b/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc
index c70448c..10070626 100644
--- a/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc
@@ -15,17 +15,21 @@
 #include "device/bluetooth/test/bluetooth_test_mac.h"
 #elif defined(OS_WIN)
 #include "device/bluetooth/test/bluetooth_test_win.h"
+#elif defined(OS_CHROMEOS) || defined(OS_LINUX)
+#include "device/bluetooth/test/bluetooth_test_bluez.h"
 #endif
 
 namespace device {
 
-#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 class BluetoothRemoteGattServiceTest : public BluetoothTest {};
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 // Android is excluded because it fires a single discovery event per device.
 #if defined(OS_WIN) || defined(OS_MACOSX)
-TEST_F(BluetoothRemoteGattServiceTest, IsDiscoveryComplete) {
+#define MAYBE_IsDiscoveryComplete IsDiscoveryComplete
+#else
+#define MAYBE_IsDiscoveryComplete DISABLED_IsDiscoveryComplete
+#endif
+TEST_F(BluetoothRemoteGattServiceTest, MAYBE_IsDiscoveryComplete) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -44,10 +48,13 @@
   BluetoothRemoteGattService* service = device->GetGattServices()[0];
   EXPECT_TRUE(service->IsDiscoveryComplete());
 }
-#endif  // defined(OS_WIN) || defined(OS_MACOSX)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
-TEST_F(BluetoothRemoteGattServiceTest, GetIdentifier) {
+#define MAYBE_GetIdentifier GetIdentifier
+#else
+#define MAYBE_GetIdentifier DISABLED_GetIdentifier
+#endif
+TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetIdentifier) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -88,10 +95,13 @@
 
   EXPECT_NE(service3->GetIdentifier(), service4->GetIdentifier());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
-TEST_F(BluetoothRemoteGattServiceTest, GetUUID) {
+#define MAYBE_GetUUID GetUUID
+#else
+#define MAYBE_GetUUID DISABLED_GetUUID
+#endif
+TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetUUID) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -116,10 +126,13 @@
   EXPECT_EQ(uuid, device->GetGattServices()[0]->GetUUID());
   EXPECT_EQ(uuid, device->GetGattServices()[1]->GetUUID());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
-TEST_F(BluetoothRemoteGattServiceTest, GetCharacteristics_FindNone) {
+#define MAYBE_GetCharacteristics_FindNone GetCharacteristics_FindNone
+#else
+#define MAYBE_GetCharacteristics_FindNone DISABLED_GetCharacteristics_FindNone
+#endif
+TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetCharacteristics_FindNone) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -140,11 +153,16 @@
 
   EXPECT_EQ(0u, service->GetCharacteristics().size());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_GetCharacteristics_and_GetCharacteristic \
+  GetCharacteristics_and_GetCharacteristic
+#else
+#define MAYBE_GetCharacteristics_and_GetCharacteristic \
+  DISABLED_GetCharacteristics_and_GetCharacteristic
+#endif
 TEST_F(BluetoothRemoteGattServiceTest,
-       GetCharacteristics_and_GetCharacteristic) {
+       MAYBE_GetCharacteristics_and_GetCharacteristic) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -194,10 +212,13 @@
   EXPECT_EQ(service->GetCharacteristic(char_id1),
             service->GetCharacteristic(char_id1));
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
-TEST_F(BluetoothRemoteGattServiceTest, GetCharacteristicsByUUID) {
+#define MAYBE_GetCharacteristicsByUUID GetCharacteristicsByUUID
+#else
+#define MAYBE_GetCharacteristicsByUUID DISABLED_GetCharacteristicsByUUID
+#endif
+TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetCharacteristicsByUUID) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -255,10 +276,16 @@
       service2->GetCharacteristicsByUUID(characteristic_uuid_not_exist_in_setup)
           .empty());
 }
-#endif  // defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_MACOSX) || defined(OS_WIN)
-TEST_F(BluetoothRemoteGattServiceTest, GattCharacteristics_ObserversCalls) {
+#define MAYBE_GattCharacteristics_ObserversCalls \
+  GattCharacteristics_ObserversCalls
+#else
+#define MAYBE_GattCharacteristics_ObserversCalls \
+  DISABLED_GattCharacteristics_ObserversCalls
+#endif
+TEST_F(BluetoothRemoteGattServiceTest,
+       MAYBE_GattCharacteristics_ObserversCalls) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -314,10 +341,13 @@
   EXPECT_FALSE(service->GetCharacteristic(removed_char));
   EXPECT_EQ(0u, service->GetCharacteristics().size());
 }
-#endif  //  defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_WIN) || defined(OS_MACOSX)
-TEST_F(BluetoothRemoteGattServiceTest, SimulateGattServiceRemove) {
+#define MAYBE_SimulateGattServiceRemove SimulateGattServiceRemove
+#else
+#define MAYBE_SimulateGattServiceRemove DISABLED_SimulateGattServiceRemove
+#endif
+TEST_F(BluetoothRemoteGattServiceTest, MAYBE_SimulateGattServiceRemove) {
   if (!PlatformSupportsLowEnergy()) {
     LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
     return;
@@ -348,7 +378,6 @@
   EXPECT_FALSE(device->GetGattService(removed_service));
   EXPECT_EQ(device->GetGattServices()[0], service2);
 }
-#endif  // defined(OS_MACOSX) || defined(OS_WIN)
 
 #if defined(OS_MACOSX)
 // Tests to receive a services changed notification from macOS, while
diff --git a/infra/config/global/luci-milo-dev.cfg b/infra/config/global/luci-milo-dev.cfg
index f0d4a08..992acd6f 100644
--- a/infra/config/global/luci-milo-dev.cfg
+++ b/infra/config/global/luci-milo-dev.cfg
@@ -3052,6 +3052,22 @@
     name: "buildbot/chromium.perf.fyi/One Buildbot Step Test Builder"
     category: "linux"
   }
+  builders: {
+    name: "buildbot/chromium.perf.fyi/Mac 10.12 Laptop Low End"
+    category: "mac"
+  }
+  builders: {
+    name: "buildbot/chromium.perf.fyi/Mac 10.13 Laptop High End"
+    category: "mac"
+  }
+  builders: {
+    name: "buildbot/chromium.perf.fyi/Mac Builder FYI"
+    category: "mac"
+  }
+  builders: {
+    name: "buildbot/chromium.perf.fyi/Android Go"
+    category: "android"
+  }
 }
 
 consoles: {
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 7500eba2..f6de24d 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -3721,6 +3721,22 @@
     name: "buildbot/chromium.perf.fyi/One Buildbot Step Test Builder"
     category: "linux"
   }
+  builders: {
+    name: "buildbot/chromium.perf.fyi/Mac 10.12 Laptop Low End"
+    category: "mac"
+  }
+  builders: {
+    name: "buildbot/chromium.perf.fyi/Mac 10.13 Laptop High End"
+    category: "mac"
+  }
+  builders: {
+    name: "buildbot/chromium.perf.fyi/Mac Builder FYI"
+    category: "mac"
+  }
+  builders: {
+    name: "buildbot/chromium.perf.fyi/Android Go"
+    category: "android"
+  }
 }
 
 consoles: {
diff --git a/ios/chrome/browser/passwords/notify_auto_signin_view_controller.mm b/ios/chrome/browser/passwords/notify_auto_signin_view_controller.mm
index 036196cf..3183b43 100644
--- a/ios/chrome/browser/passwords/notify_auto_signin_view_controller.mm
+++ b/ios/chrome/browser/passwords/notify_auto_signin_view_controller.mm
@@ -142,7 +142,7 @@
   // Fetch user's avatar and update displayed image.
   if (self.iconURL.is_valid()) {
     __weak NotifyUserAutoSigninViewController* weakSelf = self;
-    _imageFetcher->StartOrQueueNetworkRequest(
+    _imageFetcher->FetchImage(
         _iconURL.spec(), _iconURL,
         base::BindBlockArc(^(const std::string& id, const gfx::Image& image,
                              const image_fetcher::RequestMetadata& metadata) {
diff --git a/ios/chrome/browser/ui/BUILD.gn b/ios/chrome/browser/ui/BUILD.gn
index 10f444ee..f7917c9 100644
--- a/ios/chrome/browser/ui/BUILD.gn
+++ b/ios/chrome/browser/ui/BUILD.gn
@@ -388,6 +388,7 @@
     "//ios/chrome/browser/ui/qr_scanner:coordinator",
     "//ios/chrome/browser/ui/qr_scanner/requirements",
     "//ios/chrome/browser/ui/reading_list",
+    "//ios/chrome/browser/ui/recent_tabs",
     "//ios/chrome/browser/ui/sad_tab:coordinator",
     "//ios/chrome/browser/ui/settings/sync_utils",
     "//ios/chrome/browser/ui/signin_interaction/public",
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view_controller.mm
index 9663985..39ef14b 100644
--- a/ios/chrome/browser/ui/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view_controller.mm
@@ -169,7 +169,6 @@
 #import "ios/chrome/browser/ui/new_foreground_tab_fullscreen_disabler.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
 #import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
-#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_coordinator.h"
 #import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
 #import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
 #import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
@@ -182,6 +181,7 @@
 #import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
 #import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
 #import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
+#import "ios/chrome/browser/ui/recent_tabs/recent_tabs_coordinator.h"
 #include "ios/chrome/browser/ui/rtl_geometry.h"
 #import "ios/chrome/browser/ui/sad_tab/sad_tab_legacy_coordinator.h"
 #import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h"
@@ -3145,10 +3145,9 @@
 - (void)createRecentTabsCoordinator {
   if (experimental_flags::IsRecentTabsUIRebootEnabled()) {
     // New RecentTabs UIReboot coordinator.
-    RecentTabsTableCoordinator* recentTabsCoordinator =
-        [[RecentTabsTableCoordinator alloc]
-            initWithBaseViewController:self
-                          browserState:_browserState];
+    RecentTabsCoordinator* recentTabsCoordinator = [
+        [RecentTabsCoordinator alloc] initWithBaseViewController:self
+                                                    browserState:_browserState];
     recentTabsCoordinator.loader = self;
     recentTabsCoordinator.dispatcher = self.dispatcher;
     self.recentTabsCoordinator = recentTabsCoordinator;
diff --git a/ios/chrome/browser/ui/ntp/recent_tabs/BUILD.gn b/ios/chrome/browser/ui/ntp/recent_tabs/BUILD.gn
index 7f55538..5029c6e2 100644
--- a/ios/chrome/browser/ui/ntp/recent_tabs/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/recent_tabs/BUILD.gn
@@ -18,11 +18,7 @@
     "recent_tabs_handset_coordinator.mm",
     "recent_tabs_handset_view_controller.h",
     "recent_tabs_handset_view_controller.mm",
-    "recent_tabs_mediator.h",
-    "recent_tabs_mediator.mm",
     "recent_tabs_table_consumer.h",
-    "recent_tabs_table_coordinator.h",
-    "recent_tabs_table_coordinator.mm",
     "recent_tabs_table_view_controller.h",
     "recent_tabs_table_view_controller.mm",
     "sessions_sync_user_state.h",
diff --git a/ios/chrome/browser/ui/ntp/recent_tabs/OWNERS b/ios/chrome/browser/ui/ntp/recent_tabs/OWNERS
index f192143..6e1a31f 100644
--- a/ios/chrome/browser/ui/ntp/recent_tabs/OWNERS
+++ b/ios/chrome/browser/ui/ntp/recent_tabs/OWNERS
@@ -1,4 +1,5 @@
 gambard@chromium.org
+sczs@chromium.org
 
 # TEAM: ios-directory-owners@chromium.org
 # OS: iOS
diff --git a/ios/chrome/browser/ui/ntp/recent_tabs/legacy_recent_tabs_table_coordinator.mm b/ios/chrome/browser/ui/ntp/recent_tabs/legacy_recent_tabs_table_coordinator.mm
index 1540408..95802c1 100644
--- a/ios/chrome/browser/ui/ntp/recent_tabs/legacy_recent_tabs_table_coordinator.mm
+++ b/ios/chrome/browser/ui/ntp/recent_tabs/legacy_recent_tabs_table_coordinator.mm
@@ -6,7 +6,7 @@
 
 #include "base/logging.h"
 #import "ios/chrome/browser/ui/ntp/recent_tabs/legacy_recent_tabs_table_view_controller.h"
-#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_mediator.h"
+#import "ios/chrome/browser/ui/recent_tabs/recent_tabs_mediator.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
diff --git a/ios/chrome/browser/ui/recent_tabs/BUILD.gn b/ios/chrome/browser/ui/recent_tabs/BUILD.gn
new file mode 100644
index 0000000..6c37c21
--- /dev/null
+++ b/ios/chrome/browser/ui/recent_tabs/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("recent_tabs") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "recent_tabs_coordinator.h",
+    "recent_tabs_coordinator.mm",
+    "recent_tabs_mediator.h",
+    "recent_tabs_mediator.mm",
+  ]
+  deps = [
+    "//base",
+    "//components/browser_sync",
+    "//components/sessions",
+    "//components/sync",
+    "//ios/chrome/app/strings",
+    "//ios/chrome/browser/browser_state",
+    "//ios/chrome/browser/sessions",
+    "//ios/chrome/browser/sync",
+    "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
+    "//ios/chrome/browser/ui/ntp",
+    "//ios/chrome/browser/ui/ntp/recent_tabs",
+    "//ios/chrome/browser/ui/table_view",
+    "//ios/chrome/browser/ui/util",
+    "//ui/base",
+  ]
+  allow_circular_includes_from = [ "//ios/chrome/browser/ui/ntp/recent_tabs" ]
+}
diff --git a/ios/chrome/browser/ui/recent_tabs/OWNERS b/ios/chrome/browser/ui/recent_tabs/OWNERS
new file mode 100644
index 0000000..753dc2c
--- /dev/null
+++ b/ios/chrome/browser/ui/recent_tabs/OWNERS
@@ -0,0 +1,5 @@
+sczs@chromium.org
+gambard@chromium.org
+
+# TEAM: ios-directory-owners@chromium.org
+# OS: iOS
diff --git a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_coordinator.h b/ios/chrome/browser/ui/recent_tabs/recent_tabs_coordinator.h
similarity index 66%
rename from ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_coordinator.h
rename to ios/chrome/browser/ui/recent_tabs/recent_tabs_coordinator.h
index ed4cf374..5d39548 100644
--- a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_coordinator.h
+++ b/ios/chrome/browser/ui/recent_tabs/recent_tabs_coordinator.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 IOS_CHROME_BROWSER_UI_NTP_RECENT_TABS_RECENT_TABS_TABLE_COORDINATOR_H_
-#define IOS_CHROME_BROWSER_UI_NTP_RECENT_TABS_RECENT_TABS_TABLE_COORDINATOR_H_
+#ifndef IOS_CHROME_BROWSER_UI_RECENT_TABS_RECENT_TABS_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_UI_RECENT_TABS_RECENT_TABS_COORDINATOR_H_
 
 #import <UIKit/UIKit.h>
 
@@ -14,11 +14,11 @@
 @protocol UrlLoader;
 
 // Coordinator that presents Recent Tabs.
-@interface RecentTabsTableCoordinator : ChromeCoordinator
+@interface RecentTabsCoordinator : ChromeCoordinator
 // The dispatcher for this Coordinator.
 @property(nonatomic, weak) id<ApplicationCommands, BrowserCommands> dispatcher;
 // URL loader being managed by this Coordinator.
 @property(nonatomic, weak) id<UrlLoader> loader;
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_NTP_RECENT_TABS_RECENT_TABS_TABLE_COORDINATOR_H_
+#endif  // IOS_CHROME_BROWSER_UI_RECENT_TABS_RECENT_TABS_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_coordinator.mm b/ios/chrome/browser/ui/recent_tabs/recent_tabs_coordinator.mm
similarity index 93%
rename from ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_coordinator.mm
rename to ios/chrome/browser/ui/recent_tabs/recent_tabs_coordinator.mm
index 4d7f141..9972ff1 100644
--- a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_coordinator.mm
+++ b/ios/chrome/browser/ui/recent_tabs/recent_tabs_coordinator.mm
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_coordinator.h"
+#import "ios/chrome/browser/ui/recent_tabs/recent_tabs_coordinator.h"
 
 #include "base/ios/block_types.h"
 #include "base/mac/foundation_util.h"
 #import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_view_controller.h"
-#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_mediator.h"
 #import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_table_view_controller.h"
+#import "ios/chrome/browser/ui/recent_tabs/recent_tabs_mediator.h"
 #import "ios/chrome/browser/ui/table_view/table_container_view_controller.h"
 #import "ios/chrome/browser/ui/util/form_sheet_navigation_controller.h"
 #include "ios/chrome/grit/ios_strings.h"
@@ -21,7 +21,7 @@
 // TODO(crbug.com/805135): Remove RecentTabsHandsetViewControllerCommand and
 // recent_tabs_handset_view_controller.h import. We need this to dismiss for
 // now, but it can be improved.
-@interface RecentTabsTableCoordinator ()<RecentTabsHandsetViewControllerCommand>
+@interface RecentTabsCoordinator ()<RecentTabsHandsetViewControllerCommand>
 // Completion block called once the recentTabsViewController is dismissed.
 @property(nonatomic, copy) ProceduralBlock completion;
 // Mediator being managed by this Coordinator.
@@ -31,7 +31,7 @@
     TableContainerViewController* recentTabsContainerViewController;
 @end
 
-@implementation RecentTabsTableCoordinator
+@implementation RecentTabsCoordinator
 @synthesize completion = _completion;
 @synthesize dispatcher = _dispatcher;
 @synthesize loader = _loader;
diff --git a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_mediator.h b/ios/chrome/browser/ui/recent_tabs/recent_tabs_mediator.h
similarity index 86%
rename from ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_mediator.h
rename to ios/chrome/browser/ui/recent_tabs/recent_tabs_mediator.h
index fbc1165..d15e0e0 100644
--- a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_mediator.h
+++ b/ios/chrome/browser/ui/recent_tabs/recent_tabs_mediator.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 IOS_CHROME_BROWSER_UI_NTP_RECENT_TABS_RECENT_TABS_MEDIATOR_H_
-#define IOS_CHROME_BROWSER_UI_NTP_RECENT_TABS_RECENT_TABS_MEDIATOR_H_
+#ifndef IOS_CHROME_BROWSER_UI_RECENT_TABS_RECENT_TABS_MEDIATOR_H_
+#define IOS_CHROME_BROWSER_UI_RECENT_TABS_RECENT_TABS_MEDIATOR_H_
 
 #import <Foundation/Foundation.h>
 
@@ -42,4 +42,4 @@
 @property(nonatomic, assign) ios::ChromeBrowserState* browserState;
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_NTP_RECENT_TABS_RECENT_TABS_MEDIATOR_H_
+#endif  // IOS_CHROME_BROWSER_UI_RECENT_TABS_RECENT_TABS_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_mediator.mm b/ios/chrome/browser/ui/recent_tabs/recent_tabs_mediator.mm
similarity index 98%
rename from ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_mediator.mm
rename to ios/chrome/browser/ui/recent_tabs/recent_tabs_mediator.mm
index 2a030aa1..18b868a 100644
--- a/ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_mediator.mm
+++ b/ios/chrome/browser/ui/recent_tabs/recent_tabs_mediator.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_mediator.h"
+#import "ios/chrome/browser/ui/recent_tabs/recent_tabs_mediator.h"
 
 #include "components/browser_sync/profile_sync_service.h"
 #include "components/sessions/core/tab_restore_service.h"
diff --git a/ios/chrome/browser/ui/tab_grid/BUILD.gn b/ios/chrome/browser/ui/tab_grid/BUILD.gn
index 8cba5359..3b681ae 100644
--- a/ios/chrome/browser/ui/tab_grid/BUILD.gn
+++ b/ios/chrome/browser/ui/tab_grid/BUILD.gn
@@ -55,6 +55,8 @@
     "tab_grid_bottom_toolbar.mm",
     "tab_grid_constants.h",
     "tab_grid_constants.mm",
+    "tab_grid_empty_state_view.h",
+    "tab_grid_empty_state_view.mm",
     "tab_grid_page_control.h",
     "tab_grid_page_control.mm",
     "tab_grid_paging.h",
diff --git a/ios/chrome/browser/ui/tab_grid/grid_cell.mm b/ios/chrome/browser/ui/tab_grid/grid_cell.mm
index b7e4cd7..fa73d72 100644
--- a/ios/chrome/browser/ui/tab_grid/grid_cell.mm
+++ b/ios/chrome/browser/ui/tab_grid/grid_cell.mm
@@ -82,6 +82,15 @@
   // NO-OP to disable highlighting and only allow selection.
 }
 
+- (void)prepareForReuse {
+  [super prepareForReuse];
+  self.itemIdentifier = nil;
+  self.title = nil;
+  self.icon = nil;
+  self.snapshot = nil;
+  self.selected = NO;
+}
+
 #pragma mark - Accessibility
 
 - (BOOL)isAccessibilityElement {
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_constants.h b/ios/chrome/browser/ui/tab_grid/tab_grid_constants.h
index c362575..0acce42 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_constants.h
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_constants.h
@@ -24,6 +24,14 @@
 // The distance between the title and body of the empty state view.
 extern const CGFloat kTabGridEmptyStateVerticalMargin;
 
+// The insets from the edges for empty state.
+extern const CGFloat kTabGridEmptyStateVerticalInset;
+extern const CGFloat kTabGridEmptyStateHorizontalInset;
+
+// The insets from the edges for the floating button.
+extern const CGFloat kTabGridFloatingButtonVerticalInset;
+extern const CGFloat kTabGridFloatingButtonHorizontalInset;
+
 // Intrinsic heights of the tab grid toolbars.
 extern const CGFloat kTabGridTopToolbarHeight;
 extern const CGFloat kTabGridBottomToolbarHeight;
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_constants.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_constants.mm
index 8b8fb31..0734b537 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_constants.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_constants.mm
@@ -21,6 +21,14 @@
 // The distance between the title and body of the empty state view.
 const CGFloat kTabGridEmptyStateVerticalMargin = 4.0f;
 
+// The insets from the edges for empty state.
+extern const CGFloat kTabGridEmptyStateVerticalInset = 17.0f;
+extern const CGFloat kTabGridEmptyStateHorizontalInset = 80.0f;
+
+// The insets from the edges for the floating button.
+const CGFloat kTabGridFloatingButtonVerticalInset = 10.0f;
+const CGFloat kTabGridFloatingButtonHorizontalInset = 10.0f;
+
 // Intrinsic heights of the tab grid toolbars.
 const CGFloat kTabGridTopToolbarHeight = 52.0f;
 const CGFloat kTabGridBottomToolbarHeight = 44.0f;
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_empty_state_view.h b/ios/chrome/browser/ui/tab_grid/tab_grid_empty_state_view.h
new file mode 100644
index 0000000..79c3467d
--- /dev/null
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_empty_state_view.h
@@ -0,0 +1,23 @@
+// 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 IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_EMPTY_STATE_VIEW_H_
+#define IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_EMPTY_STATE_VIEW_H_
+
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/browser/ui/tab_grid/tab_grid_paging.h"
+
+// A view that informs the user that the grid is empty. The displayed
+// text is customized for incognito and regular tabs pages. No text is
+// displayed for the remote tabs page.
+@interface TabGridEmptyStateView : UIView
+// Initializes view with |page|, which changes the displayed text.
+- (instancetype)initWithPage:(TabGridPage)page NS_DESIGNATED_INITIALIZER;
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
+- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_TAB_GRID_TAB_GRID_EMPTY_STATE_VIEW_H_
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_empty_state_view.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_empty_state_view.mm
new file mode 100644
index 0000000..ce2c6fae
--- /dev/null
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_empty_state_view.mm
@@ -0,0 +1,126 @@
+// 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 "ios/chrome/browser/ui/tab_grid/tab_grid_empty_state_view.h"
+
+#import "ios/chrome/browser/ui/tab_grid/tab_grid_constants.h"
+#import "ios/chrome/browser/ui/uikit_ui_util.h"
+#include "ios/chrome/grit/ios_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface TabGridEmptyStateView ()
+@property(nonatomic, copy, readonly) NSString* title;
+@property(nonatomic, copy, readonly) NSString* body;
+@property(nonatomic, strong) NSArray* centeredConstraints;
+@property(nonatomic, strong) NSArray* trailingAlignedConstraints;
+@end
+
+@implementation TabGridEmptyStateView
+@synthesize title = _title;
+@synthesize body = _body;
+@synthesize centeredConstraints = _centeredConstraints;
+@synthesize trailingAlignedConstraints = _trailingAlignedConstraints;
+
+- (instancetype)initWithPage:(TabGridPage)page {
+  if (self = [super initWithFrame:CGRectZero]) {
+    switch (page) {
+      case TabGridPageIncognitoTabs:
+        _title = l10n_util::GetNSString(
+            IDS_IOS_TAB_GRID_INCOGNITO_TABS_EMPTY_STATE_TITLE);
+        _body = l10n_util::GetNSString(
+            IDS_IOS_TAB_GRID_INCOGNITO_TABS_EMPTY_STATE_BODY);
+        break;
+      case TabGridPageRegularTabs:
+        _title = l10n_util::GetNSString(
+            IDS_IOS_TAB_GRID_REGULAR_TABS_EMPTY_STATE_TITLE);
+        _body = l10n_util::GetNSString(
+            IDS_IOS_TAB_GRID_REGULAR_TABS_EMPTY_STATE_BODY);
+        break;
+      case TabGridPageRemoteTabs:
+        // No-op. Empty page.
+        break;
+    }
+  }
+  return self;
+}
+
+#pragma mark - UIView
+
+- (void)willMoveToSuperview:(UIView*)newSuperview {
+  // The first time this moves to a superview, perform the view setup.
+  if (newSuperview && self.subviews.count == 0) {
+    [self setupViews];
+  }
+}
+
+- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
+  [super traitCollectionDidChange:previousTraitCollection];
+  if (self.traitCollection.verticalSizeClass ==
+          UIUserInterfaceSizeClassRegular &&
+      self.traitCollection.horizontalSizeClass ==
+          UIUserInterfaceSizeClassCompact) {
+    // The only centered configuration is when the UI is narrow but
+    // vertically long.
+    [NSLayoutConstraint deactivateConstraints:self.trailingAlignedConstraints];
+    [NSLayoutConstraint activateConstraints:self.centeredConstraints];
+  } else {
+    [NSLayoutConstraint deactivateConstraints:self.centeredConstraints];
+    [NSLayoutConstraint activateConstraints:self.trailingAlignedConstraints];
+  }
+}
+
+#pragma mark - Private
+
+- (void)setupViews {
+  UILabel* topLabel = [[UILabel alloc] init];
+  topLabel.translatesAutoresizingMaskIntoConstraints = NO;
+  topLabel.text = self.title;
+  topLabel.textColor = UIColorFromRGB(kTabGridEmptyStateTitleTextColor);
+  topLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleTitle2];
+  topLabel.adjustsFontForContentSizeCategory = YES;
+  topLabel.numberOfLines = 0;
+  topLabel.textAlignment = NSTextAlignmentCenter;
+  [self addSubview:topLabel];
+  UILabel* bottomLabel = [[UILabel alloc] init];
+  bottomLabel.translatesAutoresizingMaskIntoConstraints = NO;
+  bottomLabel.text = self.body;
+  bottomLabel.textColor = UIColorFromRGB(kTabGridEmptyStateBodyTextColor);
+  bottomLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
+  bottomLabel.adjustsFontForContentSizeCategory = YES;
+  bottomLabel.numberOfLines = 0;
+  bottomLabel.textAlignment = NSTextAlignmentCenter;
+  [self addSubview:bottomLabel];
+
+  self.centeredConstraints = @[
+    [topLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
+    [topLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
+    [topLabel.bottomAnchor
+        constraintEqualToAnchor:self.centerYAnchor
+                       constant:-kTabGridEmptyStateVerticalMargin / 2.0f],
+    [bottomLabel.topAnchor
+        constraintEqualToAnchor:self.centerYAnchor
+                       constant:kTabGridEmptyStateVerticalMargin / 2.0f],
+    [bottomLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
+    [bottomLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
+  ];
+  self.trailingAlignedConstraints = @[
+    [bottomLabel.trailingAnchor
+        constraintEqualToAnchor:self.trailingAnchor
+                       constant:-kTabGridEmptyStateHorizontalInset],
+    [bottomLabel.bottomAnchor
+        constraintEqualToAnchor:self.bottomAnchor
+                       constant:-kTabGridEmptyStateVerticalInset],
+    [bottomLabel.topAnchor
+        constraintEqualToAnchor:topLabel.bottomAnchor
+                       constant:kTabGridEmptyStateVerticalMargin],
+    [bottomLabel.trailingAnchor
+        constraintEqualToAnchor:topLabel.trailingAnchor],
+  ];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
index c164de4..3dbfee5 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
@@ -11,6 +11,7 @@
 #import "ios/chrome/browser/ui/tab_grid/grid_view_controller.h"
 #import "ios/chrome/browser/ui/tab_grid/tab_grid_bottom_toolbar.h"
 #import "ios/chrome/browser/ui/tab_grid/tab_grid_constants.h"
+#import "ios/chrome/browser/ui/tab_grid/tab_grid_empty_state_view.h"
 #import "ios/chrome/browser/ui/tab_grid/tab_grid_page_control.h"
 #import "ios/chrome/browser/ui/tab_grid/tab_grid_top_toolbar.h"
 #import "ios/chrome/browser/ui/uikit_ui_util.h"
@@ -306,11 +307,8 @@
   [self addChildViewController:viewController];
   [contentView addSubview:viewController.view];
   [viewController didMoveToParentViewController:self];
-  int titleStringID = IDS_IOS_TAB_GRID_INCOGNITO_TABS_EMPTY_STATE_TITLE;
-  int bodyStringID = IDS_IOS_TAB_GRID_INCOGNITO_TABS_EMPTY_STATE_BODY;
   viewController.emptyStateView =
-      [self createEmptyStateViewWithTitleStringID:titleStringID
-                                     bodyStringID:bodyStringID];
+      [[TabGridEmptyStateView alloc] initWithPage:TabGridPageIncognitoTabs];
   viewController.theme = GridThemeDark;
   viewController.delegate = self;
   if (@available(iOS 11, *)) {
@@ -341,11 +339,8 @@
   [self addChildViewController:viewController];
   [contentView addSubview:viewController.view];
   [viewController didMoveToParentViewController:self];
-  int titleStringID = IDS_IOS_TAB_GRID_REGULAR_TABS_EMPTY_STATE_TITLE;
-  int bodyStringID = IDS_IOS_TAB_GRID_REGULAR_TABS_EMPTY_STATE_BODY;
   viewController.emptyStateView =
-      [self createEmptyStateViewWithTitleStringID:titleStringID
-                                     bodyStringID:bodyStringID];
+      [[TabGridEmptyStateView alloc] initWithPage:TabGridPageRegularTabs];
   viewController.theme = GridThemeLight;
   viewController.delegate = self;
   if (@available(iOS 11, *)) {
@@ -395,44 +390,6 @@
   [NSLayoutConstraint activateConstraints:constraints];
 }
 
-// Creates an empty state view.
-- (UIView*)createEmptyStateViewWithTitleStringID:(int)titleStringID
-                                    bodyStringID:(int)bodyStringID {
-  UIView* view = [[UIView alloc] init];
-  UILabel* topLabel = [[UILabel alloc] init];
-  topLabel.translatesAutoresizingMaskIntoConstraints = NO;
-  topLabel.text = l10n_util::GetNSString(titleStringID);
-  topLabel.textColor = UIColorFromRGB(kTabGridEmptyStateTitleTextColor);
-  topLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleTitle2];
-  topLabel.adjustsFontForContentSizeCategory = YES;
-  topLabel.numberOfLines = 0;
-  topLabel.textAlignment = NSTextAlignmentCenter;
-  [view addSubview:topLabel];
-  UILabel* bottomLabel = [[UILabel alloc] init];
-  bottomLabel.translatesAutoresizingMaskIntoConstraints = NO;
-  bottomLabel.text = l10n_util::GetNSString(bodyStringID);
-  bottomLabel.textColor = UIColorFromRGB(kTabGridEmptyStateBodyTextColor);
-  bottomLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
-  bottomLabel.adjustsFontForContentSizeCategory = YES;
-  bottomLabel.numberOfLines = 0;
-  bottomLabel.textAlignment = NSTextAlignmentCenter;
-  [view addSubview:bottomLabel];
-  NSArray* constraints = @[
-    [topLabel.leadingAnchor constraintEqualToAnchor:view.leadingAnchor],
-    [topLabel.trailingAnchor constraintEqualToAnchor:view.trailingAnchor],
-    [topLabel.bottomAnchor
-        constraintEqualToAnchor:view.centerYAnchor
-                       constant:-kTabGridEmptyStateVerticalMargin / 2.0f],
-    [bottomLabel.topAnchor
-        constraintEqualToAnchor:view.centerYAnchor
-                       constant:kTabGridEmptyStateVerticalMargin / 2.0f],
-    [bottomLabel.leadingAnchor constraintEqualToAnchor:view.leadingAnchor],
-    [bottomLabel.trailingAnchor constraintEqualToAnchor:view.trailingAnchor],
-  ];
-  [NSLayoutConstraint activateConstraints:constraints];
-  return view;
-}
-
 // Adds the top toolbar and sets constraints.
 - (void)setupTopToolbar {
   TabGridTopToolbar* topToolbar = [[TabGridTopToolbar alloc] init];
@@ -503,10 +460,12 @@
   [self.view addSubview:button];
   self.floatingButton = button;
   NSArray* constraints = @[
-    [button.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor
-                                          constant:-10.0f],
-    [button.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor
-                                        constant:-10.0f]
+    [button.trailingAnchor
+        constraintEqualToAnchor:self.view.trailingAnchor
+                       constant:-kTabGridFloatingButtonHorizontalInset],
+    [button.bottomAnchor
+        constraintEqualToAnchor:self.view.bottomAnchor
+                       constant:-kTabGridFloatingButtonVerticalInset]
   ];
   [NSLayoutConstraint activateConstraints:constraints];
 }
diff --git a/ios/chrome/browser/web/browsing_egtest.mm b/ios/chrome/browser/web/browsing_egtest.mm
index d814551..970b708e6 100644
--- a/ios/chrome/browser/web/browsing_egtest.mm
+++ b/ios/chrome/browser/web/browsing_egtest.mm
@@ -26,6 +26,7 @@
 #include "ios/web/public/test/http_server/data_response_provider.h"
 #import "ios/web/public/test/http_server/http_server.h"
 #include "ios/web/public/test/http_server/http_server_util.h"
+#import "ios/web/public/test/url_test_util.h"
 #import "ios/web/public/web_client.h"
 #include "net/http/http_response_headers.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -228,7 +229,9 @@
   [ChromeEarlGrey waitForMainTabCount:2];
 
   // Verify the new tab was opened with the expected URL.
-  [[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
+  const std::string omniboxText =
+      web::GetContentAndFragmentForUrl(destinationURL);
+  [[EarlGrey selectElementWithMatcher:OmniboxText(omniboxText)]
       assertWithMatcher:grey_notNil()];
 }
 
diff --git a/ios/chrome/browser/web/navigation_egtest.mm b/ios/chrome/browser/web/navigation_egtest.mm
index 7c75f32..4fcc467 100644
--- a/ios/chrome/browser/web/navigation_egtest.mm
+++ b/ios/chrome/browser/web/navigation_egtest.mm
@@ -11,6 +11,7 @@
 #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"
+#import "ios/web/public/test/url_test_util.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -393,7 +394,9 @@
   std::string backHashChangeContent = "backHashChange";
   [self addHashChangeListenerWithContent:backHashChangeContent];
   [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
-  [[EarlGrey selectElementWithMatcher:OmniboxText(page1URL.GetContent())]
+  const std::string page1OmniboxText =
+      web::GetContentAndFragmentForUrl(page1URL);
+  [[EarlGrey selectElementWithMatcher:OmniboxText(page1OmniboxText)]
       assertWithMatcher:grey_notNil()];
   [ChromeEarlGrey waitForWebViewContainingText:backHashChangeContent];
 
@@ -402,9 +405,10 @@
   [self addHashChangeListenerWithContent:forwardHashChangeContent];
   [[EarlGrey selectElementWithMatcher:ForwardButton()]
       performAction:grey_tap()];
+  const std::string hashChangedWithHistoryOmniboxText =
+      web::GetContentAndFragmentForUrl(hashChangedWithHistoryURL);
   [[EarlGrey
-      selectElementWithMatcher:OmniboxText(
-                                   hashChangedWithHistoryURL.GetContent())]
+      selectElementWithMatcher:OmniboxText(hashChangedWithHistoryOmniboxText)]
       assertWithMatcher:grey_notNil()];
   [ChromeEarlGrey waitForWebViewContainingText:forwardHashChangeContent];
 
@@ -432,25 +436,26 @@
   // Tap link to replace the location value.
   GREYAssert(TapWebViewElementWithId(kHashChangeWithoutHistoryLabel),
              @"Failed to tap %s", kHashChangeWithoutHistoryLabel);
-  [[EarlGrey
-      selectElementWithMatcher:OmniboxText(
-                                   hashChangedWithoutHistoryURL.GetContent())]
+  const std::string hashChangedWithoutHistoryOmniboxText =
+      web::GetContentAndFragmentForUrl(hashChangedWithoutHistoryURL);
+  [[EarlGrey selectElementWithMatcher:OmniboxText(
+                                          hashChangedWithoutHistoryOmniboxText)]
       assertWithMatcher:grey_notNil()];
 
   // Tap link to update the location.hash with a new value.
   GREYAssert(TapWebViewElementWithId(kHashChangeWithHistoryLabel),
              @"Failed to tap %s", kHashChangeWithHistoryLabel);
+  const std::string hashChangedWithHistoryOmniboxText =
+      web::GetContentAndFragmentForUrl(hashChangedWithHistoryURL);
   [[EarlGrey
-      selectElementWithMatcher:OmniboxText(
-                                   hashChangedWithHistoryURL.GetContent())]
+      selectElementWithMatcher:OmniboxText(hashChangedWithHistoryOmniboxText)]
       assertWithMatcher:grey_notNil()];
 
   // Navigate back and verify that the URL that replaced window.location
   // has been reached.
   [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
-  [[EarlGrey
-      selectElementWithMatcher:OmniboxText(
-                                   hashChangedWithoutHistoryURL.GetContent())]
+  [[EarlGrey selectElementWithMatcher:OmniboxText(
+                                          hashChangedWithoutHistoryOmniboxText)]
       assertWithMatcher:grey_notNil()];
 }
 
@@ -469,9 +474,10 @@
   // Tap link to update location.hash with a new value.
   GREYAssert(TapWebViewElementWithId(kHashChangeWithHistoryLabel),
              @"Failed to tap %s", kHashChangeWithHistoryLabel);
+  const std::string hashChangedWithHistoryOmniboxText =
+      web::GetContentAndFragmentForUrl(hashChangedWithHistoryURL);
   [[EarlGrey
-      selectElementWithMatcher:OmniboxText(
-                                   hashChangedWithHistoryURL.GetContent())]
+      selectElementWithMatcher:OmniboxText(hashChangedWithHistoryOmniboxText)]
       assertWithMatcher:grey_notNil()];
 
   // Tap link to update location.hash with the same value.
@@ -480,15 +486,16 @@
 
   // Tap back once to return to original URL.
   [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
-  [[EarlGrey selectElementWithMatcher:OmniboxText(page1URL.GetContent())]
+  const std::string page1OmniboxText =
+      web::GetContentAndFragmentForUrl(page1URL);
+  [[EarlGrey selectElementWithMatcher:OmniboxText(page1OmniboxText)]
       assertWithMatcher:grey_notNil()];
 
   // Navigate forward and verify the URL.
   [[EarlGrey selectElementWithMatcher:ForwardButton()]
       performAction:grey_tap()];
   [[EarlGrey
-      selectElementWithMatcher:OmniboxText(
-                                   hashChangedWithHistoryURL.GetContent())]
+      selectElementWithMatcher:OmniboxText(hashChangedWithHistoryOmniboxText)]
       assertWithMatcher:grey_notNil()];
 }
 
diff --git a/ios/chrome/browser/web/push_and_replace_state_navigation_egtest.mm b/ios/chrome/browser/web/push_and_replace_state_navigation_egtest.mm
index 5facf3d..3e7b643 100644
--- a/ios/chrome/browser/web/push_and_replace_state_navigation_egtest.mm
+++ b/ios/chrome/browser/web/push_and_replace_state_navigation_egtest.mm
@@ -13,6 +13,7 @@
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
 #import "ios/web/public/test/http_server/http_server.h"
 #include "ios/web/public/test/http_server/http_server_util.h"
+#import "ios/web/public/test/url_test_util.h"
 #import "ios/web/public/web_client.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -50,52 +51,58 @@
 - (void)testHtml5HistoryPushStateThenGoBackAndForward {
   const GURL pushStateHashWithObjectURL = web::test::HttpServer::MakeUrl(
       "http://ios/testing/data/http_server_files/history.html#pushWithObject");
+  const std::string pushStateHashWithObjectOmniboxText =
+      web::GetContentAndFragmentForUrl(pushStateHashWithObjectURL);
   const GURL pushStateRootPathURL =
       web::test::HttpServer::MakeUrl("http://ios/rootpath");
+  const std::string pushStateRootPathOmniboxText =
+      web::GetContentAndFragmentForUrl(pushStateRootPathURL);
   const GURL pushStatePathSpaceURL =
       web::test::HttpServer::MakeUrl("http://ios/pa%20th");
+  const std::string pushStatePathSpaceOmniboxText =
+      web::GetContentAndFragmentForUrl(pushStatePathSpaceURL);
   web::test::SetUpFileBasedHttpServer();
   [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(kHistoryTestUrl)];
 
   // Push 3 URLs. Verify that the URL changed and the status was updated.
   [ChromeEarlGrey tapWebViewElementWithID:@"pushStateHashWithObject"];
   [self assertStatusText:@"pushStateHashWithObject"
-         withOmniboxText:pushStateHashWithObjectURL.GetContent()
+         withOmniboxText:pushStateHashWithObjectOmniboxText
               pageLoaded:NO];
 
   [ChromeEarlGrey tapWebViewElementWithID:@"pushStateRootPath"];
   [self assertStatusText:@"pushStateRootPath"
-         withOmniboxText:pushStateRootPathURL.GetContent()
+         withOmniboxText:pushStateRootPathOmniboxText
               pageLoaded:NO];
 
   [ChromeEarlGrey tapWebViewElementWithID:@"pushStatePathSpace"];
   [self assertStatusText:@"pushStatePathSpace"
-         withOmniboxText:pushStatePathSpaceURL.GetContent()
+         withOmniboxText:pushStatePathSpaceOmniboxText
               pageLoaded:NO];
 
   // Go back and check that the page doesn't load and the status text is updated
   // by the popstate event.
   [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
   [self assertStatusText:@"pushStateRootPath"
-         withOmniboxText:pushStateRootPathURL.GetContent()
+         withOmniboxText:pushStateRootPathOmniboxText
               pageLoaded:NO];
 
   [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
   [self assertStatusText:@"pushStateHashWithObject"
-         withOmniboxText:pushStateHashWithObjectURL.GetContent()
+         withOmniboxText:pushStateHashWithObjectOmniboxText
               pageLoaded:NO];
 
   [ChromeEarlGrey tapWebViewElementWithID:@"goBack"];
   const GURL historyTestURL = web::test::HttpServer::MakeUrl(kHistoryTestUrl);
   [self assertStatusText:nil
-         withOmniboxText:historyTestURL.GetContent()
+         withOmniboxText:web::GetContentAndFragmentForUrl(historyTestURL)
               pageLoaded:NO];
 
   // Go forward 2 pages and check that the page doesn't load and the status text
   // is updated by the popstate event.
   [ChromeEarlGrey tapWebViewElementWithID:@"goForward2"];
   [self assertStatusText:@"pushStateRootPath"
-         withOmniboxText:pushStateRootPathURL.GetContent()
+         withOmniboxText:pushStateRootPathOmniboxText
               pageLoaded:NO];
 }
 
@@ -103,20 +110,24 @@
 - (void)testHtml5HistoryReplaceStateThenGoBackAndForward {
   web::test::SetUpFileBasedHttpServer();
   const GURL initialURL = web::test::HttpServer::MakeUrl(kNonPushedUrl);
+  const std::string initialOmniboxText =
+      web::GetContentAndFragmentForUrl(initialURL);
   [ChromeEarlGrey loadURL:initialURL];
   [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(kHistoryTestUrl)];
 
   // Replace the URL and go back then forward.
   const GURL replaceStateHashWithObjectURL =
       web::test::HttpServer::MakeUrl(kReplaceStateHashWithObjectURL);
+  const std::string replaceStateHashWithObjectOmniboxText =
+      web::GetContentAndFragmentForUrl(replaceStateHashWithObjectURL);
   [ChromeEarlGrey tapWebViewElementWithID:@"replaceStateHashWithObject"];
   [self assertStatusText:@"replaceStateHashWithObject"
-         withOmniboxText:replaceStateHashWithObjectURL.GetContent()
+         withOmniboxText:replaceStateHashWithObjectOmniboxText
               pageLoaded:NO];
 
   [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
   [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(
-                                          initialURL.GetContent())]
+                                          initialOmniboxText)]
       assertWithMatcher:grey_notNil()];
 
   [[EarlGrey selectElementWithMatcher:ForwardButton()]
@@ -125,52 +136,60 @@
   // navigation and WKBasedNavigationManager inherits this behavior.
   bool expectOnLoad = !web::GetWebClient()->IsSlimNavigationManagerEnabled();
   [self assertStatusText:@"replaceStateHashWithObject"
-         withOmniboxText:replaceStateHashWithObjectURL.GetContent()
+         withOmniboxText:replaceStateHashWithObjectOmniboxText
               pageLoaded:expectOnLoad];
 
   // Push URL then replace it. Do this twice.
   const GURL pushStateHashStringURL =
       web::test::HttpServer::MakeUrl(kPushStateHashStringURL);
+  const std::string pushStateHashStringOmniboxText =
+      web::GetContentAndFragmentForUrl(pushStateHashStringURL);
   [ChromeEarlGrey tapWebViewElementWithID:@"pushStateHashString"];
   [self assertStatusText:@"pushStateHashString"
-         withOmniboxText:pushStateHashStringURL.GetContent()
+         withOmniboxText:pushStateHashStringOmniboxText
               pageLoaded:NO];
 
   const GURL replaceStateHashStringURL =
       web::test::HttpServer::MakeUrl(kReplaceStateHashStringURL);
+  const std::string replaceStateHashStringOmniboxText =
+      web::GetContentAndFragmentForUrl(replaceStateHashStringURL);
   [ChromeEarlGrey tapWebViewElementWithID:@"replaceStateHashString"];
   [self assertStatusText:@"replaceStateHashString"
-         withOmniboxText:replaceStateHashStringURL.GetContent()
+         withOmniboxText:replaceStateHashStringOmniboxText
               pageLoaded:NO];
 
   const GURL pushStatePathURL =
       web::test::HttpServer::MakeUrl(kPushStatePathURL);
+  const std::string pushStatePathOmniboxText =
+      web::GetContentAndFragmentForUrl(pushStatePathURL);
   [ChromeEarlGrey tapWebViewElementWithID:@"pushStatePath"];
   [self assertStatusText:@"pushStatePath"
-         withOmniboxText:pushStatePathURL.GetContent()
+         withOmniboxText:pushStatePathOmniboxText
               pageLoaded:NO];
 
   const GURL replaceStateRootPathSpaceURL =
       web::test::HttpServer::MakeUrl(kReplaceStateRootPathSpaceURL);
+  const std::string replaceStateRootPathSpaceOmniboxText =
+      web::GetContentAndFragmentForUrl(replaceStateRootPathSpaceURL);
   [ChromeEarlGrey tapWebViewElementWithID:@"replaceStateRootPathSpace"];
   [self assertStatusText:@"replaceStateRootPathSpace"
-         withOmniboxText:replaceStateRootPathSpaceURL.GetContent()
+         withOmniboxText:replaceStateRootPathSpaceOmniboxText
               pageLoaded:NO];
 
   // Go back and check URLs.
   [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
   [self assertStatusText:@"replaceStateHashString"
-         withOmniboxText:replaceStateHashStringURL.GetContent()
+         withOmniboxText:replaceStateHashStringOmniboxText
               pageLoaded:NO];
   [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
   [self assertStatusText:@"replaceStateHashWithObject"
-         withOmniboxText:replaceStateHashWithObjectURL.GetContent()
+         withOmniboxText:replaceStateHashWithObjectOmniboxText
               pageLoaded:NO];
 
   // Go forward and check URL.
   [ChromeEarlGrey tapWebViewElementWithID:@"goForward2"];
   [self assertStatusText:@"replaceStateRootPathSpace"
-         withOmniboxText:replaceStateRootPathSpaceURL.GetContent()
+         withOmniboxText:replaceStateRootPathSpaceOmniboxText
               pageLoaded:NO];
 }
 
@@ -180,17 +199,21 @@
   web::test::SetUpFileBasedHttpServer();
   const GURL historyTestURL = web::test::HttpServer::MakeUrl(kHistoryTestUrl);
   [ChromeEarlGrey loadURL:historyTestURL];
+  const std::string historyTestOmniboxText =
+      web::GetContentAndFragmentForUrl(historyTestURL);
 
   // Push same URL twice. Verify that URL changed and the status was updated.
   const GURL pushStateHashStringURL =
       web::test::HttpServer::MakeUrl(kPushStateHashStringURL);
+  const std::string pushStateHashStringOmniboxText =
+      web::GetContentAndFragmentForUrl(pushStateHashStringURL);
   [ChromeEarlGrey tapWebViewElementWithID:@"pushStateHashString"];
   [self assertStatusText:@"pushStateHashString"
-         withOmniboxText:pushStateHashStringURL.GetContent()
+         withOmniboxText:pushStateHashStringOmniboxText
               pageLoaded:NO];
   [ChromeEarlGrey tapWebViewElementWithID:@"pushStateHashString"];
   [self assertStatusText:@"pushStateHashString"
-         withOmniboxText:pushStateHashStringURL.GetContent()
+         withOmniboxText:pushStateHashStringOmniboxText
               pageLoaded:NO];
 
   // Load a non-pushed URL.
@@ -200,7 +223,7 @@
   [ChromeEarlGrey loadURL:historyTestURL];
   [ChromeEarlGrey tapWebViewElementWithID:@"pushStateHashString"];
   [self assertStatusText:@"pushStateHashString"
-         withOmniboxText:pushStateHashStringURL.GetContent()
+         withOmniboxText:pushStateHashStringOmniboxText
               pageLoaded:NO];
 
   // At this point the history looks like this:
@@ -209,7 +232,7 @@
   // Go back (to second history.html) and verify page did not load.
   [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
   [self assertStatusText:nil
-         withOmniboxText:historyTestURL.GetContent()
+         withOmniboxText:historyTestOmniboxText
               pageLoaded:NO];
 
   // Go back twice (to second #string) and verify page did load.
@@ -219,27 +242,27 @@
   // navigation and WKBasedNavigationManager inherits this behavior.
   bool expectOnLoad = !web::GetWebClient()->IsSlimNavigationManagerEnabled();
   [self assertStatusText:nil
-         withOmniboxText:pushStateHashStringURL.GetContent()
+         withOmniboxText:pushStateHashStringOmniboxText
               pageLoaded:expectOnLoad];
 
   // Go back once (to first #string) and verify page did not load.
   [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
   [self assertStatusText:@"pushStateHashString"
-         withOmniboxText:pushStateHashStringURL.GetContent()
+         withOmniboxText:pushStateHashStringOmniboxText
               pageLoaded:NO];
 
   // Go forward 4 entries at once (to third #string) and verify page did load.
   [ChromeEarlGrey tapWebViewElementWithID:@"goForward4"];
 
   [self assertStatusText:nil
-         withOmniboxText:pushStateHashStringURL.GetContent()
+         withOmniboxText:pushStateHashStringOmniboxText
               pageLoaded:expectOnLoad];
 
   // Go back 4 entries at once (to first #string) and verify page did load.
   [ChromeEarlGrey tapWebViewElementWithID:@"goBack4"];
 
   [self assertStatusText:nil
-         withOmniboxText:pushStateHashStringURL.GetContent()
+         withOmniboxText:pushStateHashStringOmniboxText
               pageLoaded:expectOnLoad];
 }
 
@@ -248,16 +271,14 @@
   // The GURL object %-escapes Unicode characters in the URL's fragment,
   // but the omnibox decodes them back to Unicode for display.
   std::string pushStateUnicode =
-      web::test::HttpServer::MakeUrl(
+      web::GetContentAndFragmentForUrl(web::test::HttpServer::MakeUrl(
           "http://ios/testing/data/http_server_files/"
-          "history.html#unicode")
-          .GetContent() +
+          "history.html#unicode")) +
       "\xe1\x84\x91";
   std::string pushStateUnicode2 =
-      web::test::HttpServer::MakeUrl(
+      web::GetContentAndFragmentForUrl(web::test::HttpServer::MakeUrl(
           "http://ios/testing/data/http_server_files/"
-          "history.html#unicode2")
-          .GetContent() +
+          "history.html#unicode2")) +
       "\xe2\x88\xa2";
   const char pushStateUnicodeLabel[] = "Action: pushStateUnicodeá„‘";
   NSString* pushStateUnicodeStatus = @"pushStateUnicodeá„‘";
@@ -283,10 +304,12 @@
   // Do a push state without a unicode character.
   const GURL pushStatePathURL =
       web::test::HttpServer::MakeUrl(kPushStatePathURL);
-  [ChromeEarlGrey tapWebViewElementWithID:@"pushStatePath"];
+  const std::string pushStatePathOmniboxText =
+      web::GetContentAndFragmentForUrl(pushStatePathURL);
 
+  [ChromeEarlGrey tapWebViewElementWithID:@"pushStatePath"];
   [self assertStatusText:@"pushStatePath"
-         withOmniboxText:pushStatePathURL.GetContent()
+         withOmniboxText:pushStatePathOmniboxText
               pageLoaded:NO];
 
   // Go back and check the unicode in the URL and status.
@@ -307,8 +330,12 @@
   GURL originURL =
       web::test::HttpServer::MakeUrl("http://foo.com/foo/bar.html");
   GURL pushResultURL = originURL.GetOrigin().Resolve("pushed/relative/url");
+  const std::string pushResultOmniboxText =
+      web::GetContentAndFragmentForUrl(pushResultURL);
   GURL replaceResultURL =
       originURL.GetOrigin().Resolve("replaced/relative/url");
+  const std::string replaceResultOmniboxText =
+      web::GetContentAndFragmentForUrl(replaceResultURL);
 
   // A simple HTML page with a base tag that makes all relative URLs
   // domain-relative, a button to trigger a relative pushState, and a button
@@ -330,12 +357,12 @@
   [ChromeEarlGrey loadURL:originURL];
   [ChromeEarlGrey tapWebViewElementWithID:@"pushState"];
   [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(
-                                          pushResultURL.GetContent())]
+                                          pushResultOmniboxText)]
       assertWithMatcher:grey_notNil()];
 
   [ChromeEarlGrey tapWebViewElementWithID:@"replaceState"];
   [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(
-                                          replaceResultURL.GetContent())]
+                                          replaceResultOmniboxText)]
       assertWithMatcher:grey_notNil()];
 }
 
diff --git a/ios/chrome/browser/web/window_open_by_dom_egtest.mm b/ios/chrome/browser/web/window_open_by_dom_egtest.mm
index c51b4ace6..415c4cd 100644
--- a/ios/chrome/browser/web/window_open_by_dom_egtest.mm
+++ b/ios/chrome/browser/web/window_open_by_dom_egtest.mm
@@ -19,6 +19,7 @@
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
 #import "ios/web/public/test/http_server/http_server.h"
 #include "ios/web/public/test/http_server/http_server_util.h"
+#import "ios/web/public/test/url_test_util.h"
 #include "ui/base/l10n/l10n_util.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -138,7 +139,9 @@
   // Ensure that the resulting tab is updated as expected.
   const GURL targetURL =
       HttpServer::MakeUrl(std::string(kTestURL) + "#assigned");
-  [[EarlGrey selectElementWithMatcher:OmniboxText(targetURL.GetContent())]
+  const std::string targetOmniboxText =
+      web::GetContentAndFragmentForUrl(targetURL);
+  [[EarlGrey selectElementWithMatcher:OmniboxText(targetOmniboxText)]
       assertWithMatcher:grey_notNil()];
 }
 
@@ -153,7 +156,9 @@
   // Ensure that the resulting tab is updated as expected.
   const GURL targetURL =
       HttpServer::MakeUrl(std::string(kTestURL) + "#updated");
-  [[EarlGrey selectElementWithMatcher:OmniboxText(targetURL.GetContent())]
+  const std::string targetOmniboxText =
+      web::GetContentAndFragmentForUrl(targetURL);
+  [[EarlGrey selectElementWithMatcher:OmniboxText(targetOmniboxText)]
       assertWithMatcher:grey_notNil()];
 }
 
diff --git a/ios/net/protocol_handler_util_unittest.mm b/ios/net/protocol_handler_util_unittest.mm
index 10992f0a..8851be6 100644
--- a/ios/net/protocol_handler_util_unittest.mm
+++ b/ios/net/protocol_handler_util_unittest.mm
@@ -164,8 +164,8 @@
 TEST_F(ProtocolHandlerUtilTest, GetResponseDataSchemeTest) {
   NSURLResponse* response;
   // MIME type and charset are correctly carried over.
-  response = BuildDataURLResponse("#mime=type'", "$(charset-*", "content");
-  CheckDataResponse(response, "#mime=type'", "$(charset-*");
+  response = BuildDataURLResponse("?mime=type'", "$(charset-*", "content");
+  CheckDataResponse(response, "?mime=type'", "$(charset-*");
   // Missing values are treated as default values.
   response = BuildDataURLResponse("", "", "content");
   CheckDataResponse(response, kTextPlain, kAscii);
diff --git a/ios/web/download/download_task_impl.h b/ios/web/download/download_task_impl.h
index 703f764..eb0944e 100644
--- a/ios/web/download/download_task_impl.h
+++ b/ios/web/download/download_task_impl.h
@@ -72,6 +72,7 @@
   std::string GetMimeType() const override;
   ui::PageTransition GetTransitionType() const override;
   base::string16 GetSuggestedFilename() const override;
+  bool HasPerformedBackgroundDownload() const override;
   void AddObserver(DownloadTaskObserver* observer) override;
   void RemoveObserver(DownloadTaskObserver* observer) override;
   ~DownloadTaskImpl() override;
@@ -115,12 +116,16 @@
   std::string content_disposition_;
   std::string mime_type_;
   ui::PageTransition page_transition_ = ui::PAGE_TRANSITION_LINK;
+  bool has_performed_background_download_ = false;
 
   const WebState* web_state_ = nullptr;
   Delegate* delegate_ = nullptr;
   NSURLSession* session_ = nil;
   NSURLSessionTask* session_task_ = nil;
 
+  // Observes UIApplicationWillResignActiveNotification notifications.
+  id<NSObject> observer_ = nil;
+
   base::WeakPtrFactory<DownloadTaskImpl> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(DownloadTaskImpl);
diff --git a/ios/web/download/download_task_impl.mm b/ios/web/download/download_task_impl.mm
index 15341db..8e4c675 100644
--- a/ios/web/download/download_task_impl.mm
+++ b/ios/web/download/download_task_impl.mm
@@ -178,10 +178,21 @@
   DCHECK(web_state_);
   DCHECK(delegate_);
   DCHECK(session_);
+
+  observer_ = [NSNotificationCenter.defaultCenter
+      addObserverForName:UIApplicationWillResignActiveNotification
+                  object:nil
+                   queue:nil
+              usingBlock:^(NSNotification* _Nonnull) {
+                if (state_ == State::kInProgress) {
+                  has_performed_background_download_ = true;
+                }
+              }];
 }
 
 DownloadTaskImpl::~DownloadTaskImpl() {
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
+  [NSNotificationCenter.defaultCenter removeObserver:observer_];
   for (auto& observer : observers_)
     observer.OnDownloadDestroyed(this);
 
@@ -290,6 +301,10 @@
                                    /*default_name=*/"document");
 }
 
+bool DownloadTaskImpl::HasPerformedBackgroundDownload() const {
+  return has_performed_background_download_;
+}
+
 void DownloadTaskImpl::AddObserver(DownloadTaskObserver* observer) {
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
   DCHECK(!observers_.HasObserver(observer));
@@ -386,6 +401,10 @@
   DCHECK_CURRENTLY_ON(web::WebThread::UI);
   DCHECK(writer_);
 
+  has_performed_background_download_ =
+      UIApplication.sharedApplication.applicationState !=
+      UIApplicationStateActive;
+
   NSURL* url = net::NSURLWithGURL(GetOriginalUrl());
   session_task_ = [session_ dataTaskWithURL:url];
   [session_.configuration.HTTPCookieStorage storeCookies:cookies
diff --git a/ios/web/public/download/download_task.h b/ios/web/public/download/download_task.h
index ffb23bb..189a419 100644
--- a/ios/web/public/download/download_task.h
+++ b/ios/web/public/download/download_task.h
@@ -102,6 +102,10 @@
   // Suggested name for the downloaded file.
   virtual base::string16 GetSuggestedFilename() const = 0;
 
+  // Returns true if the last download operation was fully or partially
+  // performed while the application was not active.
+  virtual bool HasPerformedBackgroundDownload() const = 0;
+
   // Adds and Removes DownloadTaskObserver. Clients must remove self from
   // observers before the task is destroyed.
   virtual void AddObserver(DownloadTaskObserver* observer) = 0;
diff --git a/ios/web/public/test/BUILD.gn b/ios/web/public/test/BUILD.gn
index 5e8d8dd..3ea06866 100644
--- a/ios/web/public/test/BUILD.gn
+++ b/ios/web/public/test/BUILD.gn
@@ -6,43 +6,61 @@
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
 
+  public_deps = [
+    ":util",
+  ]
+
   deps = [
     "//base",
     "//base/test:test_support",
     "//ios/testing:ios_test_support",
     "//ios/web:web",
-    "//ios/web/interstitials",
-    "//ios/web/navigation:core",
     "//ios/web/public/test/fakes",
-    "//ios/web/public/test/http_server",
     "//ios/web/test:test_support",
     "//testing/gtest",
     "//ui/base",
-    "//url",
   ]
 
   allow_circular_includes_from = [ "//ios/web/test:test_support" ]
 
   sources = [
+    "scoped_testing_web_client.h",
+    "scoped_testing_web_client.mm",
+    "test_redirect_observer.h",
+    "test_redirect_observer.mm",
+    "test_web_thread.h",
+    "test_web_thread_bundle.h",
+    "web_js_test.h",
+    "web_test.h",
+    "web_test.mm",
+    "web_test_suite.h",
+    "web_test_with_web_state.h",
+    "web_test_with_web_state.mm",
+  ]
+}
+
+source_set("util") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  testonly = true
+
+  deps = [
+    "//base",
+    "//base/test:test_support",
+    "//ios/testing:ios_test_support",
+    "//ios/web/public:public",
+    "//ios/web/web_state:web_state_impl_header",
+    "//ios/web/web_state/ui:ui",
+    "//testing/gtest",
+  ]
+
+  sources = [
     "js_test_util.h",
     "js_test_util.mm",
     "native_controller_test_util.h",
     "native_controller_test_util.mm",
     "navigation_test_util.h",
     "navigation_test_util.mm",
-    "scoped_testing_web_client.h",
-    "scoped_testing_web_client.mm",
-    "test_redirect_observer.h",
-    "test_redirect_observer.mm",
-    "test_web_thread.h",
-    "test_web_thread_bundle.h",
     "url_test_util.h",
-    "web_js_test.h",
-    "web_test.h",
-    "web_test.mm",
-    "web_test_suite.h",
-    "web_test_with_web_state.h",
-    "web_test_with_web_state.mm",
     "web_view_content_test_util.h",
     "web_view_content_test_util.mm",
     "web_view_interaction_test_util.h",
diff --git a/ios/web/public/test/fakes/fake_download_task.h b/ios/web/public/test/fakes/fake_download_task.h
index c4d6ae4..23601b1 100644
--- a/ios/web/public/test/fakes/fake_download_task.h
+++ b/ios/web/public/test/fakes/fake_download_task.h
@@ -37,6 +37,7 @@
   std::string GetMimeType() const override;
   ui::PageTransition GetTransitionType() const override;
   base::string16 GetSuggestedFilename() const override;
+  bool HasPerformedBackgroundDownload() const override;
   void AddObserver(DownloadTaskObserver* observer) override;
   void RemoveObserver(DownloadTaskObserver* observer) override;
 
@@ -51,6 +52,7 @@
   void SetMimeType(const std::string& mime_type);
   void SetTransitionType(ui::PageTransition page_transition);
   void SetSuggestedFilename(const base::string16& suggested_file_name);
+  void SetPerformedBackgroundDownload(bool flag);
 
  private:
   // Called when download task was updated.
@@ -70,6 +72,7 @@
   std::string mime_type_;
   ui::PageTransition page_transition_ = ui::PAGE_TRANSITION_LINK;
   base::string16 suggested_file_name_;
+  bool has_performed_background_download_ = false;
   __strong NSString* identifier_ = nil;
 
   DISALLOW_COPY_AND_ASSIGN(FakeDownloadTask);
diff --git a/ios/web/public/test/fakes/fake_download_task.mm b/ios/web/public/test/fakes/fake_download_task.mm
index 89cf90d..e54bffd0 100644
--- a/ios/web/public/test/fakes/fake_download_task.mm
+++ b/ios/web/public/test/fakes/fake_download_task.mm
@@ -90,6 +90,10 @@
   return suggested_file_name_;
 }
 
+bool FakeDownloadTask::HasPerformedBackgroundDownload() const {
+  return has_performed_background_download_;
+}
+
 void FakeDownloadTask::AddObserver(DownloadTaskObserver* observer) {
   DCHECK(!observers_.HasObserver(observer));
   observers_.AddObserver(observer);
@@ -152,6 +156,10 @@
   OnDownloadUpdated();
 }
 
+void FakeDownloadTask::SetPerformedBackgroundDownload(bool flag) {
+  has_performed_background_download_ = flag;
+}
+
 void FakeDownloadTask::OnDownloadUpdated() {
   for (auto& observer : observers_)
     observer.OnDownloadUpdated(this);
diff --git a/ios/web/public/test/http_server/BUILD.gn b/ios/web/public/test/http_server/BUILD.gn
index 6effcdc..bb85b48 100644
--- a/ios/web/public/test/http_server/BUILD.gn
+++ b/ios/web/public/test/http_server/BUILD.gn
@@ -9,6 +9,7 @@
   deps = [
     "//base",
     "//base/test:test_support",
+    "//ios/web/public/test:util",
     "//net",
     "//net:test_support",
     "//url",
diff --git a/ios/web/public/test/http_server/http_server.mm b/ios/web/public/test/http_server/http_server.mm
index d59145d..7a7474d 100644
--- a/ios/web/public/test/http_server/http_server.mm
+++ b/ios/web/public/test/http_server/http_server.mm
@@ -12,6 +12,7 @@
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/sys_string_conversions.h"
+#import "ios/web/public/test/url_test_util.h"
 #import "net/base/mac/url_conversions.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
@@ -154,7 +155,8 @@
 GURL HttpServer::MakeUrlForHttpServer(const std::string& url) const {
   GURL result(url);
   DCHECK(result.is_valid());
-  return embedded_test_server_->GetURL("/" + result.GetContent());
+  return embedded_test_server_->GetURL(
+      "/" + web::GetContentAndFragmentForUrl(result));
 }
 
 scoped_refptr<RefCountedResponseProviderWrapper>
diff --git a/ios/web/public/test/url_test_util.h b/ios/web/public/test/url_test_util.h
index ac159c6..f5c8390 100644
--- a/ios/web/public/test/url_test_util.h
+++ b/ios/web/public/test/url_test_util.h
@@ -15,6 +15,13 @@
 // for a page with that URL.
 base::string16 GetDisplayTitleForUrl(const GURL& url);
 
+// Returns the content and frament of |url| concatenated together. For example:
+//
+//  http://www.example.com/some/content.html?param1=foo#fragment_data
+//
+//  Returns "www.example.com/some/content.html?param1=foo#fragment_data".
+std::string GetContentAndFragmentForUrl(const GURL& url);
+
 }  // namespace web
 
 #endif  // IOS_WEB_PUBLIC_TEST_URL_TEST_UTIL_H_
diff --git a/ios/web/public/web_state/navigation_context.h b/ios/web/public/web_state/navigation_context.h
index 1269be9..336bc03a 100644
--- a/ios/web/public/web_state/navigation_context.h
+++ b/ios/web/public/web_state/navigation_context.h
@@ -47,8 +47,9 @@
   virtual bool IsSameDocument() const = 0;
 
   // Whether the navigation has committed. Navigations that end up being
-  // downloads or return 204/205 response codes do not commit (i.e. the
-  // WebState stays at the existing URL).
+  // downloads, return 204/205 response codes or their response is rejected by
+  // the policy decider do not commit (i.e. the WebState stays at the existing
+  // URL).
   // This returns true for either successful commits or error pages that
   // replace the previous page, and false for errors that leave the user on the
   // previous page.
diff --git a/ios/web/test/BUILD.gn b/ios/web/test/BUILD.gn
index ae9bd25..dab429a 100644
--- a/ios/web/test/BUILD.gn
+++ b/ios/web/test/BUILD.gn
@@ -59,6 +59,7 @@
     "//ios/testing:ios_test_support",
     "//ios/web",
     "//ios/web/navigation:core",
+    "//ios/web/public/test:util",
     "//ios/web/public/test/fakes",
     "//ios/web/public/test/http_server",
     "//ios/web/test/fakes",
diff --git a/ios/web/test/url_test_util.mm b/ios/web/test/url_test_util.mm
index f0ed6343..c2a2b8a 100644
--- a/ios/web/test/url_test_util.mm
+++ b/ios/web/test/url_test_util.mm
@@ -16,4 +16,8 @@
   return NavigationItemImpl::GetDisplayTitleForURL(url);
 }
 
+std::string GetContentAndFragmentForUrl(const GURL& url) {
+  return url.GetContent() + (url.has_ref() ? "#" + url.ref() : "");
+}
+
 }  // namespace web
diff --git a/ios/web/web_state/navigation_and_load_callbacks_inttest.mm b/ios/web/web_state/navigation_and_load_callbacks_inttest.mm
index ad0d9a67..4d548607 100644
--- a/ios/web/web_state/navigation_and_load_callbacks_inttest.mm
+++ b/ios/web/web_state/navigation_and_load_callbacks_inttest.mm
@@ -144,6 +144,35 @@
   EXPECT_EQ(url, item->GetURL());
 }
 
+// Verifies correctness of |NavigationContext| (|arg1|) passed to
+// |DidFinishNavigation| for navigation canceled due to a rejected response.
+// Asserts that |NavigationContext| the same as |context|.
+ACTION_P4(VerifyResponseRejectedFinishedContext,
+          web_state,
+          url,
+          context,
+          nav_id) {
+  ASSERT_EQ(*context, arg1);
+  EXPECT_EQ(web_state, arg0);
+  ASSERT_TRUE((*context));
+  EXPECT_EQ(web_state, (*context)->GetWebState());
+  EXPECT_EQ(*nav_id, (*context)->GetNavigationId());
+  EXPECT_EQ(url, (*context)->GetUrl());
+  EXPECT_TRUE(
+      PageTransitionCoreTypeIs(ui::PageTransition::PAGE_TRANSITION_TYPED,
+                               (*context)->GetPageTransition()));
+  EXPECT_FALSE((*context)->IsSameDocument());
+  // When the response is rejected discard non committed items is called and
+  // no item should be committed.
+  EXPECT_FALSE((*context)->HasCommitted());
+  EXPECT_FALSE((*context)->IsDownload());
+  EXPECT_FALSE((*context)->IsPost());
+  EXPECT_FALSE((*context)->GetError());
+  EXPECT_FALSE((*context)->IsRendererInitiated());
+  EXPECT_FALSE((*context)->GetResponseHeaders());
+  ASSERT_FALSE(web_state->IsLoading());
+}
+
 // Verifies correctness of |NavigationContext| (|arg1|) for navigations via POST
 // HTTP methods passed to |DidStartNavigation|. Stores |NavigationContext| in
 // |context| pointer.
@@ -1234,8 +1263,10 @@
           &nav_id));
   EXPECT_CALL(*decider_, ShouldAllowResponse(_, /*for_main_frame=*/true))
       .WillOnce(Return(false));
-  // TODO(crbug.com/809557): DidFinishNavigation should be called.
   EXPECT_CALL(observer_, DidStopLoading(web_state()));
+  EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
+      .WillOnce(VerifyResponseRejectedFinishedContext(web_state(), url,
+                                                      &context, &nav_id));
   web::test::LoadUrl(web_state(), test_server_->GetURL("/echo"));
   EXPECT_TRUE(WaitUntilConditionOrTimeout(testing::kWaitForPageLoadTimeout, ^{
     return !web_state()->IsLoading();
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 0017628..2498b94 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -880,7 +880,8 @@
           forNavigation:(WKNavigation*)navigation;
 
 // Handles cancelled load in WKWebView (error with NSURLErrorCancelled code).
-- (void)handleCancelledError:(NSError*)error;
+- (void)handleCancelledError:(NSError*)error
+               forNavigation:(WKNavigation*)navigation;
 
 // Used to decide whether a load that generates errors with the
 // NSURLErrorCancelled code should be cancelled.
@@ -1975,6 +1976,7 @@
 }
 
 - (void)loadCancelled {
+  // TODO(crbug.com/821995):  Check if this function should be removed.
   if (_loadPhase != web::PAGE_LOADED) {
     _loadPhase = web::PAGE_LOADED;
     if (!_isHalted) {
@@ -2872,6 +2874,8 @@
       }
     }
 
+    // TODO(crbug.com/820201): Launching External Applications shouldn't happen
+    // here.
     // External application launcher needs |isNavigationTypeLinkActivated| to
     // decide if the user intended to open the application by clicking on a
     // link.
@@ -2911,7 +2915,7 @@
     return;
 
   if (error.code == NSURLErrorCancelled) {
-    [self handleCancelledError:error];
+    [self handleCancelledError:error forNavigation:navigation];
     // NSURLErrorCancelled errors that aren't handled by aborting the load will
     // automatically be retried by the web view, so early return in this case.
     return;
@@ -3016,7 +3020,8 @@
   [self loadCompleteWithSuccess:NO forNavigation:navigation];
 }
 
-- (void)handleCancelledError:(NSError*)error {
+- (void)handleCancelledError:(NSError*)error
+               forNavigation:(WKNavigation*)navigation {
   if ([self shouldCancelLoadForCancelledError:error]) {
     [self loadCancelled];
     self.navigationManagerImpl->DiscardNonCommittedItems();
@@ -3028,6 +3033,13 @@
         [self loadCurrentURLInNativeView];
       }
     }
+    web::NavigationContextImpl* navigationContext =
+        [_navigationStates contextForNavigation:navigation];
+
+    if ([_navigationStates stateForNavigation:navigation] ==
+        web::WKNavigationState::PROVISIONALY_FAILED) {
+      _webStateImpl->OnNavigationFinished(navigationContext);
+    }
   }
 }
 
@@ -4442,7 +4454,7 @@
   // Handle load cancellation for directly cancelled navigations without
   // handling their potential errors. Otherwise, handle the error.
   if ([_pendingNavigationInfo cancelled]) {
-    [self handleCancelledError:error];
+    [self handleCancelledError:error forNavigation:navigation];
   } else {
     error = WKWebViewErrorWithSource(error, PROVISIONAL_LOAD);
 
diff --git a/ios/web_view/internal/translate/cwv_translation_controller_unittest.mm b/ios/web_view/internal/translate/cwv_translation_controller_unittest.mm
index 82de2cd..c187ea1 100644
--- a/ios/web_view/internal/translate/cwv_translation_controller_unittest.mm
+++ b/ios/web_view/internal/translate/cwv_translation_controller_unittest.mm
@@ -23,6 +23,7 @@
 #import "testing/gtest_mac.h"
 #include "testing/platform_test.h"
 #import "third_party/ocmock/OCMock/OCMock.h"
+#include "ui/base/l10n/l10n_util_mac.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -39,6 +40,8 @@
 class CWVTranslationControllerTest : public PlatformTest {
  protected:
   CWVTranslationControllerTest() : browser_state_(/*off_the_record=*/false) {
+    l10n_util::OverrideLocaleWithCocoaLocale();
+
     web_state_.SetBrowserState(&browser_state_);
     auto test_navigation_manager =
         std::make_unique<web::TestNavigationManager>();
diff --git a/media/renderers/paint_canvas_video_renderer.cc b/media/renderers/paint_canvas_video_renderer.cc
index c052885..40c96ba 100644
--- a/media/renderers/paint_canvas_video_renderer.cc
+++ b/media/renderers/paint_canvas_video_renderer.cc
@@ -104,6 +104,10 @@
 
   GrGLTextureInfo source_textures[] = {{0, 0}, {0, 0}, {0, 0}};
   GLint min_mag_filter[][2] = {{0, 0}, {0, 0}, {0, 0}};
+  // TODO(bsalomon): Use GL_RGB8 once Skia supports it.
+  // skbug.com/7533
+  GrGLenum skia_texture_format =
+      video_frame->format() == PIXEL_FORMAT_NV12 ? GL_RGBA8 : GL_R8_EXT;
   for (size_t i = 0; i < video_frame->NumTextures(); ++i) {
     // Get the texture from the mailbox and wrap it in a GrTexture.
     const gpu::MailboxHolder& mailbox_holder = video_frame->mailbox_holder(i);
@@ -116,6 +120,7 @@
     source_textures[i].fID =
         gl->CreateAndConsumeTextureCHROMIUM(mailbox_holder.mailbox.name);
     source_textures[i].fTarget = mailbox_holder.texture_target;
+    source_textures[i].fFormat = skia_texture_format;
 
     gl->BindTexture(mailbox_holder.texture_target, source_textures[i].fID);
     gl->GetTexParameteriv(mailbox_holder.texture_target, GL_TEXTURE_MIN_FILTER,
@@ -139,22 +144,13 @@
       source_textures[i].fTarget = GL_TEXTURE_2D;
     }
   }
-  GrPixelConfig config = video_frame->format() == PIXEL_FORMAT_NV12
-                             ? kRGBA_8888_GrPixelConfig
-                             : kAlpha_8_GrPixelConfig;
   GrBackendTexture textures[3] = {
-      GrBackendTexture(ya_tex_size.width(), ya_tex_size.height(), config,
-                       source_textures[0]),
-      GrBackendTexture(uv_tex_size.width(), uv_tex_size.height(), config,
-                       source_textures[1]),
-      GrBackendTexture(uv_tex_size.width(), uv_tex_size.height(), config,
-                       source_textures[2]),
-  };
-
-  SkISize yuvSizes[] = {
-      {ya_tex_size.width(), ya_tex_size.height()},
-      {uv_tex_size.width(), uv_tex_size.height()},
-      {uv_tex_size.width(), uv_tex_size.height()},
+      GrBackendTexture(ya_tex_size.width(), ya_tex_size.height(),
+                       GrMipMapped::kNo, source_textures[0]),
+      GrBackendTexture(uv_tex_size.width(), uv_tex_size.height(),
+                       GrMipMapped::kNo, source_textures[1]),
+      GrBackendTexture(uv_tex_size.width(), uv_tex_size.height(),
+                       GrMipMapped::kNo, source_textures[2]),
   };
 
   SkYUVColorSpace color_space = kRec601_SkYUVColorSpace;
@@ -166,12 +162,10 @@
   sk_sp<SkImage> img;
   if (video_frame->format() == PIXEL_FORMAT_NV12) {
     img = SkImage::MakeFromNV12TexturesCopy(context_3d.gr_context, color_space,
-                                            textures, yuvSizes,
-                                            kTopLeft_GrSurfaceOrigin);
+                                            textures, kTopLeft_GrSurfaceOrigin);
   } else {
     img = SkImage::MakeFromYUVTexturesCopy(context_3d.gr_context, color_space,
-                                           textures, yuvSizes,
-                                           kTopLeft_GrSurfaceOrigin);
+                                           textures, kTopLeft_GrSurfaceOrigin);
   }
   for (size_t i = 0; i < video_frame->NumTextures(); ++i) {
     gl->BindTexture(source_textures[i].fTarget, source_textures[i].fID);
diff --git a/native_client_sdk/src/libraries/nacl_io/httpfs/http_fs_node.cc b/native_client_sdk/src/libraries/nacl_io/httpfs/http_fs_node.cc
index b92dc64..df3f949b 100644
--- a/native_client_sdk/src/libraries/nacl_io/httpfs/http_fs_node.cc
+++ b/native_client_sdk/src/libraries/nacl_io/httpfs/http_fs_node.cc
@@ -62,9 +62,14 @@
         // Found a non-whitespace, mark this as the start of the value.
         start = &headers[i];
         state = FINDING_VALUE;
-        // Fallthrough to start processing value without incrementing i.
-        [[clang::fallthrough]];
 
+        // NOTE: Avoid fallthrough as it produces a warning on newer compilers,
+        // but can't easily be silenced by the older NaCl compilers.
+        //
+        // Fallthrough to start processing value without incrementing i.
+        goto finding_value;
+
+      finding_value:
       case FINDING_VALUE:
         if (headers[i] == '\n') {
           // Found value.
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 323b8f81e..5432ce5 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -306,6 +306,8 @@
     "log/net_log_source_type_list.h",
     "log/net_log_with_source.cc",
     "log/net_log_with_source.h",
+    "quic/core/quic_error_codes.cc",
+    "quic/core/quic_error_codes.h",
     "socket/client_socket_handle.cc",
     "socket/client_socket_handle.h",
     "socket/connection_attempts.h",
@@ -1318,8 +1320,6 @@
       "quic/core/quic_data_reader.h",
       "quic/core/quic_data_writer.cc",
       "quic/core/quic_data_writer.h",
-      "quic/core/quic_error_codes.cc",
-      "quic/core/quic_error_codes.h",
       "quic/core/quic_flags_list.h",
       "quic/core/quic_flow_controller.cc",
       "quic/core/quic_flow_controller.h",
diff --git a/net/base/data_url.cc b/net/base/data_url.cc
index f35d6cc..172efb5d 100644
--- a/net/base/data_url.cc
+++ b/net/base/data_url.cc
@@ -25,25 +25,23 @@
                     std::string* mime_type,
                     std::string* charset,
                     std::string* data) {
-  if (!url.is_valid())
+  if (!url.is_valid() || !url.has_scheme())
     return false;
 
   DCHECK(mime_type->empty());
   DCHECK(charset->empty());
-  std::string::const_iterator begin = url.spec().begin();
-  std::string::const_iterator end = url.spec().end();
 
-  std::string::const_iterator after_colon = std::find(begin, end, ':');
-  if (after_colon == end)
-    return false;
-  ++after_colon;
+  std::string content = url.GetContent();
 
-  std::string::const_iterator comma = std::find(after_colon, end, ',');
+  std::string::const_iterator begin = content.begin();
+  std::string::const_iterator end = content.end();
+
+  std::string::const_iterator comma = std::find(begin, end, ',');
   if (comma == end)
     return false;
 
   std::vector<base::StringPiece> meta_data =
-      base::SplitStringPiece(base::StringPiece(after_colon, comma), ";",
+      base::SplitStringPiece(base::StringPiece(begin, comma), ";",
                              base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
 
   auto iter = meta_data.cbegin();
diff --git a/net/base/data_url_unittest.cc b/net/base/data_url_unittest.cc
index 1b2d036c..5379378 100644
--- a/net/base/data_url_unittest.cc
+++ b/net/base/data_url_unittest.cc
@@ -228,7 +228,15 @@
       true,
       "text/plain",
       "utf-8",
-      "\xE2\x80\x8F\xD8\xA7\xD8\xAE\xD8\xAA\xD8\xA8\xD8\xA7\xD8\xB1"}
+      "\xE2\x80\x8F\xD8\xA7\xD8\xAE\xD8\xAA\xD8\xA8\xD8\xA7\xD8\xB1"},
+
+    // The 'data' of a data URI does not include any ref it has.
+    {
+      "data:text/plain,this/is/a/test/%23include/#dontinclude",
+      true,
+      "text/plain",
+      "",
+      "this/is/a/test/#include/"},
 
     // TODO(darin): add more interesting tests
   };
diff --git a/net/base/net_errors.cc b/net/base/net_errors.cc
index e634d089..d35382f 100644
--- a/net/base/net_errors.cc
+++ b/net/base/net_errors.cc
@@ -4,6 +4,8 @@
 
 #include "net/base/net_errors.h"
 
+#include "net/quic/core/quic_error_codes.h"
+
 namespace net {
 
 const char kErrorDomain[] = "net";
@@ -12,6 +14,15 @@
   return "net::" + ErrorToShortString(error);
 }
 
+std::string ExtendedErrorToString(int error, int extended_error_code) {
+  if (error == ERR_QUIC_PROTOCOL_ERROR && extended_error_code != 0) {
+    return std::string("net::ERR_QUIC_PROTOCOL_ERROR.") +
+           QuicErrorCodeToString(
+               static_cast<QuicErrorCode>(extended_error_code));
+  }
+  return ErrorToString(error);
+}
+
 std::string ErrorToShortString(int error) {
   if (error == 0)
     return "OK";
diff --git a/net/base/net_errors.h b/net/base/net_errors.h
index 8fe976d..9c52702 100644
--- a/net/base/net_errors.h
+++ b/net/base/net_errors.h
@@ -36,6 +36,11 @@
 // Same as above, but leaves off the leading "net::".
 NET_EXPORT std::string ErrorToShortString(int error);
 
+// Returns a textual representation of the error code and the extended eror
+// code.
+NET_EXPORT std::string ExtendedErrorToString(int error,
+                                             int extended_error_code);
+
 // Returns true if |error| is a certificate error code.
 NET_EXPORT bool IsCertificateError(int error);
 
diff --git a/net/cert_net/nss_ocsp.cc b/net/cert_net/nss_ocsp.cc
index 1926cf6..2bbc5e32 100644
--- a/net/cert_net/nss_ocsp.cc
+++ b/net/cert_net/nss_ocsp.cc
@@ -90,23 +90,6 @@
   void AddRequest(OCSPRequestSession* request);
   void RemoveRequest(OCSPRequestSession* request);
 
-  // Clears internal state and calls |StartUsing()|. Should be called only in
-  // the context of testing.
-  void ReuseForTesting() {
-    {
-      base::AutoLock autolock(lock_);
-      DCHECK(base::MessageLoopForIO::current());
-      thread_checker_.DetachFromThread();
-
-      // CalledOnValidThread is the only available API to reassociate
-      // thread_checker_ with the current thread. Result ignored intentionally.
-      ignore_result(thread_checker_.CalledOnValidThread());
-      shutdown_ = false;
-      used_ = false;
-    }
-    StartUsing();
-  }
-
  private:
   friend struct base::LazyInstanceTraitsBase<OCSPIOLoop>;
 
@@ -881,10 +864,6 @@
   g_ocsp_io_loop.Get().Shutdown();
 }
 
-void ResetNSSHttpIOForTesting() {
-  g_ocsp_io_loop.Get().ReuseForTesting();
-}
-
 // This function would be called before NSS initialization.
 void SetURLRequestContextForNSSHttpIO(URLRequestContext* request_context) {
   pthread_mutex_lock(&g_request_context_lock);
diff --git a/net/cert_net/nss_ocsp.h b/net/cert_net/nss_ocsp.h
index a422cb22..6239bc2 100644
--- a/net/cert_net/nss_ocsp.h
+++ b/net/cert_net/nss_ocsp.h
@@ -26,10 +26,6 @@
 // related HTTP fetches.
 NET_EXPORT void ShutdownNSSHttpIO();
 
-// Can be called after a call to |ShutdownNSSHttpIO()| to reset internal state
-// and associate it with the current thread.
-NET_EXPORT void ResetNSSHttpIOForTesting();
-
 // Sets the URLRequestContext for HTTP requests issued by NSS.
 NET_EXPORT void SetURLRequestContextForNSSHttpIO(
     URLRequestContext* request_context);
diff --git a/net/quic/core/quic_error_codes.h b/net/quic/core/quic_error_codes.h
index 3dd8c1b..88fcca8f 100644
--- a/net/quic/core/quic_error_codes.h
+++ b/net/quic/core/quic_error_codes.h
@@ -295,7 +295,7 @@
     QuicRstStreamErrorCode error);
 
 // Returns the name of the QuicErrorCode as a char*
-QUIC_EXPORT_PRIVATE const char* QuicErrorCodeToString(QuicErrorCode error);
+QUIC_EXPORT const char* QuicErrorCodeToString(QuicErrorCode error);
 
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
diff --git a/net/reporting/reporting_uploader.cc b/net/reporting/reporting_uploader.cc
index 2220858c..2353726 100644
--- a/net/reporting/reporting_uploader.cc
+++ b/net/reporting/reporting_uploader.cc
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/callback_helpers.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "net/base/elements_upload_data_stream.h"
 #include "net/base/load_flags.h"
@@ -40,6 +41,26 @@
   return ReportingUploader::Outcome::FAILURE;
 }
 
+enum class UploadOutcome {
+  CANCELED_REDIRECT_TO_INSECURE_URL = 0,
+  CANCELED_AUTH_REQUIRED = 1,
+  CANCELED_CERTIFICATE_REQUESTED = 2,
+  CANCELED_SSL_CERTIFICATE_ERROR = 3,
+  CANCELED_REPORTING_SHUTDOWN = 4,
+  FAILED = 5,  // See Net.Reporting.UploadError for breakdown.
+  SUCCEEDED_SUCCESS = 6,
+  SUCCEEDED_REMOVE_ENDPOINT = 7,
+
+  MAX
+};
+
+void RecordUploadOutcome(UploadOutcome outcome) {
+  UMA_HISTOGRAM_ENUMERATION("Net.Reporting.UploadOutcome", outcome,
+                            UploadOutcome::MAX);
+}
+
+// TODO: Record net and HTTP error.
+
 class ReportingUploaderImpl : public ReportingUploader, URLRequest::Delegate {
  public:
   ReportingUploaderImpl(const URLRequestContext* context) : context_(context) {
@@ -157,6 +178,18 @@
     int response_code = headers ? headers->response_code() : 0;
     Outcome outcome = ResponseCodeToOutcome(response_code);
 
+    if (net_error != OK) {
+      RecordUploadOutcome(UploadOutcome::FAILED);
+      base::UmaHistogramSparse("Net.Reporting.UploadError", net_error);
+    } else if (response_code >= 200 && response_code <= 299) {
+      RecordUploadOutcome(UploadOutcome::SUCCEEDED_SUCCESS);
+    } else if (response_code == 410) {
+      RecordUploadOutcome(UploadOutcome::SUCCEEDED_REMOVE_ENDPOINT);
+    } else {
+      RecordUploadOutcome(UploadOutcome::FAILED);
+      base::UmaHistogramSparse("Net.Reporting.UploadError", response_code);
+    }
+
     std::move(upload->second).Run(outcome);
 
     request->Cancel();
diff --git a/net/ssl/client_cert_store_nss.cc b/net/ssl/client_cert_store_nss.cc
index 7c773ee..3a248f3b 100644
--- a/net/ssl/client_cert_store_nss.cc
+++ b/net/ssl/client_cert_store_nss.cc
@@ -20,6 +20,7 @@
 #include "base/task_scheduler/post_task.h"
 #include "base/threading/scoped_blocking_call.h"
 #include "crypto/nss_crypto_module_delegate.h"
+#include "crypto/nss_util.h"
 #include "net/cert/scoped_nss_types.h"
 #include "net/cert/x509_util_nss.h"
 #include "net/ssl/ssl_cert_request_info.h"
@@ -166,6 +167,8 @@
         password_delegate,
     const CertFilter& cert_filter,
     ClientCertIdentityList* identities) {
+  crypto::EnsureNSSInit();
+
   CERTCertList* found_certs =
       CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(), certUsageSSLClient,
                                 PR_FALSE, PR_FALSE, password_delegate.get());
diff --git a/services/network/public/cpp/url_loader_completion_status.cc b/services/network/public/cpp/url_loader_completion_status.cc
index b7792fa..a078bd9 100644
--- a/services/network/public/cpp/url_loader_completion_status.cc
+++ b/services/network/public/cpp/url_loader_completion_status.cc
@@ -26,6 +26,7 @@
 bool URLLoaderCompletionStatus::operator==(
     const URLLoaderCompletionStatus& rhs) const {
   return error_code == rhs.error_code &&
+         extended_error_code == rhs.extended_error_code &&
          exists_in_cache == rhs.exists_in_cache &&
          completion_time == rhs.completion_time &&
          encoded_data_length == rhs.encoded_data_length &&
diff --git a/services/network/public/cpp/url_loader_completion_status.h b/services/network/public/cpp/url_loader_completion_status.h
index 4d474d1..959b8a0 100644
--- a/services/network/public/cpp/url_loader_completion_status.h
+++ b/services/network/public/cpp/url_loader_completion_status.h
@@ -36,6 +36,9 @@
   // The error code. ERR_FAILED is set for CORS errors.
   int error_code = 0;
 
+  // Extra detail on the error.
+  int extended_error_code = 0;
+
   // A copy of the data requested exists in the cache.
   bool exists_in_cache = false;
 
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index 5767fb3..7119328 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -690,6 +690,11 @@
 
   URLLoaderCompletionStatus status;
   status.error_code = error_code;
+  if (error_code == net::ERR_QUIC_PROTOCOL_ERROR) {
+    net::NetErrorDetails details;
+    url_request_->PopulateNetErrorDetails(&details);
+    status.extended_error_code = details.quic_connection_error;
+  }
   status.exists_in_cache = url_request_->response_info().was_cached;
   status.completion_time = base::TimeTicks::Now();
   status.encoded_data_length = url_request_->GetTotalReceivedBytes();
diff --git a/third_party/WebKit/LayoutTests/SlowTests b/third_party/WebKit/LayoutTests/SlowTests
index 2a501596..4147c66 100644
--- a/third_party/WebKit/LayoutTests/SlowTests
+++ b/third_party/WebKit/LayoutTests/SlowTests
@@ -491,6 +491,14 @@
 
 crbug.com/808185 [ Win Release ] http/tests/devtools/editor/text-editor-ctrl-d-1.js [ Slow ]
 
+# OffscreenCanvas non-virtual convert-to-blob tests
+# These two tests are running without idle-task support in non-virtual
+# environment; we are testing whether they can switch to force-encoding mode
+# after waiting for idle task to start for a threshold time. Therefore, we
+# intentionally mark them as slow tests to allow the waiting to finish.
+crbug.com/817091 external/wpt/offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.html [ Slow ]
+crbug.com/817091 external/wpt/offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.w.html [ Slow ]
+
 crbug.com/808153 fast/webgl/texImage-imageBitmap-from-blob-resize.html [ Slow ]
 
 # This test is very CPU intensive, as it parses a lot of IDL in JS. It's slow
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 3e41a57..b0c9d31 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -3263,7 +3263,6 @@
 
 # Sheriff failures 2018-02-19
 crbug.com/771643 [ Mac10.11 ] virtual/gpu-rasterization/images/color-profile-image-filter-all.html [ Failure ]
-crbug.com/813462 external/wpt/offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.html [ Pass Timeout ]
 
 # Sheriff failures 2018-02-20
 crbug.com/789921 media/controls/repaint-on-resize.html [ Failure Pass ]
@@ -3375,5 +3374,4 @@
 crbug.com/681919 [ Linux Debug ] http/tests/media/media-src-suspend-before-have-metadata.html [ Timeout Pass ]
 
 # Sheriff 2018-03-07
-crbug.com/819591 virtual/threaded/animations/hit-testing/composited-with-hit-testing.html [ Failure Pass ]
 crbug.com/819778 [ Linux ] external/wpt/css/cssom-view/interfaces.html [ Pass Timeout ]
diff --git a/third_party/WebKit/LayoutTests/VirtualTestSuites b/third_party/WebKit/LayoutTests/VirtualTestSuites
index e5c6f9b..790a03f 100644
--- a/third_party/WebKit/LayoutTests/VirtualTestSuites
+++ b/third_party/WebKit/LayoutTests/VirtualTestSuites
@@ -22,6 +22,11 @@
   },
   {
     "prefix": "threaded",
+    "base": "external/wpt/offscreen-canvas/convert-to-blob",
+    "args": ["--enable-threaded-compositing"]
+  },
+  {
+    "prefix": "threaded",
     "base": "compositing/visibility",
     "args": ["--enable-threaded-compositing"]
   },
diff --git a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
index ef6be4e..1e572f47 100644
--- a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
+++ b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
@@ -80453,6 +80453,102 @@
      {}
     ]
    ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-001.html": [
+    [
+     "/css/css-writing-modes/sizing-orthogonal-percentage-margin-001.html",
+     [
+      [
+       "/css/css-writing-modes/sizing-orthogonal-percentage-margin-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-002.html": [
+    [
+     "/css/css-writing-modes/sizing-orthogonal-percentage-margin-002.html",
+     [
+      [
+       "/css/css-writing-modes/sizing-orthogonal-percentage-margin-002-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-003.html": [
+    [
+     "/css/css-writing-modes/sizing-orthogonal-percentage-margin-003.html",
+     [
+      [
+       "/css/css-writing-modes/sizing-orthogonal-percentage-margin-003-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-004.html": [
+    [
+     "/css/css-writing-modes/sizing-orthogonal-percentage-margin-004.html",
+     [
+      [
+       "/css/css-writing-modes/sizing-orthogonal-percentage-margin-003-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-005.html": [
+    [
+     "/css/css-writing-modes/sizing-orthogonal-percentage-margin-005.html",
+     [
+      [
+       "/css/css-writing-modes/sizing-orthogonal-percentage-margin-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-006.html": [
+    [
+     "/css/css-writing-modes/sizing-orthogonal-percentage-margin-006.html",
+     [
+      [
+       "/css/css-writing-modes/sizing-orthogonal-percentage-margin-006-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-007.html": [
+    [
+     "/css/css-writing-modes/sizing-orthogonal-percentage-margin-007.html",
+     [
+      [
+       "/css/css-writing-modes/sizing-orthogonal-percentage-margin-007-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-008.html": [
+    [
+     "/css/css-writing-modes/sizing-orthogonal-percentage-margin-008.html",
+     [
+      [
+       "/css/css-writing-modes/sizing-orthogonal-percentage-margin-007-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-writing-modes/svg-aliasing-001.html": [
     [
      "/css/css-writing-modes/svg-aliasing-001.html",
@@ -127657,6 +127753,36 @@
      {}
     ]
    ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-001-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-002-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-003-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-005-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-006-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/css-writing-modes/sizing-orthogonal-percentage-margin-007-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-writing-modes/support/100x100-lime.png": [
     [
      {}
@@ -208042,6 +208168,18 @@
      {}
     ]
    ],
+   "offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.html": [
+    [
+     "/offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.html",
+     {}
+    ]
+   ],
+   "offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.w.html": [
+    [
+     "/offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.w.html",
+     {}
+    ]
+   ],
    "offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.3arg.html": [
     [
      "/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.3arg.html",
@@ -215086,18 +215224,6 @@
      {}
     ]
    ],
-   "offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.html": [
-    [
-     "/offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.html",
-     {}
-    ]
-   ],
-   "offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.w.html": [
-    [
-     "/offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.w.html",
-     {}
-    ]
-   ],
    "offscreen-canvas/the-offscreen-canvas/offscreencanvas.getcontext.html": [
     [
      "/offscreen-canvas/the-offscreen-canvas/offscreencanvas.getcontext.html",
@@ -303724,7 +303850,7 @@
    "testharness"
   ],
   "css/css-typed-om/the-stylepropertymap/properties/height.html": [
-   "36b8677eb45f8555da4381e1644624df2f2060e0",
+   "617ec941ab1cbd02b31b8a9bb7ce6da311109476",
    "testharness"
   ],
   "css/css-typed-om/the-stylepropertymap/properties/isolation.html": [
@@ -303940,7 +304066,7 @@
    "testharness"
   ],
   "css/css-typed-om/the-stylepropertymap/properties/width.html": [
-   "d429f7a88012179ef3d604b79b3db4aaba0ca426",
+   "205915eb7162e23fd5600488304dd8dfa8e51ee2",
    "testharness"
   ],
   "css/css-typed-om/the-stylepropertymap/properties/writing-mode-expected.txt": [
@@ -311631,6 +311757,62 @@
    "770cfe7939de94e221926b65bccfa057ee7711f2",
    "reftest"
   ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-001-ref.html": [
+   "dc2667e1dee2cb1c22f678aabe4d617bb4f64f69",
+   "support"
+  ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-001.html": [
+   "efa71c02524fa57eac92e8311b7c95befd06bda1",
+   "reftest"
+  ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-002-ref.html": [
+   "da28e37e576350d3eec7061f0dab66d5d7d4a261",
+   "support"
+  ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-002.html": [
+   "109856fd1d885f7d5f8cd4a6c0db0eb8cec83dc0",
+   "reftest"
+  ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-003-ref.html": [
+   "bea55f8e91d4ff012e7a2ce5efa70b653893c565",
+   "support"
+  ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-003.html": [
+   "186bbff0e10a299950a3594349115956a977d2d8",
+   "reftest"
+  ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-004.html": [
+   "663ce0026de3c61a649c77aa6ac0097940ab1a47",
+   "reftest"
+  ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-005-ref.html": [
+   "3b68b98fd6e8be39b56d2882e0fe739d46b826d0",
+   "support"
+  ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-005.html": [
+   "2d4ccf4c626ead622054753ebdabadc2626483f8",
+   "reftest"
+  ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-006-ref.html": [
+   "6d45ce1c05f792c126bd5c912f6f0232cd6a8fb9",
+   "support"
+  ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-006.html": [
+   "582ce006ae8576c9b44f2532fea73641a7f4ad9b",
+   "reftest"
+  ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-007-ref.html": [
+   "a2a544c867707e4ac9715495c3ba7b6d26670818",
+   "support"
+  ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-007.html": [
+   "b4a0abd69a69f19ef671042ee972a7fe62675fc6",
+   "reftest"
+  ],
+  "css/css-writing-modes/sizing-orthogonal-percentage-margin-008.html": [
+   "2543ef7b6e41b2b0614c32167f4e0a85319dae0c",
+   "reftest"
+  ],
   "css/css-writing-modes/support/100x100-lime.png": [
    "b02fc2d0ad1d95a2aeb6011022e63928841b183f",
    "support"
@@ -319296,7 +319478,7 @@
    "testharness"
   ],
   "custom-elements/builtin-coverage.html": [
-   "a33f3950860fead2c1cbe249d73394b1888facd0",
+   "14cda641d06979600b461a4985f36c1fbc3defc9",
    "testharness"
   ],
   "custom-elements/connected-callbacks.html": [
@@ -350459,6 +350641,14 @@
    "aebe4c1ad096a35fce7aa8c12d7655b525b5cd2a",
    "testharness"
   ],
+  "offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.html": [
+   "4268a275ad695dfded9f6ed8de1850fafb649f95",
+   "testharness"
+  ],
+  "offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.w.html": [
+   "8466fb824c584d343ca20a52fd8d316fc2cd0d1c",
+   "testharness"
+  ],
   "offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.3arg.html": [
    "f2e95967ca89ad85787c121f3cae4ed35e825b44",
    "testharness"
@@ -355207,14 +355397,6 @@
    "ca1cedea429efabeaaf1132b54fe45a7626f7f86",
    "testharness"
   ],
-  "offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.html": [
-   "4268a275ad695dfded9f6ed8de1850fafb649f95",
-   "testharness"
-  ],
-  "offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.w.html": [
-   "8466fb824c584d343ca20a52fd8d316fc2cd0d1c",
-   "testharness"
-  ],
   "offscreen-canvas/the-offscreen-canvas/offscreencanvas.getcontext.html": [
    "2961422d4081a0fc55cb8bde62e6d33cb2877832",
    "testharness"
@@ -365000,7 +365182,7 @@
    "testharness"
   ],
   "resource-timing/resource_initiator_types.html": [
-   "11ad45e383c110eee2496c481abbff9642f6e6ee",
+   "b05a2dfad953398d6b31f11055606f2f3694a9af",
    "testharness"
   ],
   "resource-timing/resource_memory_cached.sub.html": [
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/OWNERS
index 51d99d17..ac6a3b0e 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/CSS2/OWNERS
@@ -1,4 +1,5 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
 kojii@chromium.org
 mstensho@chromium.org
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/OWNERS
new file mode 100644
index 0000000..110a538a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/OWNERS
@@ -0,0 +1,3 @@
+# TEAM: style-dev@chromium.org
+# COMPONENT: Blink>CSS
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-align/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-align/OWNERS
index e20e807..01fa028 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-align/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-align/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
 jfernandez@igalia.com
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-backgrounds/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-backgrounds/OWNERS
index 7c1ec12..9b2e5be 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-backgrounds/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-backgrounds/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-break/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-break/OWNERS
index 35f3611b..198e62f8 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-break/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-break/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
 mstensho@chromium.org
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-display/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-display/OWNERS
index 1023dc6..16fcd21 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-display/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-display/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
 ecobos@igalia.com
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/OWNERS
index 30158c8ab..102c483 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout>Grid
+# WPT-NOTIFY: true
 rego@igalia.com
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/OWNERS
index 3e100ec1..75b007c7 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
 file://third_party/WebKit/Source/core/layout/custom/OWNERS
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-logical/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-logical/OWNERS
index 7c1ec12..9b2e5be 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-logical/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-logical/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-multicol/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-multicol/OWNERS
index fbf1502..6a3de9b 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-multicol/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-multicol/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout>MultiCol
+# WPT-NOTIFY: true
 mstensho@chromium.org
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-position/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-position/OWNERS
index 55866e4..8f03d3c 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-position/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-position/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
 smcgruer@chromium.org
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-rhythm/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-rhythm/OWNERS
index 1fd22e16..b1e355e6 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-rhythm/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-rhythm/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
 kojii@chromium.org
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-scroll-anchoring/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-scroll-anchoring/OWNERS
index 0bf77dd..6db7af8 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-scroll-anchoring/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-scroll-anchoring/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
 skobes@chromium.org
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-scroll-snap/snap-inline-block.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-scroll-snap/snap-inline-block.html
new file mode 100644
index 0000000..d41dcc8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-scroll-snap/snap-inline-block.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-1" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+div {
+  position: absolute;
+  margin: 0px;
+}
+#scroller {
+  width: 400px;
+  height: 350px;
+  overflow: scroll;
+  scroll-snap-type: both mandatory;
+}
+#space {
+  width: 1000px;
+  height: 1000px;
+}
+#target {
+  width: 200px;
+  height: 200px;
+  left: 300px;
+  top: 300px;
+  scroll-snap-align: start end;
+}
+</style>
+
+<div id="scroller">
+  <div id="space"></div>
+  <div id="target"></div>
+</div>
+
+<script>
+var scroller = document.getElementById("scroller");
+var width = scroller.clientWidth;
+var height = scroller.clientHeight;
+[
+  ["horizontal-tb", 300, 500 - height],
+  ["vertical-lr", 500 - width, 300],
+  ["vertical-rl", 300, 300]
+].forEach(([writing_mode, left, top]) => {
+  test(() => {
+    scroller.style.writingMode = writing_mode;
+    if (writing_mode == "vertical-rl")
+      document.getElementById("target").style.left = (width - 700) + "px";
+    scroller.scrollTo(0, 0);
+    assert_equals(scroller.scrollLeft, left, "aligns correctly on x");
+    assert_equals(scroller.scrollTop, top, "aligns correctly on y");
+  }, "Snaps correctly for " + writing_mode +
+     " writing mode with 'inline' and 'block' alignments");
+})
+</script>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes/OWNERS
index 993f11e..45d17fb8 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout>Shape
+# WPT-NOTIFY: true
 bjonesbe@adobe.com
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-002.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-002.html
index ee2dd75..01d916d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-002.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-002.html
@@ -22,7 +22,7 @@
         }
         #image {
             float: left;
-            shape-outside: url('data:image/svg+xml;utf8,<svg width="100px" height="100px" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>');
+            shape-outside: url('data:image/svg+xml;utf8,<svg width="100px" height="100px" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="%23006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>');
         }
     </style>
 </head>
@@ -31,7 +31,7 @@
         The test passes if you see a solid green square. There should be no red.
     </p>
     <div id="test" class="container">
-        <img id="image" src='data:image/svg+xml;utf8,<svg width="100px" height="100px" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>'/>
+        <img id="image" src='data:image/svg+xml;utf8,<svg width="100px" height="100px" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="%23006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>'/>
         X
         X
     </div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-005.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-005.html
index 2c642c02..93f3978 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-005.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-005.html
@@ -22,7 +22,7 @@
         }
         #image {
             float: left;
-            shape-outside: url('data:image/svg+xml;utf8,<svg width="100px" height="100px" style="background-color: rgba(0,0,0,0.7)" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>');
+            shape-outside: url('data:image/svg+xml;utf8,<svg width="100px" height="100px" style="background-color: rgba(0,0,0,0.7)" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="%23006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>');
             shape-image-threshold: 0.8;
         }
     </style>
@@ -32,7 +32,7 @@
         The test passes if you see a solid green square. There should be no red.
     </p>
     <div id="test" class="container">
-        <img id="image" src='data:image/svg+xml;utf8,<svg width="100px" height="100px" style="background-color: rgba(0,0,0,0.7)" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>'/>
+        <img id="image" src='data:image/svg+xml;utf8,<svg width="100px" height="100px" style="background-color: rgba(0,0,0,0.7)" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="%23006400" d=" M 0.00 0.00 L 50.00 0.00 C 50.00 33.33 50.00 66.67 50.00 100.00 L 0.00 100.00 L 0.00 0.00 Z" /></svg>'/>
         X
         X
     </div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-tables/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-tables/OWNERS
index 63bdf6f0..1cc9d5b 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-tables/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-tables/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout>Table
+# WPT-NOTIFY: true
 dgrogan@chromium.org
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-text-decor/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-text-decor/OWNERS
index 1556434..801a0f4 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-text-decor/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-text-decor/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
 drott@chromium.org
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-text/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-text/OWNERS
index 7c1ec12..9b2e5be 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-text/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-text/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui/OWNERS
index 2564ee5b..5f43f911 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-ui/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-ui/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
 rego@igalia.com
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/OWNERS
index 8b189f12..d7655b1 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/OWNERS
@@ -1,3 +1,4 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout>WritingMode
+# WPT-NOTIFY: true
 kojii@chromium.org
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-001-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-001-ref.html
new file mode 100644
index 0000000..d8edfb69
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-001-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 50px;
+  height: 100px;
+  writing-mode: vertical-lr;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-001.html
new file mode 100644
index 0000000..d5a77cd
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-001.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-001-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that a verticalLR element inside an horizontal container computes properly its percentage margins against the container's inline size (the container's width in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (500px), so it should be 50px and element's height should be 100px. */
+  writing-mode: vertical-lr;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-002-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-002-ref.html
new file mode 100644
index 0000000..98e6d77
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-002-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 50px;
+  height: 100px;
+  writing-mode: vertical-rl;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-002.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-002.html
new file mode 100644
index 0000000..55e266fd
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-002.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-002-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that a verticalRL element inside an horizontal container computes properly its percentage margins against the container's inline size (the container's width in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (500px), so it should be 50px and element's height should be 100px. */
+  writing-mode: vertical-rl;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-003-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-003-ref.html
new file mode 100644
index 0000000..f035f59
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-003-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+  writing-mode: vertical-lr;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 20px;
+  width: 460px;
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-003.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-003.html
new file mode 100644
index 0000000..d722504
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-003.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-003-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that an horizontal element inside a verticalLR container computes properly its percentage margins against the container's inline size (the container's height in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+  writing-mode: vertical-lr;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (200px), so it should be 20px and element's width should be 460px. */
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-004.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-004.html
new file mode 100644
index 0000000..2cd59eb6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-004.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-003-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that an horizontal element inside a verticalLR container computes properly its percentage margins against the container's inline size (the container's height in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 200px;
+  width: 500px;
+  writing-mode: vertical-rl;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (200px), so it should be 20px and element's width should be 460px. */
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-005-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-005-ref.html
new file mode 100644
index 0000000..40e4412
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-005-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 20px;
+  height: 460px;
+  writing-mode: vertical-lr;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-005.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-005.html
new file mode 100644
index 0000000..79801424
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-005.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-005-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that a verticalLR element inside an horizontal container computes properly its percentage margins against the container's inline size (the container's width in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (200px), so it should be 20px and element's height should be 460px. */
+  writing-mode: vertical-lr;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-006-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-006-ref.html
new file mode 100644
index 0000000..3b25252
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-006-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 20px;
+  height: 460px;
+  writing-mode: vertical-rl;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-006.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-006.html
new file mode 100644
index 0000000..f945dec8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-006.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-006-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that a verticalRL element inside an horizontal container computes properly its percentage margins against the container's inline size (the container's width in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+}
+.element {
+  background: magenta;
+  font: 50px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (200px), so it should be 20px and element's height should be 460px. */
+  writing-mode: vertical-rl;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-007-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-007-ref.html
new file mode 100644
index 0000000..02dfe07
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-007-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins reference file</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+  writing-mode: vertical-lr;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 50px;
+  width: 100px;
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-007.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-007.html
new file mode 100644
index 0000000..93f5053
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-007.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-007-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that an horizontal element inside a verticalLR container computes properly its percentage margins against the container's inline size (the container's height in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+  writing-mode: vertical-lr;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (500px), so it should be 50px and element's width should be 100px. */
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-008.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-008.html
new file mode 100644
index 0000000..39bf4f5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-writing-modes/sizing-orthogonal-percentage-margin-008.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Writing Modes Test: Orthogonal element sizing and percentage margins</title>
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="help" href="http://www.w3.org/TR/css-writing-modes-3/#dimension-mapping">
+<link rel="match" href="sizing-orthogonal-percentage-margin-007-ref.html">
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<meta name="assert" content="This test checks that an horizontal element inside a verticalLR container computes properly its percentage margins against the container's inline size (the container's height in this test), and the element is sized accordingly subtracting those margins from the available size.">
+<style>
+.container {
+  background: cyan;
+  border: solid thick;
+  height: 500px;
+  width: 200px;
+  writing-mode: vertical-rl;
+}
+.element {
+  background: magenta;
+  font: 25px/1 Ahem;
+  margin: 10%; /* This should be computed against the container's inline size (500px), so it should be 50px and element's width should be 100px. */
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>The test passes if it has the same visual effect as reference.</p>
+
+<div class="container">
+  <div class="element">XX X X X XX X X XX X XX</div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/data-urls/processing.any-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/fetch/data-urls/processing.any-expected.txt
index 3ed08b40..a9a569b 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/fetch/data-urls/processing.any-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/fetch/data-urls/processing.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 71 tests; 44 PASS, 27 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 71 tests; 45 PASS, 26 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Setup.
 PASS "data://test/,X"
 FAIL "data://test:test/,X" assert_unreached: Should have rejected: undefined Reached unreachable code
@@ -8,7 +8,7 @@
 PASS "data:text/html"
 PASS "data:text/html    ;charset=x   "
 PASS "data:,"
-FAIL "data:,X#X" assert_array_equals: lengths differ, expected 1 got 3
+PASS "data:,X#X"
 PASS "data:,%FF"
 PASS "data:text/plain,X"
 PASS "data:text/plain ,X"
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/data-urls/processing.any.worker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/fetch/data-urls/processing.any.worker-expected.txt
index 3ed08b40..a9a569b 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/fetch/data-urls/processing.any.worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/fetch/data-urls/processing.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 71 tests; 44 PASS, 27 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 71 tests; 45 PASS, 26 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Setup.
 PASS "data://test/,X"
 FAIL "data://test:test/,X" assert_unreached: Should have rejected: undefined Reached unreachable code
@@ -8,7 +8,7 @@
 PASS "data:text/html"
 PASS "data:text/html    ;charset=x   "
 PASS "data:,"
-FAIL "data:,X#X" assert_array_equals: lengths differ, expected 1 got 3
+PASS "data:,X#X"
 PASS "data:,%FF"
 PASS "data:text/plain,X"
 PASS "data:text/plain ,X"
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html b/third_party/WebKit/LayoutTests/external/wpt/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html
index 3b13fccd..d344320 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html
@@ -16,7 +16,7 @@
   var doubleEscapedBrace = "&amp;amp;lt;";
   var rawNewline = "&#10;";
   var escapedNewline = "&amp;#10;";
-  var doubleEscapedNewline = "&amp;amp;#10;";
+  var doubleEscapedNewline = "&amp;amp;%2310;";
 
   function appendFrameAndGetElement(test, frame) {
     return new Promise((resolve, reject) => {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/flow-content-0/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/flow-content-0/OWNERS
index 7c1ec12..9b2e5be 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/flow-content-0/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/flow-content-0/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/lists/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/lists/OWNERS
index 7c1ec12..9b2e5be 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/lists/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/lists/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/phrasing-content-0/font-element-text-decoration-color/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/phrasing-content-0/font-element-text-decoration-color/OWNERS
index 7c1ec12..9b2e5be 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/phrasing-content-0/font-element-text-decoration-color/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/phrasing-content-0/font-element-text-decoration-color/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/OWNERS
index 8c39d70..77c2a59a 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout>Table
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-element-0/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-element-0/OWNERS
index 7c1ec12..9b2e5be 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-element-0/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-fieldset-element-0/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/OWNERS
index 7c1ec12..9b2e5be 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/the-hr-element-0/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/OWNERS
index 7c1ec12..9b2e5be 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/replaced-elements/embedded-content-rendering-rules/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/replaced-elements/embedded-content-rendering-rules/OWNERS
index 7c1ec12..9b2e5be 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/replaced-elements/embedded-content-rendering-rules/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/replaced-elements/embedded-content-rendering-rules/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/replaced-elements/images/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/replaced-elements/images/OWNERS
index 7c1ec12..9b2e5be 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/replaced-elements/images/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/replaced-elements/images/OWNERS
@@ -1,2 +1,3 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/077.html b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/077.html
index a7a5942..dbcd16be 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/077.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/077.html
@@ -17,12 +17,12 @@
                         var t = async_test()
 
                         function test() {
-                                var script = createScript('data:text\/javascript,log("Script #1 ran")');
+                                var script = createScript('data:text\/javascript,log("Script %231 ran")');
                                 var script2 = createScript('','log("Script #2 ran")');
                                 if(script2) {
                                     head.removeChild(script2);
                                 }
-                                var script3 = createScript('data:text\/javascript, log("Script #3 ran"); createScript(\'\', \'log("Script #4 ran")\')');
+                                var script3 = createScript('data:text\/javascript, log("Script %233 ran"); createScript(\'\', \'log("Script %234 ran")\')');
                                 if(script3) {
                                     head.removeChild(script3);
                                 }
diff --git a/third_party/WebKit/LayoutTests/external/wpt/intersection-observer/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/intersection-observer/OWNERS
index a0b6172..1d4fb997 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/intersection-observer/OWNERS
+++ b/third_party/WebKit/LayoutTests/external/wpt/intersection-observer/OWNERS
@@ -1,4 +1,5 @@
 # TEAM: layout-dev@chromium.org
 # COMPONENT: Blink>Layout
+# WPT-NOTIFY: true
 eae@chromium.org
 szager@chromium.org
diff --git a/third_party/WebKit/LayoutTests/external/wpt/offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.html b/third_party/WebKit/LayoutTests/external/wpt/offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.html
similarity index 100%
rename from third_party/WebKit/LayoutTests/external/wpt/offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.html
rename to third_party/WebKit/LayoutTests/external/wpt/offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.html
diff --git a/third_party/WebKit/LayoutTests/external/wpt/offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.w.html b/third_party/WebKit/LayoutTests/external/wpt/offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.w.html
similarity index 100%
rename from third_party/WebKit/LayoutTests/external/wpt/offscreen-canvas/the-offscreen-canvas/offscreencanvas.convert.to.blob.w.html
rename to third_party/WebKit/LayoutTests/external/wpt/offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.w.html
diff --git a/third_party/WebKit/LayoutTests/external/wpt/resource-timing/resource_initiator_types.html b/third_party/WebKit/LayoutTests/external/wpt/resource-timing/resource_initiator_types.html
index 69cb23b..d593f13d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/resource-timing/resource_initiator_types.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/resource-timing/resource_initiator_types.html
@@ -112,7 +112,7 @@
     addEntryIfExists(entries, expected_entries, pathname + 'blue.png?id=video-poster', 'video');
     addEntryIfExists(entries, expected_entries, '/media/test.mp4?id=video-src', 'video');
     addEntryIfExists(entries, expected_entries, '/media/test.mp4?id=video-source', 'source');
-    addEntryIfExists(entries, expected_entries, '/media/test.ogg?id=video-source', 'source');
+    addEntryIfExists(entries, expected_entries, '/media/test.ogv?id=video-source', 'source');
     addEntryIfExists(entries, expected_entries, pathname + 'empty.py?id=video-track', 'track');
     addEntryIfExists(entries, expected_entries, pathname + 'empty.py?id=audio-src', 'audio');
     addEntryIfExists(entries, expected_entries, pathname + 'empty.py?id=audio-source-wav', 'source');
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/data-uri-fragment-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/url/data-uri-fragment-expected.txt
deleted file mode 100644
index 556dd2b..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/url/data-uri-fragment-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Data URI parsing of fragments assert_equals: expected "<p id=\"foo\">This should be the only visible text.</p>" but got "<p id=\"foo\">This should be the only visible text.</p>#foo"
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/fast/backgrounds/background-image-relative-url-in-iframe-expected.html b/third_party/WebKit/LayoutTests/fast/backgrounds/background-image-relative-url-in-iframe-expected.html
index 5b47b90..cda6fdb 100644
--- a/third_party/WebKit/LayoutTests/fast/backgrounds/background-image-relative-url-in-iframe-expected.html
+++ b/third_party/WebKit/LayoutTests/fast/backgrounds/background-image-relative-url-in-iframe-expected.html
@@ -1,3 +1,3 @@
 <!DOCTYPE html>
-<iframe src="data:text/html,<div style='background: #00FF01; width: 24px; height: 24px'></div>" >
+<iframe src="data:text/html,<div style='background: %2300FF01; width: 24px; height: 24px'></div>" >
 </iframe>
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-protocol/terminate-execution-with-fetch-callbacks-expected.txt b/third_party/WebKit/LayoutTests/http/tests/inspector-protocol/terminate-execution-with-fetch-callbacks-expected.txt
new file mode 100644
index 0000000..7e01385
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-protocol/terminate-execution-with-fetch-callbacks-expected.txt
@@ -0,0 +1,21 @@
+Tests terminate execution.
+{
+    description : 1
+    type : number
+    value : 1
+}
+{
+    id : <number>
+    result : {
+    }
+}
+{
+    id : <number>
+    result : {
+        result : {
+            type : string
+            value : evaluated after
+        }
+    }
+}
+
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector-protocol/terminate-execution-with-fetch-callbacks.js b/third_party/WebKit/LayoutTests/http/tests/inspector-protocol/terminate-execution-with-fetch-callbacks.js
new file mode 100644
index 0000000..42bb534
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/inspector-protocol/terminate-execution-with-fetch-callbacks.js
@@ -0,0 +1,19 @@
+(async function(testRunner) {
+  var {page, session, dp} =
+      await testRunner.startBlank('Tests terminate execution.');
+  dp.Runtime.enable();
+  let consoleCall = dp.Runtime.onceConsoleAPICalled();
+  dp.Runtime.evaluate({
+    expression: `let p = fetch('data:text/plain,Hello again!');
+    p.then(() => {console.log(1); while(true){}});
+    p.then(() => console.log(42));`
+  });
+  await consoleCall.then(msg => testRunner.log(msg.params.args[0]));
+  await Promise.all([
+    dp.Runtime
+        .evaluate({expression: '\'evaluated after\'', returnByValue: true})
+        .then(msg => testRunner.log(msg)),
+    dp.Runtime.terminateExecution().then(msg => testRunner.log(msg))
+  ]);
+  testRunner.completeTest();
+})
diff --git a/third_party/WebKit/LayoutTests/http/tests/linkHeader/link-rel-import-css-rule-capital.html b/third_party/WebKit/LayoutTests/http/tests/linkHeader/link-rel-import-css-rule-capital.html
index e65fb67..709e8c6 100644
--- a/third_party/WebKit/LayoutTests/http/tests/linkHeader/link-rel-import-css-rule-capital.html
+++ b/third_party/WebKit/LayoutTests/http/tests/linkHeader/link-rel-import-css-rule-capital.html
@@ -5,7 +5,7 @@
 #target2 { color: 008000; }
 </style>
 
-<link rel="import" href="data:text/html,<style>.test .testWithCapital { background-color: green; } .test .testwithoutcapital { border: 2px green; } #target2 { color: ff0000 } #tArGeT1 { color: green }</style>">
+<link rel="import" href="data:text/html,<style>.test .testWithCapital { background-color: green; } .test .testwithoutcapital { border: 2px green; } %23target2 { color: ff0000 } %23tArGeT1 { color: green }</style>">
 <div class="test">
   <div id="target1" class="testwithoutcapital testWithCapital"></div>
 </div>
@@ -23,4 +23,4 @@
 test(function() {
   assert_equals(getComputedStyle(target2).color, green);
 }, "Test that rules in <link rel=import> are parsed in strict mode");
-</script>
\ No newline at end of file
+</script>
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/debugger/message-channel-async-stack.js b/third_party/WebKit/LayoutTests/inspector-protocol/debugger/message-channel-async-stack.js
index 4a1483d..c3a4693 100644
--- a/third_party/WebKit/LayoutTests/inspector-protocol/debugger/message-channel-async-stack.js
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/debugger/message-channel-async-stack.js
@@ -8,7 +8,7 @@
   await dp.Runtime.evaluate({
     expression: `
     let frame = document.createElement('iframe');
-    frame.src = 'data:text/html,<script>onmessage = (e) => e.ports[0].postMessage(\\'pong\\');//# sourceURL=iframe.js</script>';
+    frame.src = 'data:text/html,<script>onmessage = (e) => e.ports[0].postMessage(\\'pong\\');//%23 sourceURL=iframe.js</script>';
     let p = new Promise(resolve => frame.onload = resolve);
     document.body.appendChild(frame);
     p
@@ -62,4 +62,4 @@
       debuggerId);
 
   testRunner.completeTest();
-})
\ No newline at end of file
+})
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/page/terminate-execution-expected.txt b/third_party/WebKit/LayoutTests/inspector-protocol/page/terminate-execution-expected.txt
new file mode 100644
index 0000000..155491ea
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/page/terminate-execution-expected.txt
@@ -0,0 +1,14 @@
+Tests terminate execution.
+{
+    id : <number>
+    result : {
+    }
+}
+{
+    error : {
+        code : -32000
+        message : Execution was terminated
+    }
+    id : <number>
+}
+
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/page/terminate-execution.js b/third_party/WebKit/LayoutTests/inspector-protocol/page/terminate-execution.js
new file mode 100644
index 0000000..d3e578b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/page/terminate-execution.js
@@ -0,0 +1,20 @@
+(async function(testRunner) {
+  var {page, session, dp} =
+      await testRunner.startHTML(`
+    <div></div>
+    <script>
+      document.querySelector('div').addEventListener('click', () => { console.log(42); while(true){} });
+      document.querySelector('div').addEventListener('click', () => { while(true){} });
+    </script>
+  `, 'Tests terminate execution.');
+  dp.Runtime.enable();
+  let promise =
+      dp.Runtime.evaluate({expression: `document.querySelector('div').click()`})
+          .then(msg => testRunner.log(msg));
+  await dp.Runtime.onceConsoleAPICalled();
+  await Promise.all([
+    dp.Emulation.setScriptExecutionDisabled({value: true}),
+    dp.Runtime.terminateExecution().then(msg => testRunner.log(msg)), promise
+  ]);
+  testRunner.completeTest();
+})
diff --git a/third_party/WebKit/LayoutTests/loader/link-load-only-supported-stylesheet-types-datauri.html b/third_party/WebKit/LayoutTests/loader/link-load-only-supported-stylesheet-types-datauri.html
index 7ae336991..615a0d4f 100644
--- a/third_party/WebKit/LayoutTests/loader/link-load-only-supported-stylesheet-types-datauri.html
+++ b/third_party/WebKit/LayoutTests/loader/link-load-only-supported-stylesheet-types-datauri.html
@@ -1,9 +1,9 @@
 <!DOCTYPE html>
 <html>
 <body>
-    <link rel="stylesheet" type="text/css" href="data:text/css,#blue { color: blue !important; }">
-    <link rel="stylesheet" type="application/javascript" href="data:text/css,#red { color: red !important; }">
-    <link rel="stylesheet" href="data:text/css,#yellow { color: yellow !important; }">
+    <link rel="stylesheet" type="text/css" href="data:text/css,%23blue { color: blue !important; }">
+    <link rel="stylesheet" type="application/javascript" href="data:text/css,%23red { color: red !important; }">
+    <link rel="stylesheet" href="data:text/css,%23yellow { color: yellow !important; }">
     <div style="color: green" id="blue">This text should be blue</div>
     <div style="color: green" id="red">This text should be green</div>
     <div style="color: green" id="yellow">This text should be yellow</div>
diff --git a/third_party/WebKit/LayoutTests/shadow-dom/link-title.html b/third_party/WebKit/LayoutTests/shadow-dom/link-title.html
index 94566b4c..7c0e36a 100644
--- a/third_party/WebKit/LayoutTests/shadow-dom/link-title.html
+++ b/third_party/WebKit/LayoutTests/shadow-dom/link-title.html
@@ -5,10 +5,10 @@
 <body>
   <div id="host">
     <template mode-data="open">
-      <link rel="stylesheet" title="preferred1" href="data:text/css,#shadowChild1 { color: green }">
-      <link rel="stylesheet" title="title1" href="data:text/css,#shadowChild2 { color: green }">
-      <link rel="alternate stylesheet" title="prefered1" href="data:text/css,#shadowChild3 { color: green }">
-      <link rel="alternate stylesheet" title="title1" href="data:text/css,#shadowChild4 { color: green }">
+      <link rel="stylesheet" title="preferred1" href="data:text/css,%23shadowChild1 { color: green }">
+      <link rel="stylesheet" title="title1" href="data:text/css,%23shadowChild2 { color: green }">
+      <link rel="alternate stylesheet" title="prefered1" href="data:text/css,%23shadowChild3 { color: green }">
+      <link rel="alternate stylesheet" title="title1" href="data:text/css,%23shadowChild4 { color: green }">
       <div id="shadowChild1"></div>
       <div id="shadowChild2"></div>
       <div id="shadowChild3"></div>
@@ -18,8 +18,8 @@
   <div id="bodyChild1"></div>
   <div id="bodyChild2"></div>
   <script>convertTemplatesToShadowRootsWithin(host);</script>
-  <link rel="stylesheet" title="preferred1" href="data:text/css,#bodyChild1 { color: green }">
-  <link rel="stylesheet" title="non-preferred" href="data:text/css,#bodyChild2 { color: green }">
+  <link rel="stylesheet" title="preferred1" href="data:text/css,%23bodyChild1 { color: green }">
+  <link rel="stylesheet" title="non-preferred" href="data:text/css,%23bodyChild2 { color: green }">
 </body>
 <script>
 function colorFor(elem) {
diff --git a/third_party/WebKit/LayoutTests/svg/custom/multiple-resource-cycles-expected.html b/third_party/WebKit/LayoutTests/svg/custom/multiple-resource-cycles-expected.html
new file mode 100644
index 0000000..1796ec5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/svg/custom/multiple-resource-cycles-expected.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<svg>
+  <rect width="80" height="80" x="10" y="10"
+        stroke="blue" fill="lightblue" stroke-width="20"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/svg/custom/multiple-resource-cycles.html b/third_party/WebKit/LayoutTests/svg/custom/multiple-resource-cycles.html
new file mode 100644
index 0000000..9a76f57
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/svg/custom/multiple-resource-cycles.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<svg>
+  <defs>
+    <pattern id="a" width="10" height="10" patternUnits="userSpaceOnUse">
+      <rect width="10" height="10" fill="url(#a) blue"/>
+    </pattern>
+    <pattern id="b" width="10" height="10" patternUnits="userSpaceOnUse">
+      <rect width="10" height="10" fill="url(#b) lightblue"/>
+    </pattern>
+  </defs>
+  <rect width="80" height="80" x="10" y="10"
+        stroke="url(#a) green" fill="url(#b) green" stroke-width="20"/>
+</svg>
diff --git a/third_party/WebKit/LayoutTests/svg/custom/object-data-href.html b/third_party/WebKit/LayoutTests/svg/custom/object-data-href.html
index 779dadc77..1025a23a 100644
--- a/third_party/WebKit/LayoutTests/svg/custom/object-data-href.html
+++ b/third_party/WebKit/LayoutTests/svg/custom/object-data-href.html
@@ -7,7 +7,7 @@
       <defs>
         <rect id='rect' width='100' height='100' fill='green'/>
       </defs>
-      <use xlink:href='#rect'/>
+      <use xlink:href='%23rect'/>
     </svg>
   "></object>
 </body>
diff --git a/third_party/WebKit/LayoutTests/virtual/threaded/external/wpt/offscreen-canvas/convert-to-blob/README.txt b/third_party/WebKit/LayoutTests/virtual/threaded/external/wpt/offscreen-canvas/convert-to-blob/README.txt
new file mode 100644
index 0000000..bf64c1a43
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/threaded/external/wpt/offscreen-canvas/convert-to-blob/README.txt
@@ -0,0 +1,5 @@
+# This suite runs the tests in virtual/threaded/external/wpt/offscreen-canvas/convert-to-blob
+# with --enable-threaded-compositing.  
+
+# Do not remove this virtual test suites unless idle tasks can be run in layout tests or
+# the test features are no longer implemented in idle tasks.
diff --git a/third_party/WebKit/Source/core/BUILD.gn b/third_party/WebKit/Source/core/BUILD.gn
index a7c223b..885dc76 100644
--- a/third_party/WebKit/Source/core/BUILD.gn
+++ b/third_party/WebKit/Source/core/BUILD.gn
@@ -2110,6 +2110,7 @@
     "workers/DedicatedWorkerTest.cpp",
     "workers/MainThreadWorkletTest.cpp",
     "workers/ThreadedWorkletTest.cpp",
+    "workers/WorkerFetchTestHelper.h",
     "workers/WorkerModuleFetchCoordinatorTest.cpp",
     "workers/WorkerThreadTest.cpp",
     "workers/WorkerThreadTestHelper.h",
diff --git a/third_party/WebKit/Source/core/css/properties/ComputedStyleUtils.cpp b/third_party/WebKit/Source/core/css/properties/ComputedStyleUtils.cpp
index 0b2dbbc8..9f4fb49 100644
--- a/third_party/WebKit/Source/core/css/properties/ComputedStyleUtils.cpp
+++ b/third_party/WebKit/Source/core/css/properties/ComputedStyleUtils.cpp
@@ -2023,9 +2023,10 @@
 CSSValue* ComputedStyleUtils::ValueForScrollSnapAlign(
     const ScrollSnapAlign& align,
     const ComputedStyle& style) {
-  return CSSValuePair::Create(CSSIdentifierValue::Create(align.alignmentX),
-                              CSSIdentifierValue::Create(align.alignmentY),
-                              CSSValuePair::kDropIdenticalValues);
+  return CSSValuePair::Create(
+      CSSIdentifierValue::Create(align.alignment_inline),
+      CSSIdentifierValue::Create(align.alignment_block),
+      CSSValuePair::kDropIdenticalValues);
 }
 
 // Returns a suitable value for the page-break-(before|after) property, given
diff --git a/third_party/WebKit/Source/core/css/resolver/StyleBuilderConverter.cpp b/third_party/WebKit/Source/core/css/resolver/StyleBuilderConverter.cpp
index 1e2d8ead..fc449fd 100644
--- a/third_party/WebKit/Source/core/css/resolver/StyleBuilderConverter.cpp
+++ b/third_party/WebKit/Source/core/css/resolver/StyleBuilderConverter.cpp
@@ -1523,14 +1523,14 @@
       ComputedStyleInitialValues::InitialScrollSnapAlign();
   if (value.IsValuePair()) {
     const CSSValuePair& pair = ToCSSValuePair(value);
-    snapAlign.alignmentX =
+    snapAlign.alignment_inline =
         ToCSSIdentifierValue(pair.First()).ConvertTo<SnapAlignment>();
-    snapAlign.alignmentY =
+    snapAlign.alignment_block =
         ToCSSIdentifierValue(pair.Second()).ConvertTo<SnapAlignment>();
   } else {
-    snapAlign.alignmentX =
+    snapAlign.alignment_inline =
         ToCSSIdentifierValue(value).ConvertTo<SnapAlignment>();
-    snapAlign.alignmentY = snapAlign.alignmentX;
+    snapAlign.alignment_block = snapAlign.alignment_inline;
   }
   return snapAlign;
 }
diff --git a/third_party/WebKit/Source/core/exported/WebDevToolsAgentImpl.cpp b/third_party/WebKit/Source/core/exported/WebDevToolsAgentImpl.cpp
index 6435ccba..a766407 100644
--- a/third_party/WebKit/Source/core/exported/WebDevToolsAgentImpl.cpp
+++ b/third_party/WebKit/Source/core/exported/WebDevToolsAgentImpl.cpp
@@ -111,7 +111,9 @@
          method == "Debugger.setBreakpointByUrl" ||
          method == "Debugger.removeBreakpoint" ||
          method == "Debugger.setBreakpointsActive" ||
-         method == "Performance.getMetrics" || method == "Page.crash";
+         method == "Performance.getMetrics" || method == "Page.crash" ||
+         method == "Runtime.terminateExecution" ||
+         method == "Emulation.setScriptExecutionDisabled";
 }
 
 }  // namespace
diff --git a/third_party/WebKit/Source/core/frame/LocalFrameViewTest.cpp b/third_party/WebKit/Source/core/frame/LocalFrameViewTest.cpp
index 06ef860..fe515e5 100644
--- a/third_party/WebKit/Source/core/frame/LocalFrameViewTest.cpp
+++ b/third_party/WebKit/Source/core/frame/LocalFrameViewTest.cpp
@@ -54,12 +54,13 @@
       : ScopedRootLayerScrollingForTest(GetParam()),
         RenderingTest(SingleChildLocalFrameClient::Create()),
         chrome_client_(new AnimationMockChromeClient) {
-    EXPECT_CALL(ChromeClient(), AttachRootGraphicsLayer(_, _))
+    EXPECT_CALL(GetAnimationMockChromeClient(), AttachRootGraphicsLayer(_, _))
         .Times(AnyNumber());
   }
 
   ~LocalFrameViewTest() {
-    ::testing::Mock::VerifyAndClearExpectations(&ChromeClient());
+    ::testing::Mock::VerifyAndClearExpectations(
+        &GetAnimationMockChromeClient());
   }
 
   ChromeClient& GetChromeClient() const override { return *chrome_client_; }
@@ -69,7 +70,9 @@
     EnableCompositing();
   }
 
-  AnimationMockChromeClient& ChromeClient() const { return *chrome_client_; }
+  AnimationMockChromeClient& GetAnimationMockChromeClient() const {
+    return *chrome_client_;
+  }
 
  private:
   Persistent<AnimationMockChromeClient> chrome_client_;
@@ -81,28 +84,28 @@
   SetBodyInnerHTML("<div id='a' style='color: blue'>A</div>");
   GetDocument().getElementById("a")->setAttribute(HTMLNames::styleAttr,
                                                   "color: green");
-  ChromeClient().has_scheduled_animation_ = false;
+  GetAnimationMockChromeClient().has_scheduled_animation_ = false;
   GetDocument().View()->UpdateAllLifecyclePhases();
-  EXPECT_FALSE(ChromeClient().has_scheduled_animation_);
+  EXPECT_FALSE(GetAnimationMockChromeClient().has_scheduled_animation_);
 }
 
 TEST_P(LocalFrameViewTest, SetPaintInvalidationOutOfUpdateAllLifecyclePhases) {
   SetBodyInnerHTML("<div id='a' style='color: blue'>A</div>");
-  ChromeClient().has_scheduled_animation_ = false;
+  GetAnimationMockChromeClient().has_scheduled_animation_ = false;
   GetDocument()
       .getElementById("a")
       ->GetLayoutObject()
       ->SetShouldDoFullPaintInvalidation();
-  EXPECT_TRUE(ChromeClient().has_scheduled_animation_);
-  ChromeClient().has_scheduled_animation_ = false;
+  EXPECT_TRUE(GetAnimationMockChromeClient().has_scheduled_animation_);
+  GetAnimationMockChromeClient().has_scheduled_animation_ = false;
   GetDocument()
       .getElementById("a")
       ->GetLayoutObject()
       ->SetShouldDoFullPaintInvalidation();
-  EXPECT_TRUE(ChromeClient().has_scheduled_animation_);
-  ChromeClient().has_scheduled_animation_ = false;
+  EXPECT_TRUE(GetAnimationMockChromeClient().has_scheduled_animation_);
+  GetAnimationMockChromeClient().has_scheduled_animation_ = false;
   GetDocument().View()->UpdateAllLifecyclePhases();
-  EXPECT_FALSE(ChromeClient().has_scheduled_animation_);
+  EXPECT_FALSE(GetAnimationMockChromeClient().has_scheduled_animation_);
 }
 
 // If we don't hide the tooltip on scroll, it can negatively impact scrolling
@@ -110,14 +113,14 @@
 TEST_P(LocalFrameViewTest, HideTooltipWhenScrollPositionChanges) {
   SetBodyInnerHTML("<div style='width:1000px;height:1000px'></div>");
 
-  EXPECT_CALL(ChromeClient(),
+  EXPECT_CALL(GetAnimationMockChromeClient(),
               MockSetToolTip(GetDocument().GetFrame(), String(), _));
   GetDocument().View()->LayoutViewportScrollableArea()->SetScrollOffset(
       ScrollOffset(1, 1), kUserScroll);
 
   // Programmatic scrolling should not dismiss the tooltip, so setToolTip
   // should not be called for this invocation.
-  EXPECT_CALL(ChromeClient(),
+  EXPECT_CALL(GetAnimationMockChromeClient(),
               MockSetToolTip(GetDocument().GetFrame(), String(), _))
       .Times(0);
   GetDocument().View()->LayoutViewportScrollableArea()->SetScrollOffset(
diff --git a/third_party/WebKit/Source/core/html/parser/HTMLPreloadScannerFuzzer.cpp b/third_party/WebKit/Source/core/html/parser/HTMLPreloadScannerFuzzer.cpp
index efd989c..9f4bbbbe6 100644
--- a/third_party/WebKit/Source/core/html/parser/HTMLPreloadScannerFuzzer.cpp
+++ b/third_party/WebKit/Source/core/html/parser/HTMLPreloadScannerFuzzer.cpp
@@ -8,6 +8,7 @@
 #include "core/html/parser/ResourcePreloader.h"
 #include "core/html/parser/TextResourceDecoderForFuzzing.h"
 #include "core/media_type_names.h"
+#include "platform/loader/SubresourceIntegrity.h"
 #include "platform/testing/BlinkFuzzerTestSupport.h"
 #include "platform/testing/FuzzedDataProvider.h"
 
@@ -23,6 +24,10 @@
   document_parameters->viewport_meta_zero_values_quirk =
       fuzzed_data.ConsumeBool();
   document_parameters->viewport_meta_enabled = fuzzed_data.ConsumeBool();
+  document_parameters->integrity_features =
+      fuzzed_data.ConsumeBool()
+          ? SubresourceIntegrity::IntegrityFeatures::kDefault
+          : SubresourceIntegrity::IntegrityFeatures::kSignatures;
   return document_parameters;
 }
 
diff --git a/third_party/WebKit/Source/core/imagebitmap/ImageBitmap.cpp b/third_party/WebKit/Source/core/imagebitmap/ImageBitmap.cpp
index a5e77b5..2f6d4fad 100644
--- a/third_party/WebKit/Source/core/imagebitmap/ImageBitmap.cpp
+++ b/third_party/WebKit/Source/core/imagebitmap/ImageBitmap.cpp
@@ -263,37 +263,69 @@
   if (skia_image->alphaType() == alpha_type)
     return image;
 
-  SkImageInfo info = GetSkImageInfo(image.get());
-  info = info.makeAlphaType(alpha_type);
+  // Premul/unpremul are performed in gamma-corrected space, using arithmetic
+  // that assumes linear space. It is an incorrect implementation that has
+  // become the de facto standard on the web. Therefore, the legacy behavior
+  // must be maintained to ensure backward compatibility. Historically, passing
+  // nullptr as the color space to SkImage::readPixels() enforces alpha
+  // disposition to be done in non-linear space, using arithmetic that assumes
+  // otherwise, but we want to avoid passing nullptr color space
+  // (crbug.com/811318). Therefore, to premul, we draw on a surface or use
+  // SkColorSpaceXform, and to unpremul, we read back the pixels and unpremul
+  // manually. These always result in a GPU readback, which cannot be avoided
+  // for now (crbug.com/740197).
 
-  // For premul to unpremul, we have to readback the pixels.
-  // For unpremul to premul, we can either readback the pixels or draw onto a
-  // surface. As shown  in
-  // https://fiddle.skia.org/c/1ec3c61ed08f7863d43b9f49ab120a0a, drawing on a
-  // surface and getting a snapshot is slower if the image is small. Therefore,
-  // for small images (< 128x128 pixels), we still do read back.
-  if (alpha_type == kUnpremul_SkAlphaType ||
-      (image->width() * image->height() < 16384)) {
-    // Set the color space of the ImageInfo to nullptr to unpremul in gamma
-    // encoded space
-    scoped_refptr<Uint8Array> dst_pixels =
-        CopyImageData(image, info.makeColorSpace(nullptr));
-    if (!dst_pixels)
-      return nullptr;
+  SkImageInfo info = GetSkImageInfo(image.get());
+  unsigned num_pixels = image->Size().Area();
+
+  if (alpha_type == kUnpremul_SkAlphaType) {
+    info = info.makeAlphaType(kUnpremul_SkAlphaType);
+    scoped_refptr<Uint8Array> dst_pixels = nullptr;
+    bool manual_unpremul_needed =
+        skia_image->colorSpace() && !skia_image->colorSpace()->gammaIsLinear();
+    if (manual_unpremul_needed) {
+      dst_pixels = CopyImageData(image);
+      if (!dst_pixels)
+        return nullptr;
+      // Unpremul manaually. This code assumes that if gamma is not linear,
+      // the pixel format is 8888. This is true for now since Skia does not
+      // support drawing wide gamut images with sRGB gamma curve.
+      // TODO(zakerinasab): Generalize this code to do manual unpremul on half
+      // floats. crbug.com/822724.
+      int alpha = 0;
+      for (unsigned i = 0; i < num_pixels; i++) {
+        alpha = dst_pixels->Data()[i * 4 + 3];
+        dst_pixels->Data()[i * 4] =
+            std::round(dst_pixels->Data()[i * 4] * 255.0 / alpha);
+        dst_pixels->Data()[i * 4 + 1] =
+            std::round(dst_pixels->Data()[i * 4 + 1] * 255.0 / alpha);
+        dst_pixels->Data()[i * 4 + 2] =
+            std::round(dst_pixels->Data()[i * 4 + 2] * 255.0 / alpha);
+      }
+    } else {
+      dst_pixels = CopyImageData(image, info);
+      if (!dst_pixels)
+        return nullptr;
+    }
     return StaticBitmapImage::Create(std::move(dst_pixels), info);
   }
 
-  // Draw on a surface. Avoid sRGB gamma transfer curve.
-  if (SkColorSpace::Equals(info.colorSpace(), SkColorSpace::MakeSRGB().get()))
-    info = info.makeColorSpace(nullptr);
-  sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
-  if (!surface)
+  // Use SkColorSpaceXform to premul. This code path supports 8888 and half
+  // float pixel stroage.
+  scoped_refptr<Uint8Array> dst_pixels = CopyImageData(image);
+  if (!dst_pixels)
     return nullptr;
-  SkPaint paint;
-  paint.setBlendMode(SkBlendMode::kSrc);
-  surface->getCanvas()->drawImage(skia_image.get(), 0, 0, &paint);
-  return StaticBitmapImage::Create(surface->makeImageSnapshot(),
-                                   image->ContextProviderWrapper());
+  SkColorSpace* color_space = SkColorSpace::MakeSRGBLinear().get();
+  SkColorSpaceXform::ColorFormat color_format =
+      SkColorSpaceXform::kRGBA_8888_ColorFormat;
+  if (info.colorType() == kRGBA_F16_SkColorType)
+    color_format = SkColorSpaceXform::kRGBA_F16_ColorFormat;
+  SkColorSpaceXform::Apply(color_space, color_format,
+                           (void*)(dst_pixels->Data()), color_space,
+                           color_format, (void*)(dst_pixels->Data()),
+                           num_pixels, SkColorSpaceXform::kPremul_AlphaOp);
+  info = info.makeAlphaType(kPremul_SkAlphaType);
+  return StaticBitmapImage::Create(std::move(dst_pixels), info);
 }
 
 void freePixels(const void*, void* pixels) {
diff --git a/third_party/WebKit/Source/core/input/PointerEventManagerTest.cpp b/third_party/WebKit/Source/core/input/PointerEventManagerTest.cpp
index 702d503..a6eb007 100644
--- a/third_party/WebKit/Source/core/input/PointerEventManagerTest.cpp
+++ b/third_party/WebKit/Source/core/input/PointerEventManagerTest.cpp
@@ -85,7 +85,7 @@
 
 class PointerEventManagerTest : public SimTest {
  protected:
-  EventHandler& EventHandler() {
+  EventHandler& GetEventHandler() {
     return GetDocument().GetFrame()->GetEventHandler();
   }
   WebPointerEvent CreateTestPointerEvent(
@@ -137,7 +137,7 @@
                              WebPointerProperties::PointerType::kPen),
       std::vector<WebPointerEvent>()));
 
-  EventHandler().HandleMousePressEvent(
+  GetEventHandler().HandleMousePressEvent(
       CreateTestMouseEvent(WebInputEvent::kMouseDown, WebFloatPoint(100, 100)));
 
   ASSERT_EQ(callback->mouseEventCount(), 0);
@@ -160,7 +160,7 @@
   ASSERT_EQ(callback->touchEventCount(), 1);
   ASSERT_EQ(callback->penEventCount(), 1);
 
-  EventHandler().HandleMouseMoveEvent(
+  GetEventHandler().HandleMouseMoveEvent(
       CreateTestMouseEvent(WebInputEvent::kMouseMove, WebFloatPoint(200, 200)),
       Vector<WebMouseEvent>());
 
diff --git a/third_party/WebKit/Source/core/input/TouchEventManagerTest.cpp b/third_party/WebKit/Source/core/input/TouchEventManagerTest.cpp
index e7dab52..dc6335c 100644
--- a/third_party/WebKit/Source/core/input/TouchEventManagerTest.cpp
+++ b/third_party/WebKit/Source/core/input/TouchEventManagerTest.cpp
@@ -16,7 +16,7 @@
 
 class TouchEventManagerTest : public SimTest {
  protected:
-  EventHandler& EventHandler() {
+  EventHandler& GetEventHandler() {
     return GetDocument().GetFrame()->GetEventHandler();
   }
 
@@ -67,22 +67,22 @@
   CheckEventListenerCallback* callback = CheckEventListenerCallback::Create();
   GetDocument().body()->addEventListener(EventTypeNames::touchstart, callback);
 
-  EventHandler().HandlePointerEvent(
+  GetEventHandler().HandlePointerEvent(
       CreateTouchPointerEvent(WebInputEvent::kPointerDown),
       Vector<WebPointerEvent>());
-  EventHandler().DispatchBufferedTouchEvents();
+  GetEventHandler().DispatchBufferedTouchEvents();
 
   GetDocument().getElementById("target")->remove();
 
-  EventHandler().HandlePointerEvent(
+  GetEventHandler().HandlePointerEvent(
       CreateTouchPointerEvent(WebInputEvent::kPointerUp),
       Vector<WebPointerEvent>());
-  EventHandler().DispatchBufferedTouchEvents();
+  GetEventHandler().DispatchBufferedTouchEvents();
 
-  EventHandler().HandlePointerEvent(
+  GetEventHandler().HandlePointerEvent(
       CreateTouchPointerEvent(WebInputEvent::kPointerDown),
       Vector<WebPointerEvent>());
-  EventHandler().DispatchBufferedTouchEvents();
+  GetEventHandler().DispatchBufferedTouchEvents();
 
   ASSERT_TRUE(callback->HasReceivedEvent());
 }
diff --git a/third_party/WebKit/Source/core/layout/LayoutBox.cpp b/third_party/WebKit/Source/core/layout/LayoutBox.cpp
index d956953d..b1cd4606 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBox.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBox.cpp
@@ -2801,10 +2801,16 @@
                                            LayoutUnit& margin_start,
                                            LayoutUnit& margin_end) const {
   DCHECK_GE(available_logical_width, 0);
-  margin_start =
-      MinimumValueForLength(Style()->MarginStart(), available_logical_width);
-  margin_end =
-      MinimumValueForLength(Style()->MarginEnd(), available_logical_width);
+
+  bool isOrthogonalElement =
+      IsHorizontalWritingMode() != ContainingBlock()->IsHorizontalWritingMode();
+  LayoutUnit available_size_for_resolving_margin =
+      isOrthogonalElement ? ContainingBlockLogicalWidthForContent()
+                          : available_logical_width;
+  margin_start = MinimumValueForLength(Style()->MarginStart(),
+                                       available_size_for_resolving_margin);
+  margin_end = MinimumValueForLength(Style()->MarginEnd(),
+                                     available_size_for_resolving_margin);
   LayoutUnit available = available_logical_width - margin_start - margin_end;
   available = std::max(available, LayoutUnit());
   return available;
diff --git a/third_party/WebKit/Source/core/layout/ScrollbarsTest.cpp b/third_party/WebKit/Source/core/layout/ScrollbarsTest.cpp
index 7e30287..b7ed309 100644
--- a/third_party/WebKit/Source/core/layout/ScrollbarsTest.cpp
+++ b/third_party/WebKit/Source/core/layout/ScrollbarsTest.cpp
@@ -40,7 +40,7 @@
     return WebView().CoreHitTestResultAt(WebPoint(x, y));
   }
 
-  EventHandler& EventHandler() {
+  EventHandler& GetEventHandler() {
     return GetDocument().GetFrame()->GetEventHandler();
   }
 
@@ -50,7 +50,7 @@
         WebPointerProperties::Button::kNoButton, 0, WebInputEvent::kNoModifiers,
         CurrentTimeTicksInSeconds());
     event.SetFrameScale(1);
-    EventHandler().HandleMouseMoveEvent(event, Vector<WebMouseEvent>());
+    GetEventHandler().HandleMouseMoveEvent(event, Vector<WebMouseEvent>());
   }
 
   void HandleMousePressEvent(int x, int y) {
@@ -59,7 +59,7 @@
         WebPointerProperties::Button::kLeft, 0,
         WebInputEvent::Modifiers::kLeftButtonDown, CurrentTimeTicksInSeconds());
     event.SetFrameScale(1);
-    EventHandler().HandleMousePressEvent(event);
+    GetEventHandler().HandleMousePressEvent(event);
   }
 
   void HandleMouseReleaseEvent(int x, int y) {
@@ -68,7 +68,7 @@
         WebPointerProperties::Button::kLeft, 0,
         WebInputEvent::Modifiers::kLeftButtonDown, CurrentTimeTicksInSeconds());
     event.SetFrameScale(1);
-    EventHandler().HandleMouseReleaseEvent(event);
+    GetEventHandler().HandleMouseReleaseEvent(event);
   }
 
   void HandleMouseLeaveEvent() {
@@ -77,7 +77,7 @@
         WebPointerProperties::Button::kLeft, 0,
         WebInputEvent::Modifiers::kLeftButtonDown, CurrentTimeTicksInSeconds());
     event.SetFrameScale(1);
-    EventHandler().HandleMouseLeaveEvent(event);
+    GetEventHandler().HandleMouseLeaveEvent(event);
   }
 
   Cursor::Type CursorType() {
@@ -450,8 +450,8 @@
   scroll_begin.data.scroll_begin.delta_x_hint = 0;
   scroll_begin.data.scroll_begin.delta_y_hint = 10;
   scroll_begin.SetFrameScale(1);
-  EventHandler().HandleGestureScrollEvent(scroll_begin);
-  DCHECK(!EventHandler().IsScrollbarHandlingGestures());
+  GetEventHandler().HandleGestureScrollEvent(scroll_begin);
+  DCHECK(!GetEventHandler().IsScrollbarHandlingGestures());
   bool should_update_capture = false;
   DCHECK(!scrollable_area->VerticalScrollbar()->GestureEvent(
       scroll_begin, &should_update_capture));
diff --git a/third_party/WebKit/Source/core/layout/svg/SVGResourcesCache.cpp b/third_party/WebKit/Source/core/layout/svg/SVGResourcesCache.cpp
index 5e2cc0a1..5e49704 100644
--- a/third_party/WebKit/Source/core/layout/svg/SVGResourcesCache.cpp
+++ b/third_party/WebKit/Source/core/layout/svg/SVGResourcesCache.cpp
@@ -50,12 +50,20 @@
   SVGResources* resources =
       cache_.Set(&object, std::move(new_resources)).stored_value->value.get();
 
+  HashSet<LayoutSVGResourceContainer*> resource_set;
+  resources->BuildSetOfResources(resource_set);
+
   // Run cycle-detection _afterwards_, so self-references can be caught as well.
-  SVGResourcesCycleSolver solver(&object, resources);
-  solver.ResolveCycles();
+  {
+    SVGResourcesCycleSolver solver(object);
+    for (auto* resource_container : resource_set) {
+      if (solver.FindCycle(resource_container))
+        resources->ClearReferencesTo(resource_container);
+    }
+    resource_set.clear();
+  }
 
   // Walk resources and register the layout object as a client of each resource.
-  HashSet<LayoutSVGResourceContainer*> resource_set;
   resources->BuildSetOfResources(resource_set);
 
   for (auto* resource_container : resource_set)
diff --git a/third_party/WebKit/Source/core/layout/svg/SVGResourcesCycleSolver.cpp b/third_party/WebKit/Source/core/layout/svg/SVGResourcesCycleSolver.cpp
index f33bd37e..7236c42 100644
--- a/third_party/WebKit/Source/core/layout/svg/SVGResourcesCycleSolver.cpp
+++ b/third_party/WebKit/Source/core/layout/svg/SVGResourcesCycleSolver.cpp
@@ -25,11 +25,10 @@
 
 namespace blink {
 
-SVGResourcesCycleSolver::SVGResourcesCycleSolver(LayoutObject* layout_object,
-                                                 SVGResources* resources)
-    : layout_object_(layout_object), resources_(resources) {
-  DCHECK(layout_object_);
-  DCHECK(resources_);
+SVGResourcesCycleSolver::SVGResourcesCycleSolver(LayoutObject& layout_object)
+    : layout_object_(layout_object) {
+  if (layout_object.IsSVGResourceContainer())
+    active_resources_.insert(ToLayoutSVGResourceContainer(&layout_object));
 }
 
 SVGResourcesCycleSolver::~SVGResourcesCycleSolver() = default;
@@ -47,13 +46,16 @@
   LayoutSVGResourceContainer* resource_;
 };
 
-bool SVGResourcesCycleSolver::ResourceContainsCycles(
+bool SVGResourcesCycleSolver::TraverseResourceContainer(
     LayoutSVGResourceContainer* resource) {
   // If we've traversed this sub-graph before and no cycles were observed, then
   // reuse that result.
   if (dag_cache_.Contains(resource))
     return false;
 
+  if (active_resources_.Contains(resource))
+    return true;
+
   ActiveFrame frame(active_resources_, resource);
 
   LayoutObject* node = resource;
@@ -64,18 +66,8 @@
       node = node->NextInPreOrderAfterChildren(resource);
       continue;
     }
-    if (SVGResources* node_resources =
-            SVGResourcesCache::CachedResourcesForLayoutObject(*node)) {
-      // Fetch all the resources referenced by |node|.
-      ResourceSet node_set;
-      node_resources->BuildSetOfResources(node_set);
-
-      // Iterate resources referenced by |node|.
-      for (auto* node : node_set) {
-        if (active_resources_.Contains(node) || ResourceContainsCycles(node))
-          return true;
-      }
-    }
+    if (TraverseResources(*node))
+      return true;
     node = node->NextInPreOrder(resource);
   }
 
@@ -84,27 +76,33 @@
   return false;
 }
 
-void SVGResourcesCycleSolver::ResolveCycles() {
-  DCHECK(active_resources_.IsEmpty());
+bool SVGResourcesCycleSolver::TraverseResources(LayoutObject& layout_object) {
+  SVGResources* resources =
+      SVGResourcesCache::CachedResourcesForLayoutObject(layout_object);
+  return resources && TraverseResources(resources);
+}
 
-  // If the starting LayoutObject is a resource container itself, then add it
-  // to the active set (to break direct self-references.)
-  if (layout_object_->IsSVGResourceContainer())
-    active_resources_.insert(ToLayoutSVGResourceContainer(layout_object_));
-
+bool SVGResourcesCycleSolver::TraverseResources(SVGResources* resources) {
+  // Fetch all the referenced resources.
   ResourceSet local_resources;
-  resources_->BuildSetOfResources(local_resources);
+  resources->BuildSetOfResources(local_resources);
 
   // This performs a depth-first search for a back-edge in all the
-  // (potentially disjoint) graphs formed by the resources referenced by
-  // |m_layoutObject|.
+  // (potentially disjoint) graphs formed by the referenced resources.
   for (auto* local_resource : local_resources) {
-    if (active_resources_.Contains(local_resource) ||
-        ResourceContainsCycles(local_resource))
-      resources_->ClearReferencesTo(local_resource);
+    if (TraverseResourceContainer(local_resource))
+      return true;
   }
+  return false;
+}
 
-  active_resources_.clear();
+bool SVGResourcesCycleSolver::FindCycle(
+    LayoutSVGResourceContainer* start_node) {
+  DCHECK(active_resources_.IsEmpty() ||
+         (active_resources_.size() == 1 &&
+          active_resources_.Contains(
+              ToLayoutSVGResourceContainer(&layout_object_))));
+  return TraverseResourceContainer(start_node);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/layout/svg/SVGResourcesCycleSolver.h b/third_party/WebKit/Source/core/layout/svg/SVGResourcesCycleSolver.h
index 95769471..b9c7c634 100644
--- a/third_party/WebKit/Source/core/layout/svg/SVGResourcesCycleSolver.h
+++ b/third_party/WebKit/Source/core/layout/svg/SVGResourcesCycleSolver.h
@@ -30,22 +30,29 @@
 class LayoutSVGResourceContainer;
 class SVGResources;
 
+// This class traverses the graph formed by SVGResources of
+// LayoutObjects, maintaining the active path as LayoutObjects are
+// visited. It also maintains a cache of sub-graphs that has already
+// been visited and that does not contain any cycles.
 class SVGResourcesCycleSolver {
   STACK_ALLOCATED();
 
  public:
-  SVGResourcesCycleSolver(LayoutObject*, SVGResources*);
+  SVGResourcesCycleSolver(LayoutObject&);
   ~SVGResourcesCycleSolver();
 
-  void ResolveCycles();
+  // Traverse the graph starting at the resource container
+  // passed. Returns true if a cycle is detected.
+  bool FindCycle(LayoutSVGResourceContainer*);
 
   typedef HashSet<LayoutSVGResourceContainer*> ResourceSet;
 
  private:
-  bool ResourceContainsCycles(LayoutSVGResourceContainer*);
+  bool TraverseResourceContainer(LayoutSVGResourceContainer*);
+  bool TraverseResources(LayoutObject&);
+  bool TraverseResources(SVGResources*);
 
-  LayoutObject* layout_object_;
-  SVGResources* resources_;
+  LayoutObject& layout_object_;
 
   ResourceSet active_resources_;
   ResourceSet dag_cache_;
diff --git a/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp b/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
index 64d618c..d7676d24 100644
--- a/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
+++ b/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
@@ -872,7 +872,7 @@
 
     // Check if |url| is allowed to run JavaScript. If not, client hints are not
     // attached to the requests that initiate on the render side.
-    if (!AllowScriptFromSource(request.Url())) {
+    if (!AllowScriptFromSourceWithoutNotifying(request.Url())) {
       return;
     }
 
@@ -980,11 +980,20 @@
 }
 
 bool FrameFetchContext::AllowScriptFromSource(const KURL& url) const {
+  if (AllowScriptFromSourceWithoutNotifying(url))
+    return true;
+  ContentSettingsClient* settings_client = GetContentSettingsClient();
+  if (settings_client)
+    settings_client->DidNotAllowScript();
+  return false;
+}
+
+bool FrameFetchContext::AllowScriptFromSourceWithoutNotifying(
+    const KURL& url) const {
   ContentSettingsClient* settings_client = GetContentSettingsClient();
   Settings* settings = GetSettings();
   if (settings_client && !settings_client->AllowScriptFromSource(
                              !settings || settings->GetScriptEnabled(), url)) {
-    settings_client->DidNotAllowScript();
     return false;
   }
   return true;
@@ -1233,7 +1242,7 @@
   if (persist_duration.InSeconds() <= 0)
     return;
 
-  if (!AllowScriptFromSource(response.Url())) {
+  if (!AllowScriptFromSourceWithoutNotifying(response.Url())) {
     // Do not persist client hint preferences if the JavaScript is disabled.
     return;
   }
diff --git a/third_party/WebKit/Source/core/loader/FrameFetchContext.h b/third_party/WebKit/Source/core/loader/FrameFetchContext.h
index fc6bff4..d2a54fd 100644
--- a/third_party/WebKit/Source/core/loader/FrameFetchContext.h
+++ b/third_party/WebKit/Source/core/loader/FrameFetchContext.h
@@ -240,6 +240,14 @@
   void ParseAndPersistClientHints(const ResourceResponse&);
   void SetFirstPartyCookieAndRequestorOrigin(ResourceRequest&);
 
+  // Returns true if execution of scripts from the url are allowed. Compared to
+  // AllowScriptFromSource(), this method does not generate any
+  // notification to the |ContentSettingsClient| that the execution of the
+  // script was blocked. This method should be called only when there is a need
+  // to check the settings, and where blocked setting doesn't really imply that
+  // JavaScript was blocked from being executed.
+  bool AllowScriptFromSourceWithoutNotifying(const KURL&) const;
+
   Member<DocumentLoader> document_loader_;
   Member<Document> document_;
 
diff --git a/third_party/WebKit/Source/core/page/scrolling/SnapCoordinator.cpp b/third_party/WebKit/Source/core/page/scrolling/SnapCoordinator.cpp
index 1226589c..7920ff88 100644
--- a/third_party/WebKit/Source/core/page/scrolling/SnapCoordinator.cpp
+++ b/third_party/WebKit/Source/core/page/scrolling/SnapCoordinator.cpp
@@ -47,8 +47,8 @@
 void SnapCoordinator::SnapAreaDidChange(LayoutBox& snap_area,
                                         ScrollSnapAlign scroll_snap_align) {
   LayoutBox* old_container = snap_area.SnapContainer();
-  if (scroll_snap_align.alignmentX == SnapAlignment::kNone &&
-      scroll_snap_align.alignmentY == SnapAlignment::kNone) {
+  if (scroll_snap_align.alignment_inline == SnapAlignment::kNone &&
+      scroll_snap_align.alignment_block == SnapAlignment::kNone) {
     snap_area.SetSnapContainer(nullptr);
     if (old_container)
       UpdateSnapContainerData(*old_container);
@@ -82,8 +82,6 @@
              : layout_box.GetScrollableArea();
 }
 
-// TODO(sunyunjia): Needs to add layout test for vertical writing mode.
-// https://crbug.com/821645
 static ScrollSnapType GetPhysicalSnapType(const LayoutBox& snap_container) {
   ScrollSnapType scroll_snap_type = snap_container.Style()->GetScrollSnapType();
   if (scroll_snap_type.axis == SnapAxis::kInline) {
@@ -274,11 +272,18 @@
     const ComputedStyle& area_style,
     const ComputedStyle& container_style) {
   ScrollSnapAlign align = area_style.GetScrollSnapAlign();
+  if (container_style.IsHorizontalWritingMode())
+    return align;
+
+  SnapAlignment tmp = align.alignment_inline;
+  align.alignment_inline = align.alignment_block;
+  align.alignment_block = tmp;
+
   if (container_style.IsFlippedBlocksWritingMode()) {
-    if (align.alignmentX == SnapAlignment::kStart) {
-      align.alignmentX = SnapAlignment::kEnd;
-    } else if (align.alignmentX == SnapAlignment::kEnd) {
-      align.alignmentX = SnapAlignment::kStart;
+    if (align.alignment_inline == SnapAlignment::kStart) {
+      align.alignment_inline = SnapAlignment::kEnd;
+    } else if (align.alignment_inline == SnapAlignment::kEnd) {
+      align.alignment_inline = SnapAlignment::kStart;
     }
   }
   return align;
@@ -294,12 +299,12 @@
 }
 
 static SnapAxis ToSnapAxis(ScrollSnapAlign align) {
-  if (align.alignmentX != SnapAlignment::kNone &&
-      align.alignmentY != SnapAlignment::kNone)
+  if (align.alignment_inline != SnapAlignment::kNone &&
+      align.alignment_block != SnapAlignment::kNone)
     return SnapAxis::kBoth;
 
-  if (align.alignmentX != SnapAlignment::kNone &&
-      align.alignmentY == SnapAlignment::kNone)
+  if (align.alignment_inline != SnapAlignment::kNone &&
+      align.alignment_block == SnapAlignment::kNone)
     return SnapAxis::kX;
 
   return SnapAxis::kY;
@@ -373,9 +378,9 @@
   ScrollSnapAlign align = GetPhysicalAlignment(*area_style, *container_style);
 
   snap_area_data.snap_position.set_x(CalculateSnapPosition(
-      align.alignmentX, SearchAxis::kX, container, max_position, area));
+      align.alignment_inline, SearchAxis::kX, container, max_position, area));
   snap_area_data.snap_position.set_y(CalculateSnapPosition(
-      align.alignmentY, SearchAxis::kY, container, max_position, area));
+      align.alignment_block, SearchAxis::kY, container, max_position, area));
 
   snap_area_data.snap_axis = ToSnapAxis(align);
 
diff --git a/third_party/WebKit/Source/core/workers/DedicatedWorkerObjectProxy.cpp b/third_party/WebKit/Source/core/workers/DedicatedWorkerObjectProxy.cpp
index 67a73e7..0db54e8 100644
--- a/third_party/WebKit/Source/core/workers/DedicatedWorkerObjectProxy.cpp
+++ b/third_party/WebKit/Source/core/workers/DedicatedWorkerObjectProxy.cpp
@@ -115,7 +115,7 @@
   worker_global_scope_ = ToWorkerGlobalScope(global_scope);
 }
 
-void DedicatedWorkerObjectProxy::DidEvaluateWorkerScript(bool success) {
+void DedicatedWorkerObjectProxy::DidEvaluateClassicScript(bool success) {
   PostCrossThreadTask(
       *GetParentFrameTaskRunners()->Get(TaskType::kUnspecedTimer), FROM_HERE,
       CrossThreadBind(&DedicatedWorkerMessagingProxy::DidEvaluateScript,
diff --git a/third_party/WebKit/Source/core/workers/DedicatedWorkerObjectProxy.h b/third_party/WebKit/Source/core/workers/DedicatedWorkerObjectProxy.h
index 399ac88f..38625fd 100644
--- a/third_party/WebKit/Source/core/workers/DedicatedWorkerObjectProxy.h
+++ b/third_party/WebKit/Source/core/workers/DedicatedWorkerObjectProxy.h
@@ -79,7 +79,7 @@
                        std::unique_ptr<SourceLocation>,
                        int exception_id) override;
   void DidCreateWorkerGlobalScope(WorkerOrWorkletGlobalScope*) override;
-  void DidEvaluateWorkerScript(bool success) override;
+  void DidEvaluateClassicScript(bool success) override;
   void DidEvaluateModuleScript(bool success) override;
   void WillDestroyWorkerGlobalScope() override;
 
diff --git a/third_party/WebKit/Source/core/workers/SharedWorkerReportingProxy.h b/third_party/WebKit/Source/core/workers/SharedWorkerReportingProxy.h
index a7797d6..781a8f9 100644
--- a/third_party/WebKit/Source/core/workers/SharedWorkerReportingProxy.h
+++ b/third_party/WebKit/Source/core/workers/SharedWorkerReportingProxy.h
@@ -35,7 +35,7 @@
                             const String& message,
                             SourceLocation*) override;
   void PostMessageToPageInspector(int session_id, const WTF::String&) override;
-  void DidEvaluateWorkerScript(bool success) override {}
+  void DidEvaluateClassicScript(bool success) override {}
   void DidCloseWorkerGlobalScope() override;
   void WillDestroyWorkerGlobalScope() override {}
   void DidTerminateWorkerThread() override;
diff --git a/third_party/WebKit/Source/core/workers/WorkerFetchTestHelper.h b/third_party/WebKit/Source/core/workers/WorkerFetchTestHelper.h
new file mode 100644
index 0000000..cdb3ea6
--- /dev/null
+++ b/third_party/WebKit/Source/core/workers/WorkerFetchTestHelper.h
@@ -0,0 +1,49 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WorkerFetchTestHelper_h
+#define WorkerFetchTestHelper_h
+
+#include "core/loader/modulescript/ModuleScriptCreationParams.h"
+#include "core/workers/WorkerOrWorkletModuleFetchCoordinator.h"
+#include "platform/loader/fetch/ResourceFetcher.h"
+#include "platform/loader/testing/FetchTestingPlatformSupport.h"
+#include "platform/testing/TestingPlatformSupport.h"
+#include "platform/testing/UnitTestHelpers.h"
+#include "platform/wtf/Optional.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace blink {
+
+class ClientImpl final : public GarbageCollectedFinalized<ClientImpl>,
+                         public WorkerOrWorkletModuleFetchCoordinator::Client {
+  USING_GARBAGE_COLLECTED_MIXIN(ClientImpl);
+
+ public:
+  enum class Result { kInitial, kOK, kFailed };
+
+  void OnFetched(const ModuleScriptCreationParams& params) override {
+    ASSERT_EQ(Result::kInitial, result_);
+    result_ = Result::kOK;
+    params_.emplace(params);
+  }
+
+  void OnFailed() override {
+    ASSERT_EQ(Result::kInitial, result_);
+    result_ = Result::kFailed;
+  }
+
+  Result GetResult() const { return result_; }
+  WTF::Optional<ModuleScriptCreationParams> GetParams() const {
+    return params_;
+  }
+
+ private:
+  Result result_ = Result::kInitial;
+  WTF::Optional<ModuleScriptCreationParams> params_;
+};
+
+}  // namespace blink
+
+#endif  // WorkerFetchTestHelper_h
diff --git a/third_party/WebKit/Source/core/workers/WorkerGlobalScope.cpp b/third_party/WebKit/Source/core/workers/WorkerGlobalScope.cpp
index c96ac13..b40fafd 100644
--- a/third_party/WebKit/Source/core/workers/WorkerGlobalScope.cpp
+++ b/third_party/WebKit/Source/core/workers/WorkerGlobalScope.cpp
@@ -182,7 +182,7 @@
     SingleCachedMetadataHandler* handler(
         CreateWorkerScriptCachedMetadataHandler(complete_url,
                                                 cached_meta_data.get()));
-    ReportingProxy().WillEvaluateImportedScript(
+    ReportingProxy().WillEvaluateImportedClassicScript(
         source_code.length(), cached_meta_data ? cached_meta_data->size() : 0);
     ScriptController()->Evaluate(
         ScriptSourceCode(source_code, ScriptSourceLocationType::kUnknown,
@@ -311,14 +311,14 @@
       CreateWorkerScriptCachedMetadataHandler(script_url,
                                               cached_meta_data.get());
   DCHECK(!source_code.IsNull());
-  ReportingProxy().WillEvaluateWorkerScript(
+  ReportingProxy().WillEvaluateClassicScript(
       source_code.length(),
       cached_meta_data.get() ? cached_meta_data->size() : 0);
   bool success = ScriptController()->Evaluate(
       ScriptSourceCode(source_code, ScriptSourceLocationType::kUnknown, handler,
                        script_url),
       nullptr /* error_event */, v8_cache_options_);
-  ReportingProxy().DidEvaluateWorkerScript(success);
+  ReportingProxy().DidEvaluateClassicScript(success);
 }
 
 void WorkerGlobalScope::ImportModuleScript(
diff --git a/third_party/WebKit/Source/core/workers/WorkerModuleFetchCoordinatorTest.cpp b/third_party/WebKit/Source/core/workers/WorkerModuleFetchCoordinatorTest.cpp
index c3875c20..7d7efd9c 100644
--- a/third_party/WebKit/Source/core/workers/WorkerModuleFetchCoordinatorTest.cpp
+++ b/third_party/WebKit/Source/core/workers/WorkerModuleFetchCoordinatorTest.cpp
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "core/loader/modulescript/ModuleScriptCreationParams.h"
+#include "core/workers/WorkerFetchTestHelper.h"
 #include "core/workers/WorkerModuleFetchCoordinator.h"
 #include "platform/loader/fetch/ResourceFetcher.h"
 #include "platform/loader/testing/FetchTestingPlatformSupport.h"
@@ -18,38 +19,6 @@
 
 namespace blink {
 
-namespace {
-
-class ClientImpl final : public GarbageCollectedFinalized<ClientImpl>,
-                         public WorkerOrWorkletModuleFetchCoordinator::Client {
-  USING_GARBAGE_COLLECTED_MIXIN(ClientImpl);
-
- public:
-  enum class Result { kInitial, kOK, kFailed };
-
-  void OnFetched(const ModuleScriptCreationParams& params) override {
-    ASSERT_EQ(Result::kInitial, result_);
-    result_ = Result::kOK;
-    params_.emplace(params);
-  }
-
-  void OnFailed() override {
-    ASSERT_EQ(Result::kInitial, result_);
-    result_ = Result::kFailed;
-  }
-
-  Result GetResult() const { return result_; }
-  WTF::Optional<ModuleScriptCreationParams> GetParams() const {
-    return params_;
-  }
-
- private:
-  Result result_ = Result::kInitial;
-  WTF::Optional<ModuleScriptCreationParams> params_;
-};
-
-}  // namespace
-
 class WorkerModuleFetchCoordinatorTest : public ::testing::Test {
  public:
   WorkerModuleFetchCoordinatorTest() = default;
diff --git a/third_party/WebKit/Source/core/workers/WorkerModuleTreeClient.cpp b/third_party/WebKit/Source/core/workers/WorkerModuleTreeClient.cpp
index 52fade3..636878e0 100644
--- a/third_party/WebKit/Source/core/workers/WorkerModuleTreeClient.cpp
+++ b/third_party/WebKit/Source/core/workers/WorkerModuleTreeClient.cpp
@@ -33,8 +33,6 @@
   // algorithm's asynchronous completion, with script being the asynchronous
   // completion value." ...
 
-  // TODO(nhiroki): Call WorkerReportingProxy::WillEvaluateWorkerScript() or
-  // something like that (e.g., WillEvaluateModuleScript()).
   ScriptValue error = modulator_->ExecuteModule(
       module_script, Modulator::CaptureEvalErrorFlag::kReport);
   WorkerGlobalScope* global_scope =
diff --git a/third_party/WebKit/Source/core/workers/WorkerReportingProxy.h b/third_party/WebKit/Source/core/workers/WorkerReportingProxy.h
index bee1bf1a..bf6a2dbf 100644
--- a/third_party/WebKit/Source/core/workers/WorkerReportingProxy.h
+++ b/third_party/WebKit/Source/core/workers/WorkerReportingProxy.h
@@ -79,30 +79,24 @@
   // This may block until CSP/ReferrerPolicy are set on the main thread
   // since they are required for script evaluation, which happens soon after
   // this function is called.
-  // Called before WillEvaluateWorkerScript().
+  // Called before WillEvaluateClassicScript().
   virtual void DidLoadInstalledScript(
       const ContentSecurityPolicyResponseHeaders&,
       const String& referrer_policy_on_worker_thread) {}
 
-  // [classic script only]
-  // Invoked when the worker script is about to be evaluated on
-  // WorkerThread::InitializeOnWorkerThread.
-  virtual void WillEvaluateWorkerScript(size_t script_size,
-                                        size_t cached_metadata_size) {}
+  // Invoked when the main classic script is about to be evaluated.
+  virtual void WillEvaluateClassicScript(size_t script_size,
+                                         size_t cached_metadata_size) {}
 
-  // [classic script only]
-  // Invoked when an imported script is about to be evaluated.
-  virtual void WillEvaluateImportedScript(size_t script_size,
-                                          size_t cached_metadata_size) {}
+  // Invoked when an imported classic script is about to be evaluated.
+  virtual void WillEvaluateImportedClassicScript(size_t script_size,
+                                                 size_t cached_metadata_size) {}
 
-  // [classic script only]
-  // Invoked when the worker script is evaluated on
-  // WorkerThread::InitializeOnWorkerThread. |success| is true if the evaluation
-  // completed with no uncaught exception.
-  virtual void DidEvaluateWorkerScript(bool success) {}
+  // Invoked when the main classic script is evaluated. |success| is true if the
+  // evaluation completed with no uncaught exception.
+  virtual void DidEvaluateClassicScript(bool success) {}
 
-  // [module script only]
-  // Invoked when the module script is evaluated. |success| is true if the
+  // Invoked when the main module script is evaluated. |success| is true if the
   // evaluation completed with no uncaught exception.
   virtual void DidEvaluateModuleScript(bool success) {}
 
diff --git a/third_party/WebKit/Source/core/workers/WorkerThreadTest.cpp b/third_party/WebKit/Source/core/workers/WorkerThreadTest.cpp
index 5c6a40a2..920bbc3 100644
--- a/third_party/WebKit/Source/core/workers/WorkerThreadTest.cpp
+++ b/third_party/WebKit/Source/core/workers/WorkerThreadTest.cpp
@@ -34,17 +34,17 @@
 
   MOCK_METHOD1(DidCreateWorkerGlobalScope, void(WorkerOrWorkletGlobalScope*));
   MOCK_METHOD0(DidInitializeWorkerContext, void());
-  MOCK_METHOD2(WillEvaluateWorkerScriptMock,
+  MOCK_METHOD2(WillEvaluateClassicScriptMock,
                void(size_t scriptSize, size_t cachedMetadataSize));
-  MOCK_METHOD1(DidEvaluateWorkerScript, void(bool success));
+  MOCK_METHOD1(DidEvaluateClassicScript, void(bool success));
   MOCK_METHOD0(DidCloseWorkerGlobalScope, void());
   MOCK_METHOD0(WillDestroyWorkerGlobalScope, void());
   MOCK_METHOD0(DidTerminateWorkerThread, void());
 
-  void WillEvaluateWorkerScript(size_t script_size,
-                                size_t cached_metadata_size) override {
+  void WillEvaluateClassicScript(size_t script_size,
+                                 size_t cached_metadata_size) override {
     script_evaluation_event_.Signal();
-    WillEvaluateWorkerScriptMock(script_size, cached_metadata_size);
+    WillEvaluateClassicScriptMock(script_size, cached_metadata_size);
   }
 
   void WaitUntilScriptEvaluation() { script_evaluation_event_.Wait(); }
@@ -108,8 +108,9 @@
   void ExpectReportingCalls() {
     EXPECT_CALL(*reporting_proxy_, DidCreateWorkerGlobalScope(_)).Times(1);
     EXPECT_CALL(*reporting_proxy_, DidInitializeWorkerContext()).Times(1);
-    EXPECT_CALL(*reporting_proxy_, WillEvaluateWorkerScriptMock(_, _)).Times(1);
-    EXPECT_CALL(*reporting_proxy_, DidEvaluateWorkerScript(true)).Times(1);
+    EXPECT_CALL(*reporting_proxy_, WillEvaluateClassicScriptMock(_, _))
+        .Times(1);
+    EXPECT_CALL(*reporting_proxy_, DidEvaluateClassicScript(true)).Times(1);
     EXPECT_CALL(*reporting_proxy_, WillDestroyWorkerGlobalScope()).Times(1);
     EXPECT_CALL(*reporting_proxy_, DidTerminateWorkerThread()).Times(1);
     EXPECT_CALL(*lifecycle_observer_, ContextDestroyed(_)).Times(1);
@@ -119,9 +120,10 @@
     EXPECT_CALL(*reporting_proxy_, DidCreateWorkerGlobalScope(_)).Times(1);
     EXPECT_CALL(*reporting_proxy_, DidInitializeWorkerContext())
         .Times(AtMost(1));
-    EXPECT_CALL(*reporting_proxy_, WillEvaluateWorkerScriptMock(_, _))
+    EXPECT_CALL(*reporting_proxy_, WillEvaluateClassicScriptMock(_, _))
         .Times(AtMost(1));
-    EXPECT_CALL(*reporting_proxy_, DidEvaluateWorkerScript(_)).Times(AtMost(1));
+    EXPECT_CALL(*reporting_proxy_, DidEvaluateClassicScript(_))
+        .Times(AtMost(1));
     EXPECT_CALL(*reporting_proxy_, WillDestroyWorkerGlobalScope())
         .Times(AtMost(1));
     EXPECT_CALL(*reporting_proxy_, DidTerminateWorkerThread()).Times(1);
@@ -131,8 +133,9 @@
   void ExpectReportingCallsForWorkerForciblyTerminated() {
     EXPECT_CALL(*reporting_proxy_, DidCreateWorkerGlobalScope(_)).Times(1);
     EXPECT_CALL(*reporting_proxy_, DidInitializeWorkerContext()).Times(1);
-    EXPECT_CALL(*reporting_proxy_, WillEvaluateWorkerScriptMock(_, _)).Times(1);
-    EXPECT_CALL(*reporting_proxy_, DidEvaluateWorkerScript(false)).Times(1);
+    EXPECT_CALL(*reporting_proxy_, WillEvaluateClassicScriptMock(_, _))
+        .Times(1);
+    EXPECT_CALL(*reporting_proxy_, DidEvaluateClassicScript(false)).Times(1);
     EXPECT_CALL(*reporting_proxy_, WillDestroyWorkerGlobalScope()).Times(1);
     EXPECT_CALL(*reporting_proxy_, DidTerminateWorkerThread()).Times(1);
     EXPECT_CALL(*lifecycle_observer_, ContextDestroyed(_)).Times(1);
diff --git a/third_party/WebKit/Source/core/workers/WorkletModuleResponsesMapTest.cpp b/third_party/WebKit/Source/core/workers/WorkletModuleResponsesMapTest.cpp
index 153a9f4..d0329f54 100644
--- a/third_party/WebKit/Source/core/workers/WorkletModuleResponsesMapTest.cpp
+++ b/third_party/WebKit/Source/core/workers/WorkletModuleResponsesMapTest.cpp
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "core/loader/modulescript/ModuleScriptCreationParams.h"
+#include "core/workers/WorkerFetchTestHelper.h"
 #include "core/workers/WorkletModuleResponsesMap.h"
 #include "platform/loader/testing/FetchTestingPlatformSupport.h"
 #include "platform/loader/testing/MockFetchContext.h"
@@ -17,38 +18,6 @@
 
 namespace blink {
 
-namespace {
-
-class ClientImpl final : public GarbageCollectedFinalized<ClientImpl>,
-                         public WorkerOrWorkletModuleFetchCoordinator::Client {
-  USING_GARBAGE_COLLECTED_MIXIN(ClientImpl);
-
- public:
-  enum class Result { kInitial, kOK, kFailed };
-
-  void OnFetched(const ModuleScriptCreationParams& params) override {
-    ASSERT_EQ(Result::kInitial, result_);
-    result_ = Result::kOK;
-    params_.emplace(params);
-  }
-
-  void OnFailed() override {
-    ASSERT_EQ(Result::kInitial, result_);
-    result_ = Result::kFailed;
-  }
-
-  Result GetResult() const { return result_; }
-  WTF::Optional<ModuleScriptCreationParams> GetParams() const {
-    return params_;
-  }
-
- private:
-  Result result_ = Result::kInitial;
-  WTF::Optional<ModuleScriptCreationParams> params_;
-};
-
-}  // namespace
-
 class WorkletModuleResponsesMapTest : public ::testing::Test {
  public:
   WorkletModuleResponsesMapTest() = default;
diff --git a/third_party/WebKit/Source/core/workers/WorkletModuleTreeClient.cpp b/third_party/WebKit/Source/core/workers/WorkletModuleTreeClient.cpp
index 0f815ba9..57133e0 100644
--- a/third_party/WebKit/Source/core/workers/WorkletModuleTreeClient.cpp
+++ b/third_party/WebKit/Source/core/workers/WorkletModuleTreeClient.cpp
@@ -54,8 +54,6 @@
     return;
   }
 
-  // TODO(nhiroki): Call WorkerReportingProxy::WillEvaluateWorkerScript() or
-  // something like that (e.g., WillEvaluateModuleScript()).
   // Step 4: "Run a module script given script."
   ScriptValue error = modulator_->ExecuteModule(
       module_script, Modulator::CaptureEvalErrorFlag::kReport);
diff --git a/third_party/WebKit/Source/modules/credentialmanager/CredentialsContainerTest.cpp b/third_party/WebKit/Source/modules/credentialmanager/CredentialsContainerTest.cpp
index 4d66019..e3350f6d 100644
--- a/third_party/WebKit/Source/modules/credentialmanager/CredentialsContainerTest.cpp
+++ b/third_party/WebKit/Source/modules/credentialmanager/CredentialsContainerTest.cpp
@@ -109,9 +109,9 @@
             WTF::Unretained(mock_credential_manager)));
   }
 
-  Document* Document() { return &dummy_context_.GetDocument(); }
+  Document* GetDocument() { return &dummy_context_.GetDocument(); }
   LocalFrame* Frame() { return &dummy_context_.GetFrame(); }
-  ScriptState* ScriptState() { return dummy_context_.GetScriptState(); }
+  ScriptState* GetScriptState() { return dummy_context_.GetScriptState(); }
 
  private:
   V8TestingScope dummy_context_;
@@ -128,8 +128,8 @@
 
   {
     CredentialManagerTestingContext context(&mock_credential_manager);
-    weak_document = context.Document();
-    CredentialsContainer::Create()->get(context.ScriptState(),
+    weak_document = context.GetDocument();
+    CredentialsContainer::Create()->get(context.GetScriptState(),
                                         CredentialRequestOptions());
     mock_credential_manager.WaitForCallToGet();
   }
@@ -150,12 +150,12 @@
   MockCredentialManager mock_credential_manager;
   CredentialManagerTestingContext context(&mock_credential_manager);
 
-  auto* proxy = CredentialManagerProxy::From(context.Document());
+  auto* proxy = CredentialManagerProxy::From(context.GetDocument());
   auto promise = CredentialsContainer::Create()->get(
-      context.ScriptState(), CredentialRequestOptions());
+      context.GetScriptState(), CredentialRequestOptions());
   mock_credential_manager.WaitForCallToGet();
 
-  context.Document()->Shutdown();
+  context.GetDocument()->Shutdown();
 
   mock_credential_manager.InvokeGetCallback();
   proxy->FlushCredentialManagerConnectionForTesting();
diff --git a/third_party/WebKit/Source/modules/media_controls/resources/modernMediaControls.css b/third_party/WebKit/Source/modules/media_controls/resources/modernMediaControls.css
index 68220a8..f8f433f 100644
--- a/third_party/WebKit/Source/modules/media_controls/resources/modernMediaControls.css
+++ b/third_party/WebKit/Source/modules/media_controls/resources/modernMediaControls.css
@@ -625,9 +625,15 @@
   background: #F1F3F4;
 }
 
+.state-no-metadata input[pseudo="-webkit-media-controls-timeline" i]::-webkit-slider-thumb,
+.state-no-metadata div[pseudo="-webkit-media-controls-current-time-display" i] {
+  display: none;
+}
+
+
 .state-no-source input[pseudo="-webkit-media-controls-overlay-play-button" i],
-.use-default-poster div[pseudo="-internal-media-controls-button-panel" i],
-.use-default-poster input[pseudo="-webkit-media-controls-timeline" i] {
+.state-no-source div[pseudo="-internal-media-controls-button-panel" i],
+video::-webkit-media-controls.state-no-source input[pseudo="-webkit-media-controls-timeline" i] {
   visibility: hidden;
 }
 
diff --git a/third_party/WebKit/Source/modules/peerconnection/RTCDTMFSender.cpp b/third_party/WebKit/Source/modules/peerconnection/RTCDTMFSender.cpp
index 5ac0127..7a575ed 100644
--- a/third_party/WebKit/Source/modules/peerconnection/RTCDTMFSender.cpp
+++ b/third_party/WebKit/Source/modules/peerconnection/RTCDTMFSender.cpp
@@ -125,7 +125,7 @@
   if (strspn(tones.Ascii().data(), "0123456789abcdABCD#*,") != tones.length()) {
     exception_state.ThrowDOMException(
         kInvalidCharacterError,
-        "Illegal characers in InsertDTMF tone argument");
+        "Illegal characters in InsertDTMF tone argument");
     return;
   }
 
diff --git a/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScope.cpp b/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScope.cpp
index 1610391d..158124c 100644
--- a/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScope.cpp
+++ b/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScope.cpp
@@ -117,7 +117,7 @@
         installed_scripts_manager->GetScriptData(script_url, &script_data);
     if (status == InstalledScriptsManager::ScriptStatus::kFailed) {
       // This eventually terminates the worker thread.
-      ReportingProxy().DidEvaluateWorkerScript(false);
+      ReportingProxy().DidEvaluateClassicScript(false);
       return;
     }
 
@@ -180,7 +180,7 @@
   script_cached_metadata_total_size_ += cached_metadata_size;
 }
 
-void ServiceWorkerGlobalScope::DidEvaluateWorkerScript() {
+void ServiceWorkerGlobalScope::DidEvaluateClassicScript() {
   DEFINE_THREAD_SAFE_STATIC_LOCAL(CustomCountHistogram, script_count_histogram,
                                   ("ServiceWorker.ScriptCount", 1, 1000, 50));
   script_count_histogram.Count(script_count_);
diff --git a/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScope.h b/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScope.h
index 7f13343..38fed11 100644
--- a/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScope.h
+++ b/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScope.h
@@ -79,7 +79,7 @@
   void CountImportedScript(size_t script_size, size_t cached_metadata_size);
 
   // Called when the main worker script is evaluated.
-  void DidEvaluateWorkerScript();
+  void DidEvaluateClassicScript();
 
   // ServiceWorkerGlobalScope.idl
   ServiceWorkerClients* clients();
diff --git a/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScopeProxy.cpp b/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScopeProxy.cpp
index 871c884..804beb9 100644
--- a/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScopeProxy.cpp
+++ b/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScopeProxy.cpp
@@ -574,24 +574,24 @@
   waitable_event.Wait();
 }
 
-void ServiceWorkerGlobalScopeProxy::WillEvaluateWorkerScript(
+void ServiceWorkerGlobalScopeProxy::WillEvaluateClassicScript(
     size_t script_size,
     size_t cached_metadata_size) {
   DCHECK(WorkerGlobalScope()->IsContextThread());
   worker_global_scope_->CountWorkerScript(script_size, cached_metadata_size);
 }
 
-void ServiceWorkerGlobalScopeProxy::WillEvaluateImportedScript(
+void ServiceWorkerGlobalScopeProxy::WillEvaluateImportedClassicScript(
     size_t script_size,
     size_t cached_metadata_size) {
   DCHECK(WorkerGlobalScope()->IsContextThread());
   worker_global_scope_->CountImportedScript(script_size, cached_metadata_size);
 }
 
-void ServiceWorkerGlobalScopeProxy::DidEvaluateWorkerScript(bool success) {
+void ServiceWorkerGlobalScopeProxy::DidEvaluateClassicScript(bool success) {
   DCHECK(WorkerGlobalScope()->IsContextThread());
-  WorkerGlobalScope()->DidEvaluateWorkerScript();
-  Client().DidEvaluateWorkerScript(success);
+  WorkerGlobalScope()->DidEvaluateClassicScript();
+  Client().DidEvaluateClassicScript(success);
 }
 
 void ServiceWorkerGlobalScopeProxy::DidCloseWorkerGlobalScope() {
diff --git a/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScopeProxy.h b/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScopeProxy.h
index ba8bf98..ba0776f 100644
--- a/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScopeProxy.h
+++ b/third_party/WebKit/Source/modules/serviceworkers/ServiceWorkerGlobalScopeProxy.h
@@ -153,11 +153,11 @@
   void DidLoadInstalledScript(
       const ContentSecurityPolicyResponseHeaders&,
       const String& referrer_policy_on_worker_thread) override;
-  void WillEvaluateWorkerScript(size_t script_size,
-                                size_t cached_metadata_size) override;
-  void WillEvaluateImportedScript(size_t script_size,
-                                  size_t cached_metadata_size) override;
-  void DidEvaluateWorkerScript(bool success) override;
+  void WillEvaluateClassicScript(size_t script_size,
+                                 size_t cached_metadata_size) override;
+  void WillEvaluateImportedClassicScript(size_t script_size,
+                                         size_t cached_metadata_size) override;
+  void DidEvaluateClassicScript(bool success) override;
   void DidCloseWorkerGlobalScope() override;
   void WillDestroyWorkerGlobalScope() override;
   void DidTerminateWorkerThread() override;
diff --git a/third_party/WebKit/Source/modules/serviceworkers/WebEmbeddedWorkerImplTest.cpp b/third_party/WebKit/Source/modules/serviceworkers/WebEmbeddedWorkerImplTest.cpp
index 1b3dcda..a56ec4c 100644
--- a/third_party/WebKit/Source/modules/serviceworkers/WebEmbeddedWorkerImplTest.cpp
+++ b/third_party/WebKit/Source/modules/serviceworkers/WebEmbeddedWorkerImplTest.cpp
@@ -36,7 +36,7 @@
   MOCK_METHOD0(WorkerContextFailedToStart, void());
   MOCK_METHOD0(WorkerScriptLoaded, void());
 
-  void DidEvaluateWorkerScript(bool /* success */) override {
+  void DidEvaluateClassicScript(bool /* success */) override {
     script_evaluated_event_.Signal();
   }
 
diff --git a/third_party/WebKit/Source/modules/webaudio/RealtimeAnalyser.cpp b/third_party/WebKit/Source/modules/webaudio/RealtimeAnalyser.cpp
index 2698788..1eb44e60 100644
--- a/third_party/WebKit/Source/modules/webaudio/RealtimeAnalyser.cpp
+++ b/third_party/WebKit/Source/modules/webaudio/RealtimeAnalyser.cpp
@@ -161,7 +161,7 @@
   // Do the analysis.
   analysis_frame_->DoFFT(temp_p);
 
-  float* real_p = analysis_frame_->RealData();
+  const float* real_p = analysis_frame_->RealData();
   float* imag_p = analysis_frame_->ImagData();
 
   // Blow away the packed nyquist component.
diff --git a/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannelTest.cpp b/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannelTest.cpp
index bc43a54..40c3469 100644
--- a/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannelTest.cpp
+++ b/third_party/WebKit/Source/modules/websockets/DocumentWebSocketChannelTest.cpp
@@ -149,7 +149,7 @@
     return static_cast<WebSocketHandleClient*>(channel_.Get());
   }
 
-  WebCallbacks<void, const WebString&>* WebCallbacks() {
+  WebCallbacks<void, const WebString&>* GetWebCallbacks() {
     return channel_.Get();
   }
 
@@ -831,7 +831,7 @@
   EXPECT_CALL(*Handle(), DoInitialize(_));
   EXPECT_CALL(*Handle(), Connect(_, _, _, _, _, _));
   EXPECT_CALL(*handshake_throttle_,
-              ThrottleHandshake(WebURL(url()), _, WebCallbacks()));
+              ThrottleHandshake(WebURL(url()), _, GetWebCallbacks()));
   EXPECT_CALL(*handshake_throttle_, Destructor());
   Channel()->Connect(url(), "");
 }
@@ -849,7 +849,7 @@
   }
   Channel()->Connect(url(), "");
   checkpoint.Call(1);
-  WebCallbacks()->OnSuccess();
+  GetWebCallbacks()->OnSuccess();
   checkpoint.Call(2);
   HandleClient()->DidConnect(Handle(), String("a"), String("b"));
 }
@@ -869,7 +869,7 @@
   checkpoint.Call(1);
   HandleClient()->DidConnect(Handle(), String("a"), String("b"));
   checkpoint.Call(2);
-  WebCallbacks()->OnSuccess();
+  GetWebCallbacks()->OnSuccess();
 }
 
 // This happens if JS code calls close() during the handshake.
@@ -979,7 +979,7 @@
     EXPECT_CALL(*ChannelClient(), DidClose(_, _, _));
   }
   Channel()->Connect(url(), "");
-  WebCallbacks()->OnError("Connection blocked by throttle");
+  GetWebCallbacks()->OnError("Connection blocked by throttle");
 }
 
 TEST_F(DocumentWebSocketChannelHandshakeThrottleTest,
@@ -993,7 +993,7 @@
   }
   Channel()->Connect(url(), "");
   HandleClient()->DidConnect(Handle(), String("a"), String("b"));
-  WebCallbacks()->OnError("Connection blocked by throttle");
+  GetWebCallbacks()->OnError("Connection blocked by throttle");
 }
 
 TEST_F(DocumentWebSocketChannelHandshakeThrottleTest,
diff --git a/third_party/WebKit/Source/platform/WebThreadType.cpp b/third_party/WebKit/Source/platform/WebThreadType.cpp
index 183c326..d796158 100644
--- a/third_party/WebKit/Source/platform/WebThreadType.cpp
+++ b/third_party/WebKit/Source/platform/WebThreadType.cpp
@@ -15,7 +15,8 @@
     case WebThreadType::kUnspecifiedWorkerThread:
       return "unspecified worker thread";
     case WebThreadType::kCompositorThread:
-      return "Compositor thread";
+      // Some benchmarks depend on this value.
+      return "Compositor";
     case WebThreadType::kDedicatedWorkerThread:
       return "DedicatedWorker thread";
     case WebThreadType::kSharedWorkerThread:
diff --git a/third_party/WebKit/Source/platform/audio/AudioBus.cpp b/third_party/WebKit/Source/platform/audio/AudioBus.cpp
index 989d61a..a7daaf2fe 100644
--- a/third_party/WebKit/Source/platform/audio/AudioBus.cpp
+++ b/third_party/WebKit/Source/platform/audio/AudioBus.cpp
@@ -500,12 +500,11 @@
   if (this == &source_bus && gain == 1)
     return;
 
-  AudioBus& source_bus_safe = const_cast<AudioBus&>(source_bus);
   const float* sources[kMaxBusChannels];
   float* destinations[kMaxBusChannels];
 
   for (unsigned i = 0; i < number_of_channels; ++i) {
-    sources[i] = source_bus_safe.Channel(i)->Data();
+    sources[i] = source_bus.Channel(i)->Data();
     destinations[i] = Channel(i)->MutableData();
   }
 
diff --git a/third_party/WebKit/Source/platform/audio/AudioBus.h b/third_party/WebKit/Source/platform/audio/AudioBus.h
index eac0720..0041cc2 100644
--- a/third_party/WebKit/Source/platform/audio/AudioBus.h
+++ b/third_party/WebKit/Source/platform/audio/AudioBus.h
@@ -81,7 +81,7 @@
 
   AudioChannel* Channel(unsigned channel) { return channels_[channel].get(); }
   const AudioChannel* Channel(unsigned channel) const {
-    return const_cast<AudioBus*>(this)->channels_[channel].get();
+    return channels_[channel].get();
   }
   AudioChannel* ChannelByType(unsigned type);
   const AudioChannel* ChannelByType(unsigned type) const;
diff --git a/third_party/WebKit/Source/platform/audio/FFTConvolver.cpp b/third_party/WebKit/Source/platform/audio/FFTConvolver.cpp
index 862a366..36ad76d 100644
--- a/third_party/WebKit/Source/platform/audio/FFTConvolver.cpp
+++ b/third_party/WebKit/Source/platform/audio/FFTConvolver.cpp
@@ -40,7 +40,7 @@
       output_buffer_(fft_size),
       last_overlap_buffer_(fft_size / 2) {}
 
-void FFTConvolver::Process(FFTFrame* fft_kernel,
+void FFTConvolver::Process(const FFTFrame* fft_kernel,
                            const float* source_p,
                            float* dest_p,
                            size_t frames_to_process) {
diff --git a/third_party/WebKit/Source/platform/audio/FFTConvolver.h b/third_party/WebKit/Source/platform/audio/FFTConvolver.h
index 0036753c..244b487 100644
--- a/third_party/WebKit/Source/platform/audio/FFTConvolver.h
+++ b/third_party/WebKit/Source/platform/audio/FFTConvolver.h
@@ -53,7 +53,7 @@
   // The input to output latency is equal to fftSize / 2
   //
   // Processing in-place is allowed...
-  void Process(FFTFrame* fft_kernel,
+  void Process(const FFTFrame* fft_kernel,
                const float* source_p,
                float* dest_p,
                size_t frames_to_process);
diff --git a/third_party/WebKit/Source/platform/audio/FFTFrame.cpp b/third_party/WebKit/Source/platform/audio/FFTFrame.cpp
index d972c0e..818d5d1 100644
--- a/third_party/WebKit/Source/platform/audio/FFTFrame.cpp
+++ b/third_party/WebKit/Source/platform/audio/FFTFrame.cpp
@@ -251,7 +251,7 @@
 
 void FFTFrame::Multiply(const FFTFrame& frame) {
   FFTFrame& frame1 = *this;
-  FFTFrame& frame2 = const_cast<FFTFrame&>(frame);
+  const FFTFrame& frame2 = frame;
 
   float* real_p1 = frame1.RealData();
   float* imag_p1 = frame1.ImagData();
diff --git a/third_party/WebKit/Source/platform/audio/FFTFrame.h b/third_party/WebKit/Source/platform/audio/FFTFrame.h
index 84d16591..c3ce7887 100644
--- a/third_party/WebKit/Source/platform/audio/FFTFrame.h
+++ b/third_party/WebKit/Source/platform/audio/FFTFrame.h
@@ -70,8 +70,10 @@
   void DoFFT(const float* data);
   void DoInverseFFT(float* data);
 
-  float* RealData() const { return const_cast<float*>(real_data_.Data()); }
-  float* ImagData() const { return const_cast<float*>(imag_data_.Data()); }
+  float* RealData() { return real_data_.Data(); }
+  const float* RealData() const { return real_data_.Data(); }
+  float* ImagData() { return imag_data_.Data(); }
+  const float* ImagData() const { return imag_data_.Data(); }
 
   unsigned FftSize() const { return fft_size_; }
   unsigned Log2FFTSize() const { return log2fft_size_; }
diff --git a/third_party/WebKit/Source/platform/audio/ffmpeg/FFTFrameFFMPEG.cpp b/third_party/WebKit/Source/platform/audio/ffmpeg/FFTFrameFFMPEG.cpp
index f1a06f3e..f29bc5c 100644
--- a/third_party/WebKit/Source/platform/audio/ffmpeg/FFTFrameFFMPEG.cpp
+++ b/third_party/WebKit/Source/platform/audio/ffmpeg/FFTFrameFFMPEG.cpp
@@ -143,7 +143,7 @@
     c[base_complex_index] = real[i];
     c[base_complex_index + 1] = imag[i];
   }
-  return const_cast<float*>(complex_data_.Data());
+  return c;
 }
 
 RDFTContext* FFTFrame::ContextForSize(unsigned fft_size, int trans) {
diff --git a/third_party/WebKit/Source/platform/exported/WebURLError.cpp b/third_party/WebKit/Source/platform/exported/WebURLError.cpp
index 47a21a5..5a09977 100644
--- a/third_party/WebKit/Source/platform/exported/WebURLError.cpp
+++ b/third_party/WebKit/Source/platform/exported/WebURLError.cpp
@@ -9,15 +9,17 @@
 namespace blink {
 
 WebURLError::WebURLError(int reason, const WebURL& url)
-    : reason_(reason), url_(url) {
+    : reason_(reason), extended_reason_(0), url_(url) {
   DCHECK_NE(reason_, 0);
 }
 
 WebURLError::WebURLError(int reason,
+                         int extended_reason,
                          HasCopyInCache has_copy_in_cache,
                          IsWebSecurityViolation is_web_security_violation,
                          const WebURL& url)
     : reason_(reason),
+      extended_reason_(extended_reason),
       has_copy_in_cache_(has_copy_in_cache == HasCopyInCache::kTrue),
       is_web_security_violation_(is_web_security_violation ==
                                  IsWebSecurityViolation::kTrue),
diff --git a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasResourceProvider.cpp b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasResourceProvider.cpp
index c32f438..46915e7 100644
--- a/third_party/WebKit/Source/platform/graphics/OffscreenCanvasResourceProvider.cpp
+++ b/third_party/WebKit/Source/platform/graphics/OffscreenCanvasResourceProvider.cpp
@@ -63,17 +63,18 @@
   }
   unsigned char* pixels = frame_resource->shared_bitmap_->pixels();
   DCHECK(pixels);
-  SkImageInfo image_info = SkImageInfo::Make(
-      width_, height_, kN32_SkColorType,
-      image->IsPremultiplied() ? kPremul_SkAlphaType : kUnpremul_SkAlphaType);
-  if (image_info.isEmpty())
-    return;
   // TODO(xlai): Optimize to avoid copying pixels. See crbug.com/651456.
   // However, in the case when |image| is texture backed, this function call
   // does a GPU readback which is required.
   sk_sp<SkImage> sk_image = image->PaintImageForCurrentFrame().GetSkImage();
   if (sk_image->bounds().isEmpty())
     return;
+  SkImageInfo image_info = SkImageInfo::Make(
+      width_, height_, kN32_SkColorType,
+      image->IsPremultiplied() ? kPremul_SkAlphaType : kUnpremul_SkAlphaType,
+      sk_image->refColorSpace());
+  if (image_info.isEmpty())
+    return;
   bool read_pixels_successful =
       sk_image->readPixels(image_info, pixels, image_info.minRowBytes(), 0, 0);
   DCHECK(read_pixels_successful);
diff --git a/third_party/WebKit/Source/platform/graphics/StaticBitmapImage.cpp b/third_party/WebKit/Source/platform/graphics/StaticBitmapImage.cpp
index c5b831d..e53349f9 100644
--- a/third_party/WebKit/Source/platform/graphics/StaticBitmapImage.cpp
+++ b/third_party/WebKit/Source/platform/graphics/StaticBitmapImage.cpp
@@ -145,8 +145,9 @@
       (color_params.GetSkColorType() == kRGBA_F16_SkColorType)
           ? kRGBA_F16_SkColorType
           : kRGBA_8888_SkColorType;
-  SkImageInfo info = SkImageInfo::Make(rect.Width(), rect.Height(), color_type,
-                                       kUnpremul_SkAlphaType);
+  SkImageInfo info = SkImageInfo::Make(
+      rect.Width(), rect.Height(), color_type, kUnpremul_SkAlphaType,
+      color_params.GetSkColorSpaceForSkSurfaces());
   sk_sp<SkImage> sk_image = src_image->PaintImageForCurrentFrame().GetSkImage();
   bool read_pixels_successful = sk_image->readPixels(
       info, result.Data(), info.minRowBytes(), rect.X(), rect.Y());
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/ImageLayerBridge.cpp b/third_party/WebKit/Source/platform/graphics/gpu/ImageLayerBridge.cpp
index 7ddaa74..89ab3c2 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/ImageLayerBridge.cpp
+++ b/third_party/WebKit/Source/platform/graphics/gpu/ImageLayerBridge.cpp
@@ -128,6 +128,7 @@
 
     SkImageInfo dst_info =
         SkImageInfo::MakeN32Premul(image_for_compositor->width(), 1);
+    dst_info = dst_info.makeColorSpace(sk_image->refColorSpace());
     size_t row_bytes = image_for_compositor->width() * 4;
 
     // Copy from SkImage into |bitmap|, while flipping the Y axis.
diff --git a/third_party/WebKit/Source/platform/heap/BlinkGC.h b/third_party/WebKit/Source/platform/heap/BlinkGC.h
index dea52b8..f5df423 100644
--- a/third_party/WebKit/Source/platform/heap/BlinkGC.h
+++ b/third_party/WebKit/Source/platform/heap/BlinkGC.h
@@ -14,6 +14,7 @@
 
 namespace blink {
 
+class HeapObjectHeader;
 class MarkingVisitor;
 class Visitor;
 class ScriptWrappableVisitor;
@@ -30,6 +31,12 @@
 using MissedWriteBarrierCallback = void (*)();
 using NameCallback = const char* (*)(const void* self);
 
+// Callback used for unit testing the marking of conservative pointers
+// (|CheckAndMarkPointer|). For each pointer that has been discovered to point
+// to a heap object, the callback is invoked with a pointer to its header. If
+// the callback returns true, the object will not be marked.
+using MarkedPointerCallbackForTesting = bool (*)(HeapObjectHeader*);
+
 // Simple alias to avoid heap compaction type signatures turning into
 // a sea of generic |void*|s.
 using MovableReference = void*;
diff --git a/third_party/WebKit/Source/platform/heap/Heap.cpp b/third_party/WebKit/Source/platform/heap/Heap.cpp
index 719289b7..a124a40 100644
--- a/third_party/WebKit/Source/platform/heap/Heap.cpp
+++ b/third_party/WebKit/Source/platform/heap/Heap.cpp
@@ -178,7 +178,7 @@
 #endif
     DCHECK(!heap_does_not_contain_cache_->Lookup(address));
     DCHECK(&visitor->Heap() == &page->Arena()->GetThreadState()->Heap());
-    page->CheckAndMarkPointer(visitor, address);
+    visitor->ConservativelyMarkAddress(page, address);
     return address;
   }
 
@@ -205,7 +205,7 @@
     DCHECK(page->Contains(address));
     DCHECK(!heap_does_not_contain_cache_->Lookup(address));
     DCHECK(&visitor->Heap() == &page->Arena()->GetThreadState()->Heap());
-    page->CheckAndMarkPointer(visitor, address, callback);
+    visitor->ConservativelyMarkAddress(page, address, callback);
     return address;
   }
   if (!heap_does_not_contain_cache_->Lookup(address))
diff --git a/third_party/WebKit/Source/platform/heap/HeapPage.cpp b/third_party/WebKit/Source/platform/heap/HeapPage.cpp
index b085ec93..df87be4 100644
--- a/third_party/WebKit/Source/platform/heap/HeapPage.cpp
+++ b/third_party/WebKit/Source/platform/heap/HeapPage.cpp
@@ -38,7 +38,6 @@
 #include "platform/heap/CallbackStack.h"
 #include "platform/heap/HeapCompact.h"
 #include "platform/heap/MarkingVerifier.h"
-#include "platform/heap/MarkingVisitor.h"
 #include "platform/heap/PageMemory.h"
 #include "platform/heap/PagePool.h"
 #include "platform/heap/SafePoint.h"
@@ -1636,64 +1635,6 @@
   return header;
 }
 
-#if DCHECK_IS_ON()
-static bool IsUninitializedMemory(void* object_pointer, size_t object_size) {
-  // Scan through the object's fields and check that they are all zero.
-  Address* object_fields = reinterpret_cast<Address*>(object_pointer);
-  for (size_t i = 0; i < object_size / sizeof(Address); ++i) {
-    if (object_fields[i])
-      return false;
-  }
-  return true;
-}
-#endif
-
-static void MarkPointer(MarkingVisitor* visitor, HeapObjectHeader* header) {
-  const GCInfo* gc_info = ThreadHeap::GcInfo(header->GcInfoIndex());
-  if (gc_info->HasVTable() && !VTableInitialized(header->Payload())) {
-    // We hit this branch when a GC strikes before GarbageCollected<>'s
-    // constructor runs.
-    //
-    // class A : public GarbageCollected<A> { virtual void f() = 0; };
-    // class B : public A {
-    //   B() : A(foo()) { };
-    // };
-    //
-    // If foo() allocates something and triggers a GC, the vtable of A
-    // has not yet been initialized. In this case, we should mark the A
-    // object without tracing any member of the A object.
-    visitor->MarkHeaderNoTracing(header);
-#if DCHECK_IS_ON()
-    DCHECK(IsUninitializedMemory(header->Payload(), header->PayloadSize()));
-#endif
-  } else {
-    visitor->MarkHeader(header, gc_info->trace_);
-  }
-}
-
-void NormalPage::CheckAndMarkPointer(MarkingVisitor* visitor, Address address) {
-#if DCHECK_IS_ON()
-  DCHECK(Contains(address));
-#endif
-  HeapObjectHeader* header = FindHeaderFromAddress(address);
-  if (!header)
-    return;
-  MarkPointer(visitor, header);
-}
-
-#if DCHECK_IS_ON()
-void NormalPage::CheckAndMarkPointer(MarkingVisitor* visitor,
-                                     Address address,
-                                     MarkedPointerCallbackForTesting callback) {
-  DCHECK(Contains(address));
-  HeapObjectHeader* header = FindHeaderFromAddress(address);
-  if (!header)
-    return;
-  if (!callback(header))
-    MarkPointer(visitor, header);
-}
-#endif
-
 void NormalPage::TakeSnapshot(base::trace_event::MemoryAllocatorDump* page_dump,
                               ThreadState::GCSnapshotInfo& info,
                               HeapSnapshotInfo& heap_info) {
@@ -1790,29 +1731,6 @@
 }
 #endif
 
-void LargeObjectPage::CheckAndMarkPointer(MarkingVisitor* visitor,
-                                          Address address) {
-#if DCHECK_IS_ON()
-  DCHECK(Contains(address));
-#endif
-  if (!ContainedInObjectPayload(address))
-    return;
-  MarkPointer(visitor, GetHeapObjectHeader());
-}
-
-#if DCHECK_IS_ON()
-void LargeObjectPage::CheckAndMarkPointer(
-    MarkingVisitor* visitor,
-    Address address,
-    MarkedPointerCallbackForTesting callback) {
-  DCHECK(Contains(address));
-  if (!ContainedInObjectPayload(address))
-    return;
-  if (!callback(GetHeapObjectHeader()))
-    MarkPointer(visitor, GetHeapObjectHeader());
-}
-#endif
-
 void LargeObjectPage::TakeSnapshot(
     base::trace_event::MemoryAllocatorDump* page_dump,
     ThreadState::GCSnapshotInfo& info,
diff --git a/third_party/WebKit/Source/platform/heap/HeapPage.h b/third_party/WebKit/Source/platform/heap/HeapPage.h
index 0d3c8dc..31f44dc 100644
--- a/third_party/WebKit/Source/platform/heap/HeapPage.h
+++ b/third_party/WebKit/Source/platform/heap/HeapPage.h
@@ -333,11 +333,6 @@
            kBlinkGuardPageSize);
 }
 
-// Callback used for unit testing the marking of conservative pointers
-// (|CheckAndMarkPointer|). For each pointer that has been discovered to point
-// to a heap object, the callback is invoked with a pointer to its header. If
-// the callback returns true, the object will not be marked.
-using MarkedPointerCallbackForTesting = bool (*)(HeapObjectHeader*);
 #endif
 
 // |BasePage| is a base class for |NormalPage| and |LargeObjectPage|.
@@ -382,20 +377,6 @@
   virtual void PoisonUnmarkedObjects() = 0;
 #endif
 
-  // Check if the given address points to an object in this heap page. If so,
-  // find the start of that object and mark it using the given |Visitor|.
-  // Otherwise do nothing. The pointer must be within the same aligned
-  // |kBlinkPageSize| as |this|.
-  //
-  // This is used during conservative stack scanning to conservatively mark all
-  // objects that could be referenced from the stack.
-  virtual void CheckAndMarkPointer(MarkingVisitor*, Address) = 0;
-#if DCHECK_IS_ON()
-  virtual void CheckAndMarkPointer(MarkingVisitor*,
-                                   Address,
-                                   MarkedPointerCallbackForTesting) = 0;
-#endif
-
   class HeapSnapshotInfo {
     STACK_ALLOCATED();
 
@@ -535,12 +516,6 @@
 #if defined(ADDRESS_SANITIZER)
   void PoisonUnmarkedObjects() override;
 #endif
-  void CheckAndMarkPointer(MarkingVisitor*, Address) override;
-#if DCHECK_IS_ON()
-  void CheckAndMarkPointer(MarkingVisitor*,
-                           Address,
-                           MarkedPointerCallbackForTesting) override;
-#endif
 
   void TakeSnapshot(base::trace_event::MemoryAllocatorDump*,
                     ThreadState::GCSnapshotInfo&,
@@ -633,12 +608,6 @@
 #if defined(ADDRESS_SANITIZER)
   void PoisonUnmarkedObjects() override;
 #endif
-  void CheckAndMarkPointer(MarkingVisitor*, Address) override;
-#if DCHECK_IS_ON()
-  void CheckAndMarkPointer(MarkingVisitor*,
-                           Address,
-                           MarkedPointerCallbackForTesting) override;
-#endif
 
   void TakeSnapshot(base::trace_event::MemoryAllocatorDump*,
                     ThreadState::GCSnapshotInfo&,
diff --git a/third_party/WebKit/Source/platform/heap/MarkingVisitor.cpp b/third_party/WebKit/Source/platform/heap/MarkingVisitor.cpp
index 3273925..6a70cab6 100644
--- a/third_party/WebKit/Source/platform/heap/MarkingVisitor.cpp
+++ b/third_party/WebKit/Source/platform/heap/MarkingVisitor.cpp
@@ -21,11 +21,81 @@
   DCHECK(state->IsGCForbidden());
 #if DCHECK_IS_ON()
   DCHECK(state->CheckThread());
-#endif
+#endif  // DCHECK_IS_ON
 }
 
 MarkingVisitor::~MarkingVisitor() = default;
 
+void MarkingVisitor::ConservativelyMarkAddress(BasePage* page,
+                                               Address address) {
+#if DCHECK_IS_ON()
+  DCHECK(page->Contains(address));
+#endif
+  HeapObjectHeader* const header =
+      page->IsLargeObjectPage()
+          ? static_cast<LargeObjectPage*>(page)->GetHeapObjectHeader()
+          : static_cast<NormalPage*>(page)->FindHeaderFromAddress(address);
+  if (!header)
+    return;
+  ConservativelyMarkHeader(header);
+}
+
+#if DCHECK_IS_ON()
+void MarkingVisitor::ConservativelyMarkAddress(
+    BasePage* page,
+    Address address,
+    MarkedPointerCallbackForTesting callback) {
+  DCHECK(page->Contains(address));
+  HeapObjectHeader* const header =
+      page->IsLargeObjectPage()
+          ? static_cast<LargeObjectPage*>(page)->GetHeapObjectHeader()
+          : static_cast<NormalPage*>(page)->FindHeaderFromAddress(address);
+  if (!header)
+    return;
+  if (!callback(header))
+    ConservativelyMarkHeader(header);
+}
+#endif  // DCHECK_IS_ON
+
+namespace {
+
+#if DCHECK_IS_ON()
+bool IsUninitializedMemory(void* object_pointer, size_t object_size) {
+  // Scan through the object's fields and check that they are all zero.
+  Address* object_fields = reinterpret_cast<Address*>(object_pointer);
+  for (size_t i = 0; i < object_size / sizeof(Address); ++i) {
+    if (object_fields[i])
+      return false;
+  }
+  return true;
+}
+#endif
+
+}  // namespace
+
+void MarkingVisitor::ConservativelyMarkHeader(HeapObjectHeader* header) {
+  const GCInfo* gc_info = ThreadHeap::GcInfo(header->GcInfoIndex());
+  if (gc_info->HasVTable() && !VTableInitialized(header->Payload())) {
+    // We hit this branch when a GC strikes before GarbageCollected<>'s
+    // constructor runs.
+    //
+    // class A : public GarbageCollected<A> { virtual void f() = 0; };
+    // class B : public A {
+    //   B() : A(foo()) { };
+    // };
+    //
+    // If foo() allocates something and triggers a GC, the vtable of A
+    // has not yet been initialized. In this case, we should mark the A
+    // object without tracing any member of the A object.
+    MarkHeaderNoTracing(header);
+#if DCHECK_IS_ON()
+    DCHECK(IsUninitializedMemory(header->Payload(), header->PayloadSize()));
+#endif
+  } else {
+    MarkHeader(header, gc_info->trace_);
+  }
+}
+
 void MarkingVisitor::MarkNoTracingCallback(Visitor* visitor, void* object) {
   reinterpret_cast<MarkingVisitor*>(visitor)->MarkHeaderNoTracing(
       HeapObjectHeader::FromPayload(object));
diff --git a/third_party/WebKit/Source/platform/heap/MarkingVisitor.h b/third_party/WebKit/Source/platform/heap/MarkingVisitor.h
index b2b6a9b7..235642b 100644
--- a/third_party/WebKit/Source/platform/heap/MarkingVisitor.h
+++ b/third_party/WebKit/Source/platform/heap/MarkingVisitor.h
@@ -11,6 +11,8 @@
 
 namespace blink {
 
+class BasePage;
+
 // Visitor used to mark Oilpan objects.
 class PLATFORM_EXPORT MarkingVisitor final : public Visitor {
  public:
@@ -37,6 +39,14 @@
 
   // Marking implementation.
 
+  // Conservatively marks an object if pointed to by Address.
+  void ConservativelyMarkAddress(BasePage*, Address);
+#if DCHECK_IS_ON()
+  void ConservativelyMarkAddress(BasePage*,
+                                 Address,
+                                 MarkedPointerCallbackForTesting);
+#endif  // DCHECK_IS_ON()
+
   // Marks an object and adds a tracing callback for processing of the object.
   inline void MarkHeader(HeapObjectHeader*, TraceCallback);
 
@@ -125,6 +135,8 @@
 
   void RegisterBackingStoreReference(void* slot);
 
+  void ConservativelyMarkHeader(HeapObjectHeader*);
+
   const MarkingMode marking_mode_;
 };
 
diff --git a/third_party/WebKit/Source/platform/loader/fetch/ResourceError.cpp b/third_party/WebKit/Source/platform/loader/fetch/ResourceError.cpp
index 19054eb..2d4a982 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/ResourceError.cpp
+++ b/third_party/WebKit/Source/platform/loader/fetch/ResourceError.cpp
@@ -93,6 +93,7 @@
 
 ResourceError::ResourceError(const WebURLError& error)
     : error_code_(error.reason()),
+      extended_error_code_(error.extended_reason()),
       failing_url_(error.url()),
       is_access_check_(error.is_web_security_violation()),
       has_copy_in_cache_(error.has_copy_in_cache()),
@@ -104,6 +105,7 @@
 ResourceError ResourceError::Copy() const {
   ResourceError error_copy(error_code_, failing_url_.Copy(),
                            cors_error_status_);
+  error_copy.extended_error_code_ = extended_error_code_;
   error_copy.has_copy_in_cache_ = has_copy_in_cache_;
   error_copy.localized_description_ = localized_description_.IsolatedCopy();
   error_copy.is_access_check_ = is_access_check_;
@@ -120,7 +122,7 @@
     return WebURLError(*cors_error_status_, has_copy_in_cache, failing_url_);
   }
 
-  return WebURLError(error_code_, has_copy_in_cache,
+  return WebURLError(error_code_, extended_error_code_, has_copy_in_cache,
                      is_access_check_
                          ? WebURLError::IsWebSecurityViolation::kTrue
                          : WebURLError::IsWebSecurityViolation::kFalse,
@@ -169,8 +171,8 @@
   if (error_code_ == net::ERR_TEMPORARILY_THROTTLED) {
     localized_description_ = WebString::FromASCII(kThrottledErrorDescription);
   } else {
-    localized_description_ =
-        WebString::FromASCII(net::ErrorToString(error_code_));
+    localized_description_ = WebString::FromASCII(
+        net::ExtendedErrorToString(error_code_, extended_error_code_));
   }
 }
 
diff --git a/third_party/WebKit/Source/platform/loader/fetch/ResourceError.h b/third_party/WebKit/Source/platform/loader/fetch/ResourceError.h
index 6aad7cff..374b5df7 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/ResourceError.h
+++ b/third_party/WebKit/Source/platform/loader/fetch/ResourceError.h
@@ -97,6 +97,7 @@
   void InitializeDescription();
 
   int error_code_;
+  int extended_error_code_;
   KURL failing_url_;
   String localized_description_;
   bool is_access_check_ = false;
diff --git a/third_party/WebKit/Source/platform/scheduler/BUILD.gn b/third_party/WebKit/Source/platform/scheduler/BUILD.gn
index 9172bf9..ccc7e6ad 100644
--- a/third_party/WebKit/Source/platform/scheduler/BUILD.gn
+++ b/third_party/WebKit/Source/platform/scheduler/BUILD.gn
@@ -98,6 +98,8 @@
     "renderer/auto_advancing_virtual_time_domain.h",
     "renderer/deadline_task_runner.cc",
     "renderer/deadline_task_runner.h",
+    "renderer/frame_origin_type.cc",
+    "renderer/frame_origin_type.h",
     "renderer/frame_status.cc",
     "renderer/frame_status.h",
     "renderer/idle_time_estimator.cc",
diff --git a/third_party/WebKit/Source/platform/scheduler/child/worker_metrics_helper.cc b/third_party/WebKit/Source/platform/scheduler/child/worker_metrics_helper.cc
index 6b460b0..8119322 100644
--- a/third_party/WebKit/Source/platform/scheduler/child/worker_metrics_helper.cc
+++ b/third_party/WebKit/Source/platform/scheduler/child/worker_metrics_helper.cc
@@ -4,6 +4,8 @@
 
 #include "platform/scheduler/child/worker_metrics_helper.h"
 
+#include "platform/scheduler/child/process_state.h"
+
 namespace blink {
 namespace scheduler {
 
@@ -12,10 +14,19 @@
       dedicated_worker_per_task_type_duration_reporter_(
           "RendererScheduler.TaskDurationPerTaskType.DedicatedWorker"),
       dedicated_worker_per_task_type_cpu_duration_reporter_(
-          "RendererScheduler.TaskCPUDurationPerTaskType.DedicatedWorker") {}
+          "RendererScheduler.TaskCPUDurationPerTaskType.DedicatedWorker"),
+      dedicated_worker_per_parent_frame_status_duration_reporter_(
+          "RendererScheduler.TaskDurationPerFrameOriginType.DedicatedWorker"),
+      background_dedicated_worker_per_parent_frame_status_duration_reporter_(
+          "RendererScheduler.TaskDurationPerFrameOriginType.DedicatedWorker."
+          "Background") {}
 
 WorkerMetricsHelper::~WorkerMetricsHelper() {}
 
+void WorkerMetricsHelper::SetParentFrameType(FrameOriginType frame_type) {
+  parent_frame_type_ = frame_type;
+}
+
 void WorkerMetricsHelper::RecordTaskMetrics(
     WorkerTaskQueue* queue,
     const TaskQueue::Task& task,
@@ -28,6 +39,8 @@
   MetricsHelper::RecordCommonTaskMetrics(queue, task, start_time, end_time,
                                          thread_time);
 
+  bool backgrounded = internal::ProcessState::Get()->is_process_backgrounded;
+
   if (thread_type_ == WebThreadType::kDedicatedWorkerThread) {
     TaskType task_type = static_cast<TaskType>(task.task_type());
     dedicated_worker_per_task_type_duration_reporter_.RecordTask(
@@ -36,6 +49,16 @@
       dedicated_worker_per_task_type_cpu_duration_reporter_.RecordTask(
           task_type, thread_time.value());
     }
+
+    if (parent_frame_type_) {
+      dedicated_worker_per_parent_frame_status_duration_reporter_.RecordTask(
+          parent_frame_type_.value(), end_time - start_time);
+
+      if (backgrounded) {
+        background_dedicated_worker_per_parent_frame_status_duration_reporter_
+            .RecordTask(parent_frame_type_.value(), end_time - start_time);
+      }
+    }
   }
 }
 
diff --git a/third_party/WebKit/Source/platform/scheduler/child/worker_metrics_helper.h b/third_party/WebKit/Source/platform/scheduler/child/worker_metrics_helper.h
index 614aad8..fca18ef1 100644
--- a/third_party/WebKit/Source/platform/scheduler/child/worker_metrics_helper.h
+++ b/third_party/WebKit/Source/platform/scheduler/child/worker_metrics_helper.h
@@ -7,6 +7,7 @@
 
 #include "platform/scheduler/child/metrics_helper.h"
 #include "platform/scheduler/child/worker_task_queue.h"
+#include "platform/scheduler/renderer/frame_origin_type.h"
 #include "platform/scheduler/util/thread_load_tracker.h"
 #include "public/platform/TaskType.h"
 
@@ -26,11 +27,19 @@
 
   using MetricsHelper::SetThreadType;
 
+  void SetParentFrameType(FrameOriginType frame_type);
+
  private:
   TaskDurationMetricReporter<TaskType>
       dedicated_worker_per_task_type_duration_reporter_;
   TaskDurationMetricReporter<TaskType>
       dedicated_worker_per_task_type_cpu_duration_reporter_;
+  TaskDurationMetricReporter<FrameOriginType>
+      dedicated_worker_per_parent_frame_status_duration_reporter_;
+  TaskDurationMetricReporter<FrameOriginType>
+      background_dedicated_worker_per_parent_frame_status_duration_reporter_;
+
+  base::Optional<FrameOriginType> parent_frame_type_;
 
   DISALLOW_COPY_AND_ASSIGN(WorkerMetricsHelper);
 };
diff --git a/third_party/WebKit/Source/platform/scheduler/child/worker_scheduler_impl.cc b/third_party/WebKit/Source/platform/scheduler/child/worker_scheduler_impl.cc
index 36e8e2f..62cec39 100644
--- a/third_party/WebKit/Source/platform/scheduler/child/worker_scheduler_impl.cc
+++ b/third_party/WebKit/Source/platform/scheduler/child/worker_scheduler_impl.cc
@@ -69,8 +69,10 @@
   load_tracker_.Resume(thread_start_time_);
   helper_->AddTaskTimeObserver(this);
 
-  if (proxy)
+  if (proxy) {
+    worker_metrics_helper_.SetParentFrameType(proxy->parent_frame_type());
     proxy->OnWorkerSchedulerCreated(GetWeakPtr());
+  }
 
   TRACE_EVENT_OBJECT_CREATED_WITH_ID(
       TRACE_DISABLED_BY_DEFAULT("worker.scheduler"), "WorkerScheduler", this);
diff --git a/third_party/WebKit/Source/platform/scheduler/child/worker_scheduler_proxy.cc b/third_party/WebKit/Source/platform/scheduler/child/worker_scheduler_proxy.cc
index 2d2c5d4b..8059fa5a 100644
--- a/third_party/WebKit/Source/platform/scheduler/child/worker_scheduler_proxy.cc
+++ b/third_party/WebKit/Source/platform/scheduler/child/worker_scheduler_proxy.cc
@@ -13,6 +13,7 @@
 WorkerSchedulerProxy::WorkerSchedulerProxy(WebFrameScheduler* frame_scheduler) {
   throttling_observer_handle_ = frame_scheduler->AddThrottlingObserver(
       WebFrameScheduler::ObserverType::kWorkerScheduler, this);
+  parent_frame_type_ = GetFrameOriginType(frame_scheduler);
 }
 
 WorkerSchedulerProxy::~WorkerSchedulerProxy() {
diff --git a/third_party/WebKit/Source/platform/scheduler/child/worker_scheduler_proxy.h b/third_party/WebKit/Source/platform/scheduler/child/worker_scheduler_proxy.h
index d99af7b..4424a703 100644
--- a/third_party/WebKit/Source/platform/scheduler/child/worker_scheduler_proxy.h
+++ b/third_party/WebKit/Source/platform/scheduler/child/worker_scheduler_proxy.h
@@ -12,6 +12,7 @@
 #include "platform/PlatformExport.h"
 #include "platform/WebFrameScheduler.h"
 #include "platform/scheduler/child/page_visibility_state.h"
+#include "platform/scheduler/renderer/frame_origin_type.h"
 #include "platform/wtf/WTF.h"
 
 namespace blink {
@@ -39,10 +40,15 @@
 
   // Should be accessed only from the main thread or during init.
   WebFrameScheduler::ThrottlingState throttling_state() const {
-    DCHECK(WTF::IsMainThread() || !initialized_);
+    DCHECK(IsMainThread() || !initialized_);
     return throttling_state_;
   }
 
+  FrameOriginType parent_frame_type() const {
+    DCHECK(IsMainThread() || !initialized_);
+    return parent_frame_type_;
+  }
+
  private:
   // Can be accessed only from the worker thread.
   base::WeakPtr<WorkerSchedulerImpl> worker_scheduler_;
@@ -58,6 +64,8 @@
 
   bool initialized_ = false;
 
+  FrameOriginType parent_frame_type_;
+
   DISALLOW_COPY_AND_ASSIGN(WorkerSchedulerProxy);
 };
 
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/frame_origin_type.cc b/third_party/WebKit/Source/platform/scheduler/renderer/frame_origin_type.cc
new file mode 100644
index 0000000..3fd9e63
--- /dev/null
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/frame_origin_type.cc
@@ -0,0 +1,26 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "platform/scheduler/renderer/frame_origin_type.h"
+
+#include "platform/WebFrameScheduler.h"
+
+namespace blink {
+namespace scheduler {
+
+FrameOriginType GetFrameOriginType(WebFrameScheduler* scheduler) {
+  DCHECK(scheduler);
+
+  if (scheduler->GetFrameType() == WebFrameScheduler::FrameType::kMainFrame)
+    return FrameOriginType::kMainFrame;
+
+  if (scheduler->IsCrossOrigin()) {
+    return FrameOriginType::kCrossOriginFrame;
+  } else {
+    return FrameOriginType::kSameOriginFrame;
+  }
+}
+
+}  // namespace scheduler
+}  // namespace blink
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/frame_origin_type.h b/third_party/WebKit/Source/platform/scheduler/renderer/frame_origin_type.h
new file mode 100644
index 0000000..66c9a1e
--- /dev/null
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/frame_origin_type.h
@@ -0,0 +1,28 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_WEBKIT_SOURCE_PLATFORM_SCHEDULER_RENDERER_FRAME_ORIGIN_TYPE_H_
+#define THIRD_PARTY_WEBKIT_SOURCE_PLATFORM_SCHEDULER_RENDERER_FRAME_ORIGIN_TYPE_H_
+
+namespace blink {
+class WebFrameScheduler;
+
+namespace scheduler {
+
+// This enum is used for a histogram (RendererSchedulerFrameOriginType)
+// and should not be renumbered.
+enum class FrameOriginType {
+  kMainFrame = 0,
+  kSameOriginFrame = 1,
+  kCrossOriginFrame = 2,
+
+  kCount = 3
+};
+
+FrameOriginType GetFrameOriginType(WebFrameScheduler* frame_scheduler);
+
+}  // namespace scheduler
+}  // namespace blink
+
+#endif  // THIRD_PARTY_WEBKIT_SOURCE_PLATFORM_SCHEDULER_RENDERER_FRAME_ORIGIN_TYPE_H_
diff --git a/third_party/WebKit/Source/platform/weborigin/KURL.h b/third_party/WebKit/Source/platform/weborigin/KURL.h
index 0e82803..9b14a93 100644
--- a/third_party/WebKit/Source/platform/weborigin/KURL.h
+++ b/third_party/WebKit/Source/platform/weborigin/KURL.h
@@ -92,7 +92,7 @@
   static KURL CreateIsolated(const String&);
 
   // Resolves the relative URL with the given base URL. If provided, the
-  // TextEncoding is used to encode non-ASCII characers. The base URL can be
+  // TextEncoding is used to encode non-ASCII characters. The base URL can be
   // null or empty, in which case the relative URL will be interpreted as
   // absolute.
   // FIXME: If the base URL is invalid, this always creates an invalid
diff --git a/third_party/WebKit/public/platform/WebURLError.h b/third_party/WebKit/public/platform/WebURLError.h
index a62a025c..66f048cd 100644
--- a/third_party/WebKit/public/platform/WebURLError.h
+++ b/third_party/WebKit/public/platform/WebURLError.h
@@ -55,6 +55,7 @@
   BLINK_PLATFORM_EXPORT WebURLError(int reason, const WebURL&);
   // |reason| must not be 0.
   BLINK_PLATFORM_EXPORT WebURLError(int reason,
+                                    int extended_reason,
                                     HasCopyInCache,
                                     IsWebSecurityViolation,
                                     const WebURL&);
@@ -63,6 +64,7 @@
                                     const WebURL&);
 
   int reason() const { return reason_; }
+  int extended_reason() const { return extended_reason_; }
   bool has_copy_in_cache() const { return has_copy_in_cache_; }
   bool is_web_security_violation() const { return is_web_security_violation_; }
   const WebURL& url() const { return url_; }
@@ -75,6 +77,9 @@
   // not be 0.
   int reason_;
 
+  // Additional information based on the reason_.
+  int extended_reason_;
+
   // A flag showing whether or not we have a (possibly stale) copy of the
   // requested resource in the cache.
   bool has_copy_in_cache_ = false;
diff --git a/third_party/WebKit/public/web/modules/serviceworker/WebServiceWorkerContextClient.h b/third_party/WebKit/public/web/modules/serviceworker/WebServiceWorkerContextClient.h
index fbbf692..c059aa70 100644
--- a/third_party/WebKit/public/web/modules/serviceworker/WebServiceWorkerContextClient.h
+++ b/third_party/WebKit/public/web/modules/serviceworker/WebServiceWorkerContextClient.h
@@ -116,7 +116,7 @@
 
   // Called when initial script evaluation finished. |success| is true if the
   // evaluation completed with no uncaught exception.
-  virtual void DidEvaluateWorkerScript(bool success) {}
+  virtual void DidEvaluateClassicScript(bool success) {}
 
   // Called when the worker context is initialized. This is probably called
   // after WorkerContextStarted(). (WorkerThread::InitializeOnWorkerThread()
diff --git a/third_party/eu-strip/bin/eu-strip b/third_party/eu-strip/bin/eu-strip
index cdd46c4e..7dcb3c0 100755
--- a/third_party/eu-strip/bin/eu-strip
+++ b/third_party/eu-strip/bin/eu-strip
Binary files differ
diff --git a/third_party/eu-strip/build.sh b/third_party/eu-strip/build.sh
index cca62bb..86f2b67 100755
--- a/third_party/eu-strip/build.sh
+++ b/third_party/eu-strip/build.sh
@@ -8,7 +8,7 @@
 aclocal
 autoconf
 automake --add-missing
-patch -p1 < ../fix-double-free.patch
+patch -p1 < ../fix-elf-size.patch
 mkdir build
 cd build
 ../configure --enable-maintainer-mode
diff --git a/third_party/eu-strip/fix-double-free.patch b/third_party/eu-strip/fix-double-free.patch
deleted file mode 100644
index a6147c3..0000000
--- a/third_party/eu-strip/fix-double-free.patch
+++ /dev/null
@@ -1,18 +0,0 @@
-diff --git a/libelf/elf_end.c b/libelf/elf_end.c
-index 160f0b88..f3a22a0c 100644
---- a/libelf/elf_end.c
-+++ b/libelf/elf_end.c
-@@ -163,13 +163,6 @@ elf_end (Elf *elf)
- 		if (scn->data_base != scn->rawdata_base)
- 		  free (scn->data_base);
- 
--		/* The section data is allocated if we couldn't mmap
--		   the file.  Or if we had to decompress.  */
--		if (elf->map_address == NULL
--		    || scn->rawdata_base == scn->zdata_base
--		    || (scn->flags & ELF_F_MALLOCED) != 0)
--		  free (scn->rawdata_base);
--
- 		/* Free the list of data buffers for the section.
- 		   We don't free the buffers themselves since this
- 		   is the users job.  */
diff --git a/third_party/eu-strip/fix-elf-size.patch b/third_party/eu-strip/fix-elf-size.patch
new file mode 100644
index 0000000..e3fdc8a
--- /dev/null
+++ b/third_party/eu-strip/fix-elf-size.patch
@@ -0,0 +1,61 @@
+diff --git a/libelf/elf32_updatenull.c b/libelf/elf32_updatenull.c
+index d83c0b3f..507e707b 100644
+--- a/libelf/elf32_updatenull.c
++++ b/libelf/elf32_updatenull.c
+@@ -137,7 +137,7 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
+     return -1;
+ 
+   /* At least the ELF header is there.  */
+-  off_t size = elf_typesize (LIBELFBITS, ELF_T_EHDR, 1);
++  ElfW2(LIBELFBITS,Off) size = elf_typesize (LIBELFBITS, ELF_T_EHDR, 1);
+ 
+   /* Set the program header position.  */
+   if (elf->state.ELFW(elf,LIBELFBITS).phdr == NULL)
+@@ -152,7 +152,7 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
+ 	{
+ 	  /* The user is supposed to fill out e_phoff.  Use it and
+ 	     e_phnum to determine the maximum extend.  */
+-	  size = MAX ((size_t) size,
++	  size = MAX (size,
+ 		      ehdr->e_phoff
+ 		      + elf_typesize (LIBELFBITS, ELF_T_PHDR, phnum));
+ 	}
+@@ -330,7 +330,7 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
+ 
+ 	      if (elf->flags & ELF_F_LAYOUT)
+ 		{
+-		  size = MAX ((GElf_Word) size,
++		  size = MAX (size,
+ 			      (shdr->sh_type != SHT_NOBITS
+ 			       ? shdr->sh_offset + shdr->sh_size : 0));
+ 
+@@ -352,9 +352,9 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
+ 		  update_if_changed (shdr->sh_addralign, sh_align,
+ 				     scn->shdr_flags);
+ 
+-		  size = (size + sh_align - 1) & ~(sh_align - 1);
++		  size = (size + sh_align - 1) & ~(ElfW2(LIBELFBITS,Off))(sh_align - 1);
+ 		  int offset_changed = 0;
+-		  update_if_changed (shdr->sh_offset, (GElf_Word) size,
++		  update_if_changed (shdr->sh_offset, size,
+ 				     offset_changed);
+ 		  changed |= offset_changed;
+ 
+@@ -416,7 +416,7 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
+ 	  /* The user is supposed to fill out e_shoff.  Use it and
+ 	     e_shnum (or sh_size of the dummy, first section header)
+ 	     to determine the maximum extend.  */
+-	  size = MAX ((GElf_Word) size,
++	  size = MAX (size,
+ 		      (ehdr->e_shoff
+ 		       + (elf_typesize (LIBELFBITS, ELF_T_SHDR, shnum))));
+ 	}
+@@ -430,7 +430,7 @@ __elfw2(LIBELFBITS,updatenull_wrlock) (Elf *elf, int *change_bop, size_t shnum)
+ #define SHDR_ALIGN sizeof (ElfW2(LIBELFBITS,Off))
+ 	  size = (size + SHDR_ALIGN - 1) & ~(SHDR_ALIGN - 1);
+ 
+-	  update_if_changed (ehdr->e_shoff, (GElf_Word) size, elf->flags);
++	  update_if_changed (ehdr->e_shoff, size, elf->flags);
+ 
+ 	  /* Account for the section header size.  */
+ 	  size += elf_typesize (LIBELFBITS, ELF_T_SHDR, shnum);
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 570824d..1aff76fb 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -30341,6 +30341,17 @@
   <int value="9" label="Delivered"/>
 </enum>
 
+<enum name="NetReportingUploadOutcome">
+  <int value="0" label="Canceled: redirect to insecure URL"/>
+  <int value="1" label="Canceled: auth required"/>
+  <int value="2" label="Canceled: certificate requested"/>
+  <int value="3" label="Canceled: SSL certificate error"/>
+  <int value="4" label="Canceled: Reporting shut down"/>
+  <int value="5" label="Failed: network error"/>
+  <int value="6" label="Succeeded: success"/>
+  <int value="7" label="Succeeded: remove endpoint"/>
+</enum>
+
 <enum name="NetTrustAnchors">
 <!-- Generated from net/data/ssl/root_stores/root_stores.json.
 Called by update_net_trust_anchors.py.-->
@@ -37757,6 +37768,12 @@
   <int value="3" label="Audio and video"/>
 </enum>
 
+<enum name="RendererSchedulerFrameOriginType">
+  <int value="0" label="MainFrame"/>
+  <int value="1" label="SameOriginFrame"/>
+  <int value="2" label="CrossOriginFrame"/>
+</enum>
+
 <enum name="RendererSchedulerFrameType">
   <obsolete>
     Superseded by RendererSchedulerFrameType2 as of 11/2017.
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index d5d1f0b4..e28a6f9 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -48386,6 +48386,26 @@
   </summary>
 </histogram>
 
+<histogram name="Net.Reporting.UploadError"
+    enum="CombinedHttpResponseAndNetErrorCode">
+  <owner>juliatuttle@chromium.org</owner>
+  <summary>
+    The error (net or HTTP) encountered by Reporting trying to upload one or
+    more reports to a single endpoint in a single request, recorded when the
+    upload attempt completes, if and only if the attempt failed with a net error
+    or an HTTP error other than 410 (which is specified to mean &quot;remove
+    endpoint&quot;).
+  </summary>
+</histogram>
+
+<histogram name="Net.Reporting.UploadOutcome" enum="NetReportingUploadOutcome">
+  <owner>juliatuttle@chromium.org</owner>
+  <summary>
+    The outcome of Reporting trying to upload one or more reports to a single
+    endpoint in a single request, recorded when the upload attempt completes.
+  </summary>
+</histogram>
+
 <histogram name="Net.RequestTime">
   <obsolete>
     Replaced by Net.RequestTime2 due to bug in original implementation.
@@ -73221,6 +73241,18 @@
   </summary>
 </histogram>
 
+<histogram base="true" name="RendererScheduler.TaskDurationPerFrameOriginType"
+    enum="RendererSchedulerFrameOriginType">
+  <owner>altimin@chromium.org</owner>
+  <summary>
+    Total cpu time of renderer tasks split by per frame origin type (main frame
+    vs same-origin frame vs cross-origin frame).
+
+    Note that this metric discards tasks longer than 30 seconds because they are
+    considered to be a result of measurement glitch.
+  </summary>
+</histogram>
+
 <histogram name="RendererScheduler.TaskDurationPerFrameType"
     enum="RendererSchedulerFrameType" units="ms">
   <obsolete>
@@ -113180,6 +113212,13 @@
   <affected-histogram name="RendererScheduler.TaskCountPerQueueType"/>
 </histogram_suffixes>
 
+<histogram_suffixes
+    name="RendererScheduler.TaskDurationPerFrameOriginTypeSplit" separator=".">
+  <suffix name="DedicatedWorker"/>
+  <suffix name="DedicatedWorker.Background"/>
+  <affected-histogram name="RendererScheduler.TaskDurationPerFrameOriginType"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="RendererScheduler.TaskDurationPerQueueTypeSplit"
     separator=".">
   <suffix name="Background"
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index 900e231..a723f54f 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -124,6 +124,12 @@
 crbug.com/667432 [ All ] smoothness.gpu_rasterization.top_25_smooth/amazon [ Skip ]
 crbug.com/528474 [ All ] smoothness.gpu_rasterization.top_25_smooth/cnn [ Skip ]
 crbug.com/803869 [ Nexus_5X ] smoothness.gpu_rasterization.top_25_smooth/youtube [ Skip ]
+crbug.com/822925 [ Android_Webview ] smoothness.gpu_rasterization.top_25_smooth/yahoo_games [ Skip ]
+crbug.com/822925 [ Android_Webview ] smoothness.gpu_rasterization.top_25_smooth/yahoo_news [ Skip ]
+crbug.com/822925 [ Android_Webview ] smoothness.gpu_rasterization.top_25_smooth/yahoo_sports [ Skip ]
+
+# Benchmark: smoothness.gpu_rasterization.tough_pinch_zoom_cases
+crbug.com/822925 [ Android_Webview ] smoothness.gpu_rasterization.tough_pinch_zoom_cases/http://games.yahoo.com [ Skip ]
 
 # Benchmark: smoothness.key_desktop_move_cases
 crbug.com/750131 [ Win ] smoothness.key_desktop_move_cases/https://mail.google.com/mail/ [ Skip ]
@@ -146,6 +152,7 @@
 
 # Benchmark: smoothness.pathological_mobile_sites
 crbug.com/685342 [ Nexus_7 ] smoothness.pathological_mobile_sites/* [ Skip ]
+crbug.com/822925 [ Android_Webview ] smoothness.pathological_mobile_sites/http://sports.yahoo.com/ [ Skip ]
 
 # Benchmark: smoothness.simple_mobile_sites
 crbug.com/750833 [ Android_Webview ] smoothness.simple_mobile_sites/https://www.flickr.com/ [ Skip ]
@@ -163,6 +170,9 @@
 crbug.com/762165 [ Win ] smoothness.top_25_smooth/google_image_search [ Skip ]
 crbug.com/762165 [ Win ] smoothness.top_25_smooth/google_docs [ Skip ]
 crbug.com/812628 [ Nexus_5X ] smoothness.top_25_smooth/youtube [ Skip ]
+crbug.com/822925 [ Android_Webview ] smoothness.top_25_smooth/yahoo_games [ Skip ]
+crbug.com/822925 [ Android_Webview ] smoothness.top_25_smooth/yahoo_news [ Skip ]
+crbug.com/822925 [ Android_Webview ] smoothness.top_25_smooth/yahoo_sports [ Skip ]
 
 # Benchmark: smoothness.tough_ad_cases
 crbug.com/555089 [ Android_Svelte ] smoothness.tough_ad_cases/* [ Skip ]
@@ -180,6 +190,9 @@
 crbug.com/785286 [ Android_Webview ] smoothness.tough_canvas_cases/http://www.effectgames.com/demos/canvascycle/ [ Skip ]
 crbug.com/364248 [ Nexus_5 ] smoothness.tough_canvas_cases/http://geoapis.appspot.com/agdnZW9hcGlzchMLEgtFeGFtcGxlQ29kZRjh1wIM [ Skip ]
 
+# Benchmark: smoothness.tough_pinch_zoom_cases
+crbug.com/822925 [ Android_Webview ] smoothness.tough_pinch_zoom_cases/http://games.yahoo.com [ Skip ]
+
 # Benchmark: smoothness.tough_scrolling_cases
 crbug.com/785473 [ Android_Webview ] smoothness.tough_scrolling_cases/canvas_15000_pixels_per_second [ Skip ]
 crbug.com/785473 [ Android_Webview ] smoothness.tough_scrolling_cases/canvas_20000_pixels_per_second [ Skip ]
diff --git a/ui/aura/BUILD.gn b/ui/aura/BUILD.gn
index 657b7c4c..16f8d0d 100644
--- a/ui/aura/BUILD.gn
+++ b/ui/aura/BUILD.gn
@@ -201,6 +201,10 @@
 
   if (use_ozone) {
     deps += [ "//ui/ozone" ]
+    sources += [
+      "mus/platform_event_source_mus_ozone.cc",
+      "mus/platform_event_source_mus_ozone.h",
+    ]
   }
 
   if (is_android) {
@@ -365,6 +369,7 @@
     "mus/window_port_mus_unittest.cc",
     "mus/window_tree_client_unittest.cc",
     "mus/window_tree_host_mus_unittest.cc",
+    "test/aura_test_suite.h",
     "test/run_all_unittests.cc",
     "window_event_dispatcher_unittest.cc",
     "window_occlusion_tracker_unittest.cc",
diff --git a/ui/aura/mus/platform_event_source_mus_ozone.cc b/ui/aura/mus/platform_event_source_mus_ozone.cc
new file mode 100644
index 0000000..dfd4133
--- /dev/null
+++ b/ui/aura/mus/platform_event_source_mus_ozone.cc
@@ -0,0 +1,26 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/aura/mus/platform_event_source_mus_ozone.h"
+
+#include "ui/events/event.h"
+#include "ui/events/platform/platform_event_observer.h"
+
+namespace aura {
+
+PlatformEventSourceMus::PlatformEventSourceMus() = default;
+
+PlatformEventSourceMus::~PlatformEventSourceMus() = default;
+
+void PlatformEventSourceMus::OnWillProcessEvent(ui::Event* event) {
+  for (ui::PlatformEventObserver& observer : observers())
+    observer.WillProcessEvent(event);
+}
+
+void PlatformEventSourceMus::OnDidProcessEvent(ui::Event* event) {
+  for (ui::PlatformEventObserver& observer : observers())
+    observer.DidProcessEvent(event);
+}
+
+}  // namespace aura
diff --git a/ui/aura/mus/platform_event_source_mus_ozone.h b/ui/aura/mus/platform_event_source_mus_ozone.h
new file mode 100644
index 0000000..8557688
--- /dev/null
+++ b/ui/aura/mus/platform_event_source_mus_ozone.h
@@ -0,0 +1,35 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_AURA_MUS_PLATFORM_EVENT_SOURCE_MUS_OZONE_H_
+#define UI_AURA_MUS_PLATFORM_EVENT_SOURCE_MUS_OZONE_H_
+
+#include "ui/events/platform/platform_event_source.h"
+
+namespace ui {
+class Event;
+}
+
+namespace aura {
+
+// PlatformEventSource implementation for mus with ozone. WindowTreeClient owns
+// and installs this. WindowTreeClient calls this to notify observers as
+// necessary.
+class PlatformEventSourceMus : public ui::PlatformEventSource {
+ public:
+  PlatformEventSourceMus();
+  ~PlatformEventSourceMus() override;
+
+  // These two functions are called from WindowTreeClient before/after
+  // dispatching events. They forward to observers.
+  void OnWillProcessEvent(ui::Event* event);
+  void OnDidProcessEvent(ui::Event* event);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PlatformEventSourceMus);
+};
+
+}  // namespace aura
+
+#endif  // UI_AURA_MUS_PLATFORM_EVENT_SOURCE_MUS_OZONE_H_
diff --git a/ui/aura/mus/window_tree_client.cc b/ui/aura/mus/window_tree_client.cc
index 9eb720c..297f56b 100644
--- a/ui/aura/mus/window_tree_client.cc
+++ b/ui/aura/mus/window_tree_client.cc
@@ -70,6 +70,10 @@
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/size.h"
 
+#if defined(USE_OZONE)
+#include "ui/aura/mus/platform_event_source_mus_ozone.h"
+#endif
+
 namespace aura {
 namespace {
 
@@ -91,11 +95,21 @@
   ~EventAckHandler() override {
     base::RunLoop::RemoveNestingObserverOnCurrentThread(this);
     if (ack_callback_) {
+      NotifyPlatformEventSource();
       ack_callback_->Run(handled_ ? ui::mojom::EventResult::HANDLED
                                   : ui::mojom::EventResult::UNHANDLED);
     }
   }
 
+#if defined(USE_OZONE)
+  void SetPlatformEventSourceAndEvent(
+      PlatformEventSourceMus* platform_event_source,
+      ui::Event* event) {
+    event_ = event;
+    platform_event_source_ = platform_event_source;
+  }
+#endif
+
   void set_handled(bool handled) { handled_ = handled; }
 
   // base::RunLoop::NestingObserver:
@@ -103,14 +117,26 @@
     // Acknowledge the event immediately if a nested run loop starts.
     // Otherwise we appear unresponsive for the life of the nested run loop.
     if (ack_callback_) {
+      NotifyPlatformEventSource();
       ack_callback_->Run(ui::mojom::EventResult::HANDLED);
       ack_callback_.reset();
     }
   }
 
  private:
+  void NotifyPlatformEventSource() {
+#if defined(USE_OZONE)
+    if (platform_event_source_)
+      platform_event_source_->OnDidProcessEvent(event_);
+#endif
+  }
+
   std::unique_ptr<EventResultCallback> ack_callback_;
   bool handled_ = false;
+#if defined(USE_OZONE)
+  ui::Event* event_ = nullptr;
+  PlatformEventSourceMus* platform_event_source_ = nullptr;
+#endif
 
   DISALLOW_COPY_AND_ASSIGN(EventAckHandler);
 };
@@ -197,6 +223,7 @@
   factory->CreateWindowTree(MakeRequest(&window_tree), std::move(client),
                             automatically_create_display_roots);
   wtc->SetWindowTree(std::move(window_tree));
+  wtc->CreatePlatformEventSourceIfNecessary();
   return wtc;
 }
 
@@ -467,6 +494,13 @@
   }
 }
 
+void WindowTreeClient::CreatePlatformEventSourceIfNecessary() {
+#if defined(USE_OZONE)
+  if (!ui::PlatformEventSource::GetInstance())
+    platform_event_source_ = std::make_unique<PlatformEventSourceMus>();
+#endif
+}
+
 void WindowTreeClient::RegisterWindowMus(WindowMus* window) {
   DCHECK(windows_.find(window->server_id()) == windows_.end());
   windows_[window->server_id()] = window;
@@ -1623,7 +1657,6 @@
     }
   }
 
-  EventAckHandler ack_handler(CreateEventResultCallback(event_id));
   // TODO(moshayedi): crbug.com/617222. No need to convert to ui::MouseEvent or
   // ui::TouchEvent once we have proper support for pointer events.
   std::unique_ptr<ui::Event> mapped_event = MapEvent(*event.get());
@@ -1650,6 +1683,13 @@
     event_to_dispatch = mapped_event_with_native.get();
   }
 #endif
+  // |ack_handler| may use |event_to_dispatch| from its destructor, so it needs
+  // to be destroyed after |event_to_dispatch| is destroyed.
+  EventAckHandler ack_handler(CreateEventResultCallback(event_id));
+#if defined(USE_OZONE)
+  ack_handler.SetPlatformEventSourceAndEvent(platform_event_source_.get(),
+                                             event_to_dispatch);
+#endif
 
   WindowMus* display_root_window = GetWindowByServerId(display_root_window_id);
   if (display_root_window && event->IsLocatedEvent() &&
@@ -1669,6 +1709,11 @@
     // focused window, which may have changed by the time we process the event.
     ui::Event::DispatcherApi(event_to_dispatch).set_target(window->GetWindow());
   }
+#if defined(USE_OZONE)
+  if (platform_event_source_)
+    platform_event_source_->OnWillProcessEvent(event_to_dispatch);
+#endif
+
   GetWindowTreeHostMus(window)->SendEventToSink(event_to_dispatch);
 
   ack_handler.set_handled(event_to_dispatch->handled());
diff --git a/ui/aura/mus/window_tree_client.h b/ui/aura/mus/window_tree_client.h
index 79354cd0..bc876ae 100644
--- a/ui/aura/mus/window_tree_client.h
+++ b/ui/aura/mus/window_tree_client.h
@@ -73,6 +73,7 @@
 class InFlightFocusChange;
 class InFlightPropertyChange;
 class InFlightVisibleChange;
+class PlatformEventSourceMus;
 class MusContextFactory;
 class WindowMus;
 class WindowPortMus;
@@ -244,6 +245,9 @@
       scoped_refptr<base::SingleThreadTaskRunner> io_task_runner = nullptr,
       bool create_discardable_memory = true);
 
+  // Creates a PlatformEventSourceMus if not created yet.
+  void CreatePlatformEventSourceIfNecessary();
+
   void RegisterWindowMus(WindowMus* window);
 
   WindowMus* GetWindowByServerId(ui::Id id);
@@ -737,6 +741,10 @@
   // removed.
   bool install_drag_drop_client_ = true;
 
+#if defined(USE_OZONE)
+  std::unique_ptr<PlatformEventSourceMus> platform_event_source_;
+#endif
+
   base::WeakPtrFactory<WindowTreeClient> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(WindowTreeClient);
diff --git a/ui/aura/mus/window_tree_client_unittest.cc b/ui/aura/mus/window_tree_client_unittest.cc
index ebbc7a9..1059364 100644
--- a/ui/aura/mus/window_tree_client_unittest.cc
+++ b/ui/aura/mus/window_tree_client_unittest.cc
@@ -38,6 +38,7 @@
 #include "ui/aura/mus/window_tree_host_mus.h"
 #include "ui/aura/mus/window_tree_host_mus_init_params.h"
 #include "ui/aura/test/aura_mus_test_base.h"
+#include "ui/aura/test/aura_test_suite.h"
 #include "ui/aura/test/mus/test_window_tree.h"
 #include "ui/aura/test/mus/window_tree_client_private.h"
 #include "ui/aura/test/test_window_delegate.h"
@@ -55,6 +56,8 @@
 #include "ui/display/screen.h"
 #include "ui/events/event.h"
 #include "ui/events/event_utils.h"
+#include "ui/events/platform/platform_event_observer.h"
+#include "ui/events/platform/platform_event_source.h"
 #include "ui/events/test/test_event_handler.h"
 #include "ui/gfx/geometry/dip_util.h"
 #include "ui/gfx/geometry/rect.h"
@@ -2968,8 +2971,6 @@
             last_event->root_location());
 }
 
-namespace {
-
 class TestEmbedRootDelegate : public EmbedRootDelegate {
  public:
   TestEmbedRootDelegate() = default;
@@ -2984,8 +2985,6 @@
   DISALLOW_COPY_AND_ASSIGN(TestEmbedRootDelegate);
 };
 
-}  // namespace
-
 // Verifies we don't crash when focus changes to a window in an EmbedRoot.
 TEST_F(WindowTreeClientClientTest, ChangeFocusInEmbedRootWindow) {
   TestEmbedRootDelegate embed_root_delegate;
@@ -2997,4 +2996,84 @@
   window_tree_client()->OnWindowFocused(server_id(embed_root->window()));
 }
 
+#if defined(USE_OZONE)
+
+class TestPlatformEventObserver : public ui::PlatformEventObserver {
+ public:
+  TestPlatformEventObserver() = default;
+  ~TestPlatformEventObserver() override = default;
+
+  int will_process_count() const { return will_process_count_; }
+  int did_process_count() const { return did_process_count_; }
+  ui::EventType will_process_type() const { return will_process_type_; }
+  ui::EventType did_process_type() const { return did_process_type_; }
+
+  // PlatformEventObserver:
+  void WillProcessEvent(const ui::PlatformEvent& event) override {
+    will_process_count_++;
+    will_process_type_ = static_cast<const ui::Event*>(event)->type();
+  }
+  void DidProcessEvent(const ui::PlatformEvent& event) override {
+    did_process_count_++;
+    did_process_type_ = static_cast<const ui::Event*>(event)->type();
+  }
+
+ private:
+  int will_process_count_ = 0;
+  int did_process_count_ = 0;
+  ui::EventType will_process_type_ = ui::ET_UNKNOWN;
+  ui::EventType did_process_type_ = ui::ET_UNKNOWN;
+
+  DISALLOW_COPY_AND_ASSIGN(TestPlatformEventObserver);
+};
+
+// Base class that installs a new version of Env configured for Mus in SetUp()
+// (and installs a new version of Env configured for Local during TearDown()).
+// This is necessary as when Env is created with a Model of Local it installs
+// a PlatformEventSource, not the one that WindowTreeClient installs.
+class WindowTreeClientWmOzoneTest : public test::AuraMusWmTestBase {
+ public:
+  WindowTreeClientWmOzoneTest() = default;
+  ~WindowTreeClientWmOzoneTest() override = default;
+
+  // test::AuraMusWmTestBase:
+  void SetUp() override {
+    env_reinstaller_ = std::make_unique<test::EnvReinstaller>();
+    env_ = Env::CreateInstance(Env::Mode::MUS);
+    AuraMusWmTestBase::SetUp();
+  }
+
+  void TearDown() override {
+    AuraMusWmTestBase::TearDown();
+    env_.reset();
+    env_reinstaller_.reset();
+  }
+
+ private:
+  std::unique_ptr<test::EnvReinstaller> env_reinstaller_;
+  std::unique_ptr<Env> env_;
+
+  DISALLOW_COPY_AND_ASSIGN(WindowTreeClientWmOzoneTest);
+};
+
+// Used to verify PlatformEventSource is correctly wired up in ozone.
+TEST_F(WindowTreeClientWmOzoneTest, PlatformEventSourceInstalled) {
+  ASSERT_TRUE(ui::PlatformEventSource::GetInstance());
+  TestPlatformEventObserver test_observer;
+  ui::PlatformEventSource::GetInstance()->AddPlatformEventObserver(
+      &test_observer);
+  ui::MouseEvent event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(),
+                       ui::EventTimeForNow(), ui::EF_NONE, 0);
+  window_tree_client()->OnWindowInputEvent(1, server_id(root_window()), 0,
+                                           ui::Id(), gfx::PointF(),
+                                           ui::Event::Clone(event), 0);
+  ui::PlatformEventSource::GetInstance()->RemovePlatformEventObserver(
+      &test_observer);
+  EXPECT_EQ(1, test_observer.will_process_count());
+  EXPECT_EQ(1, test_observer.did_process_count());
+  EXPECT_EQ(ui::ET_MOUSE_MOVED, test_observer.will_process_type());
+  EXPECT_EQ(ui::ET_MOUSE_MOVED, test_observer.did_process_type());
+}
+#endif
+
 }  // namespace aura
diff --git a/ui/aura/test/aura_test_suite.h b/ui/aura/test/aura_test_suite.h
new file mode 100644
index 0000000..8e805f97
--- /dev/null
+++ b/ui/aura/test/aura_test_suite.h
@@ -0,0 +1,42 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_AURA_TEST_AURA_TEST_SUITE_H_
+#define UI_AURA_TEST_AURA_TEST_SUITE_H_
+
+#include "base/macros.h"
+
+namespace aura {
+namespace test {
+
+// The aura test suite is configured in such a way that Env is shared across
+// all tests. If a test needs to install a fresh copy of env it can create an
+// instance of this. The constructor destroys the global instance, and the
+// destructor reinstates it.
+// Typical usage is:
+//    MyTest::SetUp() {
+//      env_reinstaller_ = std::make_unique<EnvReinstaller>();
+//      my_test_env_ = Env::CreateInstance()
+//      AuraTestBase::SetUp();
+//    }
+//    MyTest::TearDown() {
+//      AuraTestBase::TearDown();
+//      my_test_env_.reset();
+//      env_reinstaller_.reset();
+//    }
+// TODO(sky): this is ugly. Instead look into having each test install it's own
+// Env. https://crbug.com/822968
+class EnvReinstaller {
+ public:
+  EnvReinstaller();
+  ~EnvReinstaller();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(EnvReinstaller);
+};
+
+}  // namespace test
+}  // namespace aura
+
+#endif  // UI_AURA_TEST_AURA_TEST_SUITE_H_
diff --git a/ui/aura/test/mus/window_tree_client_private.cc b/ui/aura/test/mus/window_tree_client_private.cc
index cd77b24..aa81a7f 100644
--- a/ui/aura/test/mus/window_tree_client_private.cc
+++ b/ui/aura/test/mus/window_tree_client_private.cc
@@ -109,6 +109,8 @@
 void WindowTreeClientPrivate::SetWindowManagerClient(
     ui::mojom::WindowManagerClient* client) {
   tree_client_impl_->window_manager_client_ = client;
+  // Mirrors what CreateForWindowManager() does.
+  tree_client_impl_->CreatePlatformEventSourceIfNecessary();
 }
 
 bool WindowTreeClientPrivate::HasPointerWatcher() {
diff --git a/ui/aura/test/run_all_unittests.cc b/ui/aura/test/run_all_unittests.cc
index f9b7de7..e728a76 100644
--- a/ui/aura/test/run_all_unittests.cc
+++ b/ui/aura/test/run_all_unittests.cc
@@ -8,15 +8,27 @@
 #include "base/test/test_suite.h"
 #include "mojo/edk/embedder/embedder.h"
 #include "ui/aura/env.h"
+#include "ui/aura/test/aura_test_suite.h"
 #include "ui/gl/gl_surface.h"
 #include "ui/gl/test/gl_surface_test_support.h"
 
+class AuraTestSuite;
+
+namespace {
+AuraTestSuite* g_test_suite = nullptr;
+}
+
 class AuraTestSuite : public base::TestSuite {
  public:
   AuraTestSuite(int argc, char** argv) : base::TestSuite(argc, argv) {}
 
+  void DestroyEnv() { env_.reset(); }
+  void CreateEnv() { env_ = aura::Env::CreateInstance(); }
+
  protected:
   void Initialize() override {
+    DCHECK(!g_test_suite);
+    g_test_suite = this;
     base::TestSuite::Initialize();
     gl::GLSurfaceTestSupport::InitializeOneOff();
     env_ = aura::Env::CreateInstance();
@@ -25,6 +37,7 @@
   void Shutdown() override {
     env_.reset();
     base::TestSuite::Shutdown();
+    g_test_suite = nullptr;
   }
 
  private:
@@ -32,6 +45,20 @@
   DISALLOW_COPY_AND_ASSIGN(AuraTestSuite);
 };
 
+namespace aura {
+namespace test {
+
+EnvReinstaller::EnvReinstaller() {
+  g_test_suite->DestroyEnv();
+}
+
+EnvReinstaller::~EnvReinstaller() {
+  g_test_suite->CreateEnv();
+}
+
+}  // namespace test
+}  // namespace aura
+
 int main(int argc, char** argv) {
   AuraTestSuite test_suite(argc, argv);
 
diff --git a/ui/display/display_switches.cc b/ui/display/display_switches.cc
index d063f20..05b8b4c 100644
--- a/ui/display/display_switches.cc
+++ b/ui/display/display_switches.cc
@@ -64,11 +64,4 @@
 const base::Feature kHighDynamicRange{"HighDynamicRange",
                                       base::FEATURE_ENABLED_BY_DEFAULT};
 
-#if defined(OS_CHROMEOS)
-// Enables using the monitor's provided color space information when rendering.
-// TODO(mcasas): remove this flag http://crbug.com/771345.
-const base::Feature kUseMonitorColorSpace{"UseMonitorColorSpace",
-                                          base::FEATURE_ENABLED_BY_DEFAULT};
-#endif
-
 }  // namespace features
diff --git a/ui/display/display_switches.h b/ui/display/display_switches.h
index da146eb..5e79ef0 100644
--- a/ui/display/display_switches.h
+++ b/ui/display/display_switches.h
@@ -34,10 +34,6 @@
 
 DISPLAY_EXPORT extern const base::Feature kHighDynamicRange;
 
-#if defined(OS_CHROMEOS)
-DISPLAY_EXPORT extern const base::Feature kUseMonitorColorSpace;
-#endif
-
 }  // namespace features
 
 #endif  // UI_DISPLAY_DISPLAY_SWITCHES_H_
diff --git a/ui/display/manager/display_manager.cc b/ui/display/manager/display_manager.cc
index 5de409eb..5a92b5e 100644
--- a/ui/display/manager/display_manager.cc
+++ b/ui/display/manager/display_manager.cc
@@ -2037,13 +2037,8 @@
   new_display.set_rotation(display_info.GetActiveRotation());
   new_display.set_touch_support(display_info.touch_support());
   new_display.set_maximum_cursor_size(display_info.maximum_cursor_size());
-#if defined(OS_CHROMEOS)
-  // TODO(mcasas): remove this check, http://crbug.com/771345.
-  if (base::FeatureList::IsEnabled(features::kUseMonitorColorSpace))
+  if (!Display::HasForceColorProfile())
     new_display.set_color_space(display_info.color_space());
-#else
-  new_display.set_color_space(display_info.color_space());
-#endif
 
   if (internal_display_has_accelerometer_ && Display::IsInternalDisplayId(id)) {
     new_display.set_accelerometer_support(
diff --git a/ui/events/platform/platform_event_source.h b/ui/events/platform/platform_event_source.h
index b4e3084..ca193b33 100644
--- a/ui/events/platform/platform_event_source.h
+++ b/ui/events/platform/platform_event_source.h
@@ -78,6 +78,8 @@
   // current message-loop iteration.
   virtual uint32_t DispatchEvent(PlatformEvent platform_event);
 
+  base::ObserverList<PlatformEventObserver>& observers() { return observers_; }
+
  private:
   friend class ScopedEventDispatcher;
   friend class test::PlatformEventSourceTestAPI;
diff --git a/ui/events/platform/x11/x11_hotplug_event_handler.cc b/ui/events/platform/x11/x11_hotplug_event_handler.cc
index 077f181..1f3ad15 100644
--- a/ui/events/platform/x11/x11_hotplug_event_handler.cc
+++ b/ui/events/platform/x11/x11_hotplug_event_handler.cc
@@ -108,13 +108,9 @@
 struct DeviceInfo {
   DeviceInfo(const XIDeviceInfo& device,
              DeviceType type,
-             const base::FilePath& path,
-             uint16_t vendor,
-             uint16_t product)
+             const base::FilePath& path)
       : id(device.deviceid),
         name(device.name),
-        vendor_id(vendor),
-        product_id(product),
         use(device.use),
         type(type),
         path(path) {
@@ -143,10 +139,6 @@
   // Internal device name.
   std::string name;
 
-  // USB-style device identifiers.
-  uint16_t vendor_id;
-  uint16_t product_id;
-
   // Device type (ie: XIMasterPointer)
   int use;
 
@@ -433,31 +425,8 @@
         (device.deviceid >= 0 && device.deviceid < kMaxDeviceNum)
             ? device_types[device.deviceid]
             : DEVICE_TYPE_OTHER;
-
-    // Obtain the USB-style vendor and product identifiers.
-    // (On Linux, XI2 makes this available for all evdev devices.
-    uint32_t* product_info;
-    Atom type;
-    int format_return;
-    unsigned long num_items_return;
-    unsigned long bytes_after_return;
-    uint16_t vendor = 0;
-    uint16_t product = 0;
-    if (XIGetProperty(gfx::GetXDisplay(), device.deviceid,
-                      gfx::GetAtom(XI_PROP_PRODUCT_ID), 0, 2, 0, XA_INTEGER,
-                      &type, &format_return, &num_items_return,
-                      &bytes_after_return,
-                      reinterpret_cast<unsigned char**>(&product_info)) == 0 &&
-        product_info) {
-      if (num_items_return == 2) {
-        vendor = product_info[0];
-        product = product_info[1];
-      }
-      XFree(product_info);
-    }
-
-    device_infos.push_back(DeviceInfo(
-        device, device_type, GetDevicePath(display, device), vendor, product));
+    device_infos.push_back(
+        DeviceInfo(device, device_type, GetDevicePath(display, device)));
   }
 
   // X11 is not thread safe, so first get all the required state.
diff --git a/ui/latency/BUILD.gn b/ui/latency/BUILD.gn
index b755496..093ad755 100644
--- a/ui/latency/BUILD.gn
+++ b/ui/latency/BUILD.gn
@@ -9,6 +9,8 @@
   sources = [
     "fixed_point.cc",
     "fixed_point.h",
+    "histograms.cc",
+    "histograms.h",
     "latency_histogram_macros.h",
     "latency_info.cc",
     "latency_info.h",
@@ -40,6 +42,9 @@
 test("latency_unittests") {
   sources = [
     "fixed_point_unittest.cc",
+    "histograms_test_common.cc",
+    "histograms_test_common.h",
+    "histograms_unittest.cc",
     "latency_info_unittest.cc",
   ]
 
@@ -66,3 +71,21 @@
     ]
   }
 }
+
+test("latency_perftests") {
+  sources = [
+    "histograms_perftest.cc",
+    "histograms_test_common.cc",
+    "histograms_test_common.h",
+  ]
+
+  deps = [
+    ":latency",
+    "//base",
+    "//base/test:test_support",
+    "//mojo/edk/test:run_all_unittests",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//testing/perf",
+  ]
+}
diff --git a/ui/latency/histograms.cc b/ui/latency/histograms.cc
new file mode 100644
index 0000000..5513fdd
--- /dev/null
+++ b/ui/latency/histograms.cc
@@ -0,0 +1,385 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/latency/histograms.h"
+
+#include <cmath>
+#include <limits>
+
+#include "base/bits.h"
+#include "base/time/time.h"
+#include "ui/latency/fixed_point.h"
+
+namespace {
+
+// Calculates percentiles in a way that can be shared by different histograms.
+ui::PercentileResults PercentilesHelper(
+    ui::frame_metrics::BoundaryIterator* boundary_iterator,
+    const uint32_t* buckets_begin,
+    const uint32_t* buckets_end,
+    uint64_t total_samples) {
+  ui::PercentileResults result;
+  uint64_t boundary_left = 0;
+  uint64_t boundary_right = boundary_iterator->Next();
+
+  double thresholds[ui::PercentileResults::kCount];
+  for (size_t i = 0; i < ui::PercentileResults::kCount; i++) {
+    thresholds[i] = ui::PercentileResults::kPercentiles[i] * total_samples;
+  }
+
+  uint64_t accumulator = 0;
+  size_t result_index = 0;
+  for (const uint32_t* bucket = buckets_begin; bucket < buckets_end; bucket++) {
+    accumulator += *bucket;
+    // Multiple percentiles might be calculated from the same bucket,
+    // so we use a while loop here.
+    while (accumulator > thresholds[result_index]) {
+      const double overage = accumulator - thresholds[result_index];
+      double b0_fraction = overage / (*bucket);
+      double b1_fraction = 1.0 - b0_fraction;
+
+      // Use a linear interpolation between two buckets.
+      // This assumes the samples are evenly distributed within a bucket.
+      // TODO(brianderson): Consider neighboring bucket sizes and fit to a
+      //   curve. http://crbug.com/821879
+      const double estimate =
+          b0_fraction * boundary_left + b1_fraction * boundary_right;
+      result.values[result_index] = estimate;
+      result_index++;
+      if (result_index >= ui::PercentileResults::kCount)
+        return result;
+    }
+
+    boundary_left = boundary_right;
+    boundary_right = boundary_iterator->Next();
+  }
+
+  NOTREACHED();
+  return result;
+}
+
+}  // namespace
+
+namespace ui {
+
+constexpr double PercentileResults::kPercentiles[];
+constexpr size_t PercentileResults::kCount;
+
+namespace frame_metrics {
+
+constexpr size_t RatioHistogram::kBucketCount;
+constexpr size_t VSyncHistogram::kBucketCount;
+
+// Ratio Histogram.
+// The distribution of each category of buckets in this historam is
+// exponential, however each category is divided into N linear buckets
+// depending on how much precision we want for that category. How much
+// precision a category gets depends on how often we expect that bucket to be
+// used and how important those buckets are.
+//
+// Most of the precision is allocated for values just > 1 since most ratios,
+// when not ~0 will be > 1. And since ratios are likely to be near whole
+// numbers (some multiple of the vsync), we only give it a precision of 1/2.
+// You can think about the stride of each category as the number of vsyncs of
+// precision that category will have.
+//
+// There will be aliasing, but because of the vsync aligned linear division
+// of each category, we won't get a bucket that represents fewer vsyncs than
+// its fprevious bucket.
+//
+// This is in contrast to the default exponential distribution of UMA
+// buckets, which result in a constant precision for each bucket and would
+// allocate lots of very small buckets near 0 where we don't need the
+// precision.
+
+namespace {
+
+constexpr size_t kBucketHalfStrideFirstBucketIndex = 17;
+
+// Within the range [16, 4096), there are 9 categories of buckets that each
+// start with a power of 2. Within a category, successive buckets have a fixed
+// stride. Across categories, the strides increase exponentionally, encoded
+// as powers of 2 in |stride_shift|, which increases linearly.
+struct RatioBucketCategory {
+  uint8_t first_bucket_index;
+  uint8_t stride_shift;
+};
+using RatioCategoryHelper = std::array<RatioBucketCategory, 9>;
+constexpr RatioCategoryHelper kCategories16to4096 = {
+    // first_bucket_index of each row below is the previous one + number of
+    // buckets. Each entry is {first_bucket_index, stride_shift}.
+    {{47, 0},      // [16, 32) stride 1 => 16 buckets.
+     {63, 1},      // [32, 64) stride 2 => 16 buckets.
+     {79, 3},      // [64, 128) stride 8 => 8 buckets.
+     {87, 4},      // [128, 256) stride 16 => 8 buckets.
+     {95, 6},      // [256, 512) stride 64 => 4 buckets
+     {99, 7},      // [512, 1024) stride 128 => 4 buckets.
+     {103, 9},     // [1024, 2048) stride 512 => 2 buckets.
+     {105, 10},    // [2048, 4096) stride 1024 => 2 buckets.
+     {107, 12}}};  // [4096, 8192) stride 4096 => 1 bucket.
+
+// The delegate RatioBoundary::Percentiles will pass to PercentilesHelper.
+struct RatioBoundaryIterator : public BoundaryIterator {
+  ~RatioBoundaryIterator() override = default;
+
+  size_t bucket = 0;
+  uint64_t boundary = 0;
+  RatioCategoryHelper::const_iterator b16to4096 = kCategories16to4096.begin();
+  uint64_t next_boundary_to_change_category =
+      32 * frame_metrics::kFixedPointMultiplier;
+
+  uint64_t Next() override {
+    if (bucket == 0) {
+      // The first bucket is [0, 1).
+      boundary = 1;
+    } else if (bucket < kBucketHalfStrideFirstBucketIndex ||
+               bucket >= kCategories16to4096.back().first_bucket_index) {
+      // The start and end buckets increase in size by powers of 2.
+      boundary *= 2;
+    } else if (bucket < kCategories16to4096.front().first_bucket_index) {
+      // The 30 buckets before 47 have a stride of .5 and represent the
+      // range [1, 16).
+      boundary += (frame_metrics::kFixedPointMultiplier / 2);
+    } else {
+      // The rest of the buckets are defined by kCategories16to4096.
+      DCHECK(b16to4096 < kCategories16to4096.end());
+      boundary +=
+          (frame_metrics::kFixedPointMultiplier << b16to4096->stride_shift);
+      // The category changes for every power of 2.
+      if (boundary >= next_boundary_to_change_category) {
+        next_boundary_to_change_category *= 2;
+        b16to4096++;
+      }
+    }
+
+    bucket++;
+    return boundary;
+  }
+};
+
+}  // namespace
+
+std::unique_ptr<BoundaryIterator> CreateRatioIteratorForTesting() {
+  return std::make_unique<RatioBoundaryIterator>();
+}
+
+RatioHistogram::RatioHistogram() = default;
+RatioHistogram::~RatioHistogram() = default;
+
+void RatioHistogram::AddSample(uint32_t ratio, uint32_t weight) {
+  size_t bucket = 0;
+
+  // Precomputed thresholds for the log base 2 of the ratio that help
+  // determine which category of buckets the sample should go in.
+  constexpr int kLog2HalfStrideStart = kFixedPointShift;
+  constexpr int kLog2Cats16to4096Start = kFixedPointShift + 4;  // 2^4 = 16.
+  constexpr int kLog2_4096Pow2Start = kFixedPointShift + 12;    // 2^12 = 4096.
+
+  if (ratio == 0) {
+    bucket = 0;
+  } else {
+    int log2 = base::bits::Log2Floor(ratio);
+    DCHECK_GE(log2, 0);
+    if (log2 < kLog2HalfStrideStart) {
+      // [2^-16, 1) pow of 2 strides => 16 buckets. (16x1)
+      bucket = 1 + log2;
+    } else if (log2 < kLog2Cats16to4096Start) {
+      // [1, 16) stride 1/2 => 30 buckets. (2 + 4 + 8 + 16)
+      const int first_bucket_index = kBucketHalfStrideFirstBucketIndex;
+      const int category_start = kFixedPointMultiplier;
+      const int total_shift = kFixedPointShift - 1;  // -1 multiplies by 2.
+      const int category_offset = (ratio - category_start) >> total_shift;
+      bucket = first_bucket_index + category_offset;
+    } else if (log2 < kLog2_4096Pow2Start) {
+      // [16, 32) stride 1 => 16 buckets.
+      // [32, 64) stride 2 => 16 buckets.
+      // [64, 128) stride 8 => 8 buckets.
+      // [128, 256) stride 16 => 8 buckets.
+      // [256, 512) stride 64 => 4 buckets.
+      // [512, 1024) stride 128 => 4 buckets.
+      // [1024, 2048) stride 512 => 2 buckets.
+      // [2048, 4096) stride 1024 => 2 buckets.
+      const int category = log2 - kLog2Cats16to4096Start;
+      const int category_start = 1 << log2;
+      const int total_shift =
+          (kFixedPointShift + kCategories16to4096[category].stride_shift);
+      const int category_offset = (ratio - category_start) >> total_shift;
+      bucket =
+          kCategories16to4096[category].first_bucket_index + category_offset;
+    } else {
+      // [4096, 2^16) pow of 2 strides => 4 buckets. (4x1)
+      const int category_offset = log2 - kLog2_4096Pow2Start;
+      bucket = kCategories16to4096.back().first_bucket_index + category_offset;
+    }
+  }
+  DCHECK_LT(bucket, kBucketCount);
+
+  // Verify overflow isn't an issue.
+  DCHECK_LT(weight, std::numeric_limits<BucketArray::value_type>::max() -
+                        buckets_[bucket]);
+  DCHECK_LT(weight, std::numeric_limits<decltype(total_samples_)>::max() -
+                        total_samples_);
+
+  buckets_[bucket] += weight;
+  total_samples_ += weight;
+}
+
+PercentileResults RatioHistogram::CalculatePercentiles() const {
+  RatioBoundaryIterator i;
+  return PercentilesHelper(&i, buckets_.data(),
+                           buckets_.data() + buckets_.size(), total_samples_);
+}
+
+void RatioHistogram::Reset() {
+  total_samples_ = 0;
+  buckets_.fill(0);
+}
+
+// VSyncHistogram.
+namespace {
+
+// The number of buckets in bucket categories 1 through 6.
+constexpr std::array<uint8_t, 6> kVSyncBucketCounts = {12, 16, 16, 16, 31, 6};
+
+// Some constants used to convert values to bucket categories.
+constexpr size_t kVSync1stBucketC0 = 0;
+constexpr size_t kVSync1stBucketC1 = kVSync1stBucketC0 + 1;
+constexpr size_t kVSync1stBucketC2 = kVSync1stBucketC1 + kVSyncBucketCounts[0];
+constexpr size_t kVSync1stBucketC3 = kVSync1stBucketC2 + kVSyncBucketCounts[1];
+constexpr size_t kVSync1stBucketC4 = kVSync1stBucketC3 + kVSyncBucketCounts[2];
+constexpr size_t kVSync1stBucketC5 = kVSync1stBucketC4 + kVSyncBucketCounts[3];
+constexpr size_t kVSync1stBucketC6 = kVSync1stBucketC5 + kVSyncBucketCounts[4];
+constexpr size_t kVSyncBucketCountC6 = kVSyncBucketCounts[5];
+
+// This iterates through the microsecond VSync boundaries.
+struct VSyncBoundaryIterator : public BoundaryIterator {
+  ~VSyncBoundaryIterator() override = default;
+
+  uint8_t category_ = 0;
+  uint8_t sub_bucket_ = 0;
+
+  uint64_t Next() override {
+    uint32_t boundary = 0;
+    switch (category_) {
+      case 0:  // Powers of two from 1 to 2048 us @ 50% precision
+        boundary = 1 << sub_bucket_;
+        break;
+      case 1:    // Every 8 Hz from 256 Hz to 128 Hz @ 3-6% precision
+      case 2:    // Every 4 Hz from 128 Hz to  64 Hz @ 3-6% precision
+      case 3:    // Every 2 Hz from  64 Hz to  32 Hz @ 3-6% precision
+      case 4: {  // Every 1 Hz from  32 Hz to   1 Hz @ 3-33% precision
+        int hz_start = 256 >> (category_ - 1);
+        int hz_stride = 8 >> (category_ - 1);
+        int hz = hz_start - hz_stride * sub_bucket_;
+        boundary = (base::TimeTicks::kMicrosecondsPerSecond + (hz / 2)) / hz;
+        break;
+      }
+      case 5:  // Powers of two from 1s to 32s @ 50% precision
+        boundary =
+            static_cast<uint32_t>(base::TimeTicks::kMicrosecondsPerSecond) *
+            (1 << sub_bucket_);
+        break;
+      case 6:  // The last boundary of 64s.
+        // Advancing would result in out-of-bounds access of
+        // kVSyncBucketCounts, so just return.
+        return 64 * base::TimeTicks::kMicrosecondsPerSecond;
+      default:
+        NOTREACHED();
+    }
+
+    if (++sub_bucket_ >= kVSyncBucketCounts[category_]) {
+      category_++;
+      sub_bucket_ = 0;
+    }
+
+    return boundary;
+  }
+};
+
+}  // namespace
+
+std::unique_ptr<BoundaryIterator> CreateVSyncIteratorForTesting() {
+  return std::make_unique<VSyncBoundaryIterator>();
+}
+
+VSyncHistogram::VSyncHistogram() = default;
+VSyncHistogram::~VSyncHistogram() = default;
+
+// Optimized to minimize the number of memory accesses.
+void VSyncHistogram::AddSample(uint32_t microseconds, uint32_t weight) {
+  size_t bucket = 0;
+
+  static constexpr int k256HzPeriodInMicroseconds =
+      base::TimeTicks::kMicrosecondsPerSecond / 256;
+
+  if (microseconds == 0) {
+    // bucket = 0;
+  } else if (microseconds < k256HzPeriodInMicroseconds) {
+    // Powers of two from 1 to 2048 us @ 50% precision
+    bucket = kVSync1stBucketC1 + base::bits::Log2Floor(microseconds);
+  } else if (microseconds < base::TimeTicks::kMicrosecondsPerSecond) {
+    // [256Hz, 1Hz)
+    int hz = base::TimeTicks::kMicrosecondsPerSecond / (microseconds + 0.5);
+    DCHECK_LT(hz, 256);
+    switch (hz / 32) {
+      // Every 1 Hz from 32 Hz to 1 Hz @ 3-33% precision
+      case 0:
+        bucket = kVSync1stBucketC6 - hz;
+        break;
+      // Every 2 Hz from 64 Hz to 32 Hz @ 3-6% precision
+      case 1:
+        bucket = kVSync1stBucketC5 - ((hz - 30) / 2);
+        break;
+      // Every 4 Hz from 128 Hz to 64 Hz @ 3-6% precision
+      case 2:
+      case 3:
+        bucket = kVSync1stBucketC4 - ((hz - 60) / 4);
+        break;
+      // Every 8 Hz from 256 Hz to 128 Hz @ 3-6% precision
+      case 4:
+      case 5:
+      case 6:
+      case 7:
+        bucket = kVSync1stBucketC3 - ((hz - 120) / 8);
+        break;
+      default:
+        NOTREACHED();
+        return;
+    }
+  } else {
+    // Powers of two from 1s to 32s @ 50% precision
+    int seconds_log2 = base::bits::Log2Floor(
+        microseconds / base::TimeTicks::kMicrosecondsPerSecond);
+    DCHECK_GE(seconds_log2, 0);
+    size_t offset = std::min<size_t>(kVSyncBucketCountC6 - 1, seconds_log2);
+    bucket = kVSync1stBucketC6 + offset;
+  }
+
+  DCHECK_GE(bucket, 0u);
+  DCHECK_LT(bucket, kVSync1stBucketC6 + kVSyncBucketCountC6);
+  DCHECK_LT(bucket, kBucketCount);
+
+  // Verify overflow isn't an issue.
+  DCHECK_LT(weight, std::numeric_limits<BucketArray::value_type>::max() -
+                        buckets_[bucket]);
+  DCHECK_LT(weight, std::numeric_limits<decltype(total_samples_)>::max() -
+                        total_samples_);
+
+  buckets_[bucket] += weight;
+  total_samples_ += weight;
+}
+
+PercentileResults VSyncHistogram::CalculatePercentiles() const {
+  VSyncBoundaryIterator i;
+  return PercentilesHelper(&i, buckets_.data(),
+                           buckets_.data() + buckets_.size(), total_samples_);
+}
+
+void VSyncHistogram::Reset() {
+  total_samples_ = 0;
+  buckets_.fill(0);
+}
+
+}  // namespace frame_metrics
+}  // namespace ui
diff --git a/ui/latency/histograms.h b/ui/latency/histograms.h
new file mode 100644
index 0000000..78cd884
--- /dev/null
+++ b/ui/latency/histograms.h
@@ -0,0 +1,103 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_FRAME_METRICS_HISTOGRAMS_H_
+#define UI_FRAME_METRICS_HISTOGRAMS_H_
+
+#include <array>
+#include <memory>
+
+#include "base/macros.h"
+
+namespace ui {
+
+// Used to communicate percentile results to clients.
+struct PercentileResults {
+  static constexpr double kPercentiles[] = {.50, .99};
+  static constexpr size_t kCount = arraysize(kPercentiles);
+
+  double values[kCount]{};
+};
+
+namespace frame_metrics {
+
+// This is an interface different metrics will use to inject their ideal
+// histogram implementations into the StreamAnalyzer.
+class Histogram {
+ public:
+  Histogram() = default;
+  virtual ~Histogram() = default;
+
+  // Increases the bucket that contains |value| by |weight|.
+  virtual void AddSample(uint32_t value, uint32_t weight) = 0;
+
+  // Calculates and returns the approximate percentiles based on the
+  // histogram distribution.
+  virtual PercentileResults CalculatePercentiles() const = 0;
+
+  // Resets all buckets in the histogram to 0.
+  // Higher level logic may periodically reset the the counts after it
+  // gathers the percentiles in order to avoid overflow.
+  virtual void Reset() = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Histogram);
+};
+
+// Ratio histogram, with a range of [0, 2^32) and most of it's precision
+// just above kFixedPointMultiplier (i.e. a fixed point of 1).
+class RatioHistogram : public Histogram {
+ public:
+  RatioHistogram();
+  ~RatioHistogram() override;
+  void AddSample(uint32_t ratio, uint32_t weight) override;
+  PercentileResults CalculatePercentiles() const override;
+  void Reset() override;
+
+ private:
+  static constexpr size_t kBucketCount = 111;
+
+  uint64_t total_samples_ = 0;
+  using BucketArray = std::array<uint32_t, kBucketCount>;
+  BucketArray buckets_{};
+
+  DISALLOW_COPY_AND_ASSIGN(RatioHistogram);
+};
+
+// A histogram of 98 buckets from 0 to 64 seconds with extra precision
+// around common vsync boundaries.
+class VSyncHistogram : public Histogram {
+ public:
+  VSyncHistogram();
+  ~VSyncHistogram() override;
+  void AddSample(uint32_t microseconds, uint32_t weight) override;
+  PercentileResults CalculatePercentiles() const override;
+  void Reset() override;
+
+ private:
+  static constexpr size_t kBucketCount = 98;
+
+  uint64_t total_samples_ = 0;
+  using BucketArray = std::array<uint32_t, kBucketCount>;
+  BucketArray buckets_{};
+
+  DISALLOW_COPY_AND_ASSIGN(VSyncHistogram);
+};
+
+// An interface that allows PercentileHelper to iterate through the
+// bucket boundaries of the delegating histogram.
+// This is an implemenation detail, but is exposed here for testing purposes.
+struct BoundaryIterator {
+  virtual ~BoundaryIterator() = default;
+  virtual uint64_t Next() = 0;
+};
+
+// These expose the internal iterators, so they can be verified in tests.
+std::unique_ptr<BoundaryIterator> CreateRatioIteratorForTesting();
+std::unique_ptr<BoundaryIterator> CreateVSyncIteratorForTesting();
+
+}  // namespace frame_metrics
+}  // namespace ui
+
+#endif  // UI_FRAME_METRICS_HISTOGRAMS_H_
diff --git a/ui/latency/histograms_perftest.cc b/ui/latency/histograms_perftest.cc
new file mode 100644
index 0000000..a44e0331
--- /dev/null
+++ b/ui/latency/histograms_perftest.cc
@@ -0,0 +1,257 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/latency/histograms.h"
+
+#include <algorithm>
+
+#include "base/metrics/bucket_ranges.h"
+#include "base/metrics/sample_vector.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+#include "ui/latency/fixed_point.h"
+#include "ui/latency/histograms_test_common.h"
+
+namespace ui {
+namespace frame_metrics {
+
+constexpr base::TimeDelta kTimeLimit = base::TimeDelta::FromSeconds(2);
+
+// A version of RatioHistogram based on the default implementations
+// of base::BucketRanges and base::SampleVector.
+class RatioHistogramBaseline : public Histogram {
+ public:
+  RatioHistogramBaseline()
+      : ratio_boundaries_(),
+        bucket_ranges_(ratio_boundaries_.size()),
+        sample_vector_(&bucket_ranges_) {
+    size_t i = 0;
+    for (const auto& b : ratio_boundaries_.boundaries) {
+      bucket_ranges_.set_range(i++, std::min<uint64_t>(b, INT_MAX));
+    }
+  }
+
+  ~RatioHistogramBaseline() override = default;
+
+  void AddSample(uint32_t microseconds, uint32_t weight) override {
+    sample_vector_.Accumulate(microseconds, weight);
+  }
+
+  PercentileResults CalculatePercentiles() const override {
+    return PercentileResults();
+  }
+  void Reset() override {}
+
+ private:
+  TestRatioBoundaries ratio_boundaries_;
+  base::BucketRanges bucket_ranges_;
+  base::SampleVector sample_vector_;
+
+  DISALLOW_COPY_AND_ASSIGN(RatioHistogramBaseline);
+};
+
+TEST(FrameMetricsHistogramsPerfTest, RatioEntireRange) {
+  const int kStride = 0x1000;
+
+  RatioHistogramBaseline vh_base;
+  RatioHistogram vh_impl;
+
+  base::TimeDelta impl_time;
+  base::TimeDelta base_time;
+
+  base::TimeTicks finish_time = base::TimeTicks::Now() + kTimeLimit;
+  while (base::TimeTicks::Now() < finish_time) {
+    // Impl then Base
+    for (int i = 0; i < INT_MAX - kStride; i += kStride) {
+      int value = (i * 37) & 0x3FFFFFFF;
+      base::TimeTicks t0 = base::TimeTicks::Now();
+      vh_impl.AddSample(value, 1);
+      base::TimeTicks t1 = base::TimeTicks::Now();
+      vh_base.AddSample(value, 1);
+      base::TimeTicks t2 = base::TimeTicks::Now();
+      base::TimeTicks t3 = base::TimeTicks::Now();
+      impl_time += t1 - t0 - (t3 - t2);
+      base_time += t2 - t1 - (t3 - t2);
+    }
+
+    // Base then Impl
+    for (int i = 0; i < INT_MAX - kStride; i += kStride) {
+      int value = (i * 37) & 0x3FFFFFFF;
+      base::TimeTicks t0 = base::TimeTicks::Now();
+      vh_base.AddSample(value, 1);
+      base::TimeTicks t1 = base::TimeTicks::Now();
+      vh_impl.AddSample(value, 1);
+      base::TimeTicks t2 = base::TimeTicks::Now();
+      base::TimeTicks t3 = base::TimeTicks::Now();
+      base_time += t1 - t0 - (t3 - t2);
+      impl_time += t2 - t1 - (t3 - t2);
+    }
+  }
+
+  double X = base_time.InSecondsF() / impl_time.InSecondsF();
+  perf_test::PrintResult(__FUNCTION__, "", __FUNCTION__, X, "x", true);
+}
+
+TEST(FrameMetricsHistogramsPerfTest, RatioCommonRange) {
+  const int kStride = 0x100;
+
+  RatioHistogramBaseline vh_base;
+  RatioHistogram vh_impl;
+
+  base::TimeDelta impl_time;
+  base::TimeDelta base_time;
+
+  base::TimeTicks finish_time = base::TimeTicks::Now() + kTimeLimit;
+  while (base::TimeTicks::Now() < finish_time) {
+    // Impl then Base
+    for (int i = 0; i < 4 * kFixedPointMultiplier; i += kStride) {
+      int value = i;
+      base::TimeTicks t0 = base::TimeTicks::Now();
+      vh_impl.AddSample(value, 1);
+      base::TimeTicks t1 = base::TimeTicks::Now();
+      vh_base.AddSample(value, 1);
+      base::TimeTicks t2 = base::TimeTicks::Now();
+      base::TimeTicks t3 = base::TimeTicks::Now();
+      impl_time += t1 - t0 - (t3 - t2);
+      base_time += t2 - t1 - (t3 - t2);
+    }
+
+    // Base then Impl
+    for (int i = 0; i < 4 * kFixedPointMultiplier; i += kStride) {
+      int value = i;
+      base::TimeTicks t0 = base::TimeTicks::Now();
+      vh_base.AddSample(value, 1);
+      base::TimeTicks t1 = base::TimeTicks::Now();
+      vh_impl.AddSample(value, 1);
+      base::TimeTicks t2 = base::TimeTicks::Now();
+      base::TimeTicks t3 = base::TimeTicks::Now();
+      base_time += t1 - t0 - (t3 - t2);
+      impl_time += t2 - t1 - (t3 - t2);
+    }
+  }
+
+  double X = base_time.InSecondsF() / impl_time.InSecondsF();
+  perf_test::PrintResult(__FUNCTION__, "", __FUNCTION__, X, "x", true);
+}
+
+// A version of VSyncHistogram based on the default implementations
+// of base::BucketRanges and base::SampleVector.
+class VSyncHistogramBaseline : public Histogram {
+ public:
+  VSyncHistogramBaseline()
+      : bucket_ranges_(kTestVSyncBoundries.size() + 1),
+        sample_vector_(&bucket_ranges_) {
+    size_t i = 0;
+    for (const auto& b : kTestVSyncBoundries) {
+      bucket_ranges_.set_range(i++, b);
+    }
+    // BucketRanges needs the last elemet set to INT_MAX.
+    bucket_ranges_.set_range(i++, INT_MAX);
+  }
+
+  ~VSyncHistogramBaseline() override = default;
+
+  void AddSample(uint32_t microseconds, uint32_t weight) override {
+    sample_vector_.Accumulate(microseconds, weight);
+  }
+
+  PercentileResults CalculatePercentiles() const override {
+    return PercentileResults();
+  }
+  void Reset() override {}
+
+ private:
+  base::BucketRanges bucket_ranges_;
+  base::SampleVector sample_vector_;
+
+  DISALLOW_COPY_AND_ASSIGN(VSyncHistogramBaseline);
+};
+
+TEST(FrameMetricsHistogramsPerfTest, VSyncEntireRange) {
+  const int kStride = 0x1000;
+
+  VSyncHistogramBaseline vh_base;
+  VSyncHistogram vh_impl;
+
+  base::TimeDelta impl_time;
+  base::TimeDelta base_time;
+
+  base::TimeTicks finish_time = base::TimeTicks::Now() + kTimeLimit;
+  while (base::TimeTicks::Now() < finish_time) {
+    // Impl then Base
+    for (int i = 0; i < INT_MAX - kStride; i += kStride) {
+      int value = (i * 37) % 64000000;
+      base::TimeTicks t0 = base::TimeTicks::Now();
+      vh_impl.AddSample(value, 1);
+      base::TimeTicks t1 = base::TimeTicks::Now();
+      vh_base.AddSample(value, 1);
+      base::TimeTicks t2 = base::TimeTicks::Now();
+      base::TimeTicks t3 = base::TimeTicks::Now();
+      impl_time += t1 - t0 - (t3 - t2);
+      base_time += t2 - t1 - (t3 - t2);
+    }
+
+    // Base then Impl
+    for (int i = 0; i < INT_MAX - kStride; i += kStride) {
+      int value = (i * 37) % 64000000;
+      base::TimeTicks t0 = base::TimeTicks::Now();
+      vh_base.AddSample(value, 1);
+      base::TimeTicks t1 = base::TimeTicks::Now();
+      vh_impl.AddSample(value, 1);
+      base::TimeTicks t2 = base::TimeTicks::Now();
+      base::TimeTicks t3 = base::TimeTicks::Now();
+      base_time += t1 - t0 - (t3 - t2);
+      impl_time += t2 - t1 - (t3 - t2);
+    }
+  }
+
+  double X = base_time.InSecondsF() / impl_time.InSecondsF();
+  perf_test::PrintResult(__FUNCTION__, "", __FUNCTION__, X, "x", true);
+}
+
+TEST(FrameMetricsHistogramsPerfTest, VSyncCommonRange) {
+  const int kStride = 0x100;
+
+  VSyncHistogramBaseline vh_base;
+  VSyncHistogram vh_impl;
+
+  base::TimeDelta impl_time;
+  base::TimeDelta base_time;
+
+  base::TimeTicks finish_time = base::TimeTicks::Now() + kTimeLimit;
+  while (base::TimeTicks::Now() < finish_time) {
+    // Impl then Base
+    for (int i = 0; i < 100000; i += kStride) {
+      int value = i;
+      base::TimeTicks t0 = base::TimeTicks::Now();
+      vh_impl.AddSample(value, 1);
+      base::TimeTicks t1 = base::TimeTicks::Now();
+      vh_base.AddSample(value, 1);
+      base::TimeTicks t2 = base::TimeTicks::Now();
+      base::TimeTicks t3 = base::TimeTicks::Now();
+      impl_time += t1 - t0 - (t3 - t2);
+      base_time += t2 - t1 - (t3 - t2);
+    }
+
+    // Base then Impl
+    for (int i = 0; i < 100000; i += kStride) {
+      int value = i;
+      base::TimeTicks t0 = base::TimeTicks::Now();
+      vh_base.AddSample(value, 1);
+      base::TimeTicks t1 = base::TimeTicks::Now();
+      vh_impl.AddSample(value, 1);
+      base::TimeTicks t2 = base::TimeTicks::Now();
+      base::TimeTicks t3 = base::TimeTicks::Now();
+      base_time += t1 - t0 - (t3 - t2);
+      impl_time += t2 - t1 - (t3 - t2);
+    }
+  }
+
+  double X = base_time.InSecondsF() / impl_time.InSecondsF();
+  perf_test::PrintResult(__FUNCTION__, "", __FUNCTION__, X, "x", true);
+}
+
+}  // namespace frame_metrics
+}  // namespace ui
diff --git a/ui/latency/histograms_test_common.cc b/ui/latency/histograms_test_common.cc
new file mode 100644
index 0000000..5fd2d5df
--- /dev/null
+++ b/ui/latency/histograms_test_common.cc
@@ -0,0 +1,56 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/latency/histograms_test_common.h"
+
+#include "base/logging.h"
+
+namespace ui {
+namespace frame_metrics {
+
+TestRatioBoundaries::TestRatioBoundaries() {
+  const uint32_t one = kFixedPointMultiplier;
+  const uint32_t half = one / 2;
+  // [0, 2^-16) => 1 bucket.
+  int i = 0;
+  boundaries[i++] = 0;
+  // [2^-16,1) pow of 2 strides => 16 buckets. (16x1)
+  for (int j = 0; j < 16; j++)
+    boundaries[i++] = 1ULL << j;
+  // [1,16) stride 1/2 => 30 buckets. (2 + 4 + 8 + 16)
+  for (int j = 0; j < 30; j++)
+    boundaries[i++] = one + (j * half);
+  // [16,32) stride 1 => 16 buckets.
+  for (int j = 0; j < 16; j++)
+    boundaries[i++] = (16 + j) * one;
+  // [32,64) stride 2 => 16 buckets.
+  for (int j = 0; j < 16; j++)
+    boundaries[i++] = (32 + 2 * j) * one;
+  // [64,128) stride 8 => 8 buckets.
+  for (int j = 0; j < 8; j++)
+    boundaries[i++] = (64 + 8 * j) * one;
+  // [128, 256) stride 16 => 8 buckets.
+  for (int j = 0; j < 8; j++)
+    boundaries[i++] = (128 + 16 * j) * one;
+  // [256, 512) stride 64 => 4 buckets.
+  for (int j = 0; j < 4; j++)
+    boundaries[i++] = (256 + 64 * j) * one;
+  // [512, 1024) stride 128 => 4 buckets.
+  for (int j = 0; j < 4; j++)
+    boundaries[i++] = (512 + 128 * j) * one;
+  // [1024, 2048) stride 512 => 2 buckets.
+  for (int j = 0; j < 2; j++)
+    boundaries[i++] = (1024 + 512 * j) * one;
+  // [2048, 4096) stride 1024 => 2 buckets.
+  for (int j = 0; j < 2; j++)
+    boundaries[i++] = (2048 + 1024 * j) * one;
+  // [4096, 2^16) pow of 2 strides => 4 buckets. (4x1)
+  for (int j = 0; j < 4; j++)
+    boundaries[i++] = (4096ULL << j) * one;
+  boundaries[i++] = 1ULL << 32;
+  DCHECK_EQ(112, i);
+}
+
+}  // namespace frame_metrics
+}  // namespace ui
diff --git a/ui/latency/histograms_test_common.h b/ui/latency/histograms_test_common.h
new file mode 100644
index 0000000..fd5257b8
--- /dev/null
+++ b/ui/latency/histograms_test_common.h
@@ -0,0 +1,52 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/latency/fixed_point.h"
+
+#include <array>
+
+namespace ui {
+namespace frame_metrics {
+
+// This class initializes the ratio boundaries on construction in a way that
+// is easier to follow than the procedural code in the RatioHistogram
+// implementation.
+class TestRatioBoundaries {
+ public:
+  TestRatioBoundaries();
+  uint64_t operator[](size_t i) const { return boundaries[i]; }
+  size_t size() const { return boundaries.size(); }
+
+ public:
+  // uint64_t since the last boundary needs 33 bits.
+  std::array<uint64_t, 112> boundaries;
+};
+
+// An explicit list of VSync boundaries to verify the procedurally generated
+// ones in the implementation.
+static constexpr std::array<uint32_t, 99> kTestVSyncBoundries = {
+    // C0: [0,1) (1 bucket).
+    0,
+    // C1: Powers of two from 1 to 2048 us @ 50% precision (12 buckets)
+    1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048,
+    // C2: Every 8 Hz from 256 Hz to 128 Hz @ 3-6% precision (16 buckets)
+    3906, 4032, 4167, 4310, 4464, 4630, 4808, 5000, 5208, 5435, 5682, 5952,
+    6250, 6579, 6944, 7353,
+    // C3: Every 4 Hz from 128 Hz to 64 Hz @ 3-6% precision (16 buckets)
+    7813, 8065, 8333, 8621, 8929, 9259, 9615, 10000, 10417, 10870, 11364, 11905,
+    12500, 13158, 13889, 14706,
+    // C4: Every 2 Hz from 64 Hz to 32 Hz @ 3-6% precision (16 buckets)
+    15625, 16129, 16667, 17241, 17857, 18519, 19231, 20000, 20833, 21739, 22727,
+    23810, 25000, 26316, 27778, 29412,
+    // C5: Every 1 Hz from 32 Hz to 1 Hz @ 3-33% precision (31 buckets)
+    31250, 32258, 33333, 34483, 35714, 37037, 38462, 40000, 41667, 43478, 45455,
+    47619, 50000, 52632, 55556, 58824, 62500, 66667, 71429, 76923, 83333, 90909,
+    100000, 111111, 125000, 142857, 166667, 200000, 250000, 333333, 500000,
+    // C6: Powers of two from 1s to 32s @ 50% precision (6 buckets)
+    1000000, 2000000, 4000000, 8000000, 16000000, 32000000,
+    // C7: Extra value to simplify estimate in Percentiles().
+    64000000};
+
+}  // namespace frame_metrics
+}  // namespace ui
diff --git a/ui/latency/histograms_unittest.cc b/ui/latency/histograms_unittest.cc
new file mode 100644
index 0000000..4687c18
--- /dev/null
+++ b/ui/latency/histograms_unittest.cc
@@ -0,0 +1,151 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/latency/histograms.h"
+
+#include <algorithm>
+
+#include "base/metrics/bucket_ranges.h"
+#include "base/metrics/sample_vector.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/latency/fixed_point.h"
+#include "ui/latency/histograms_test_common.h"
+
+namespace ui {
+namespace frame_metrics {
+
+// Verifies the ratio boundaries generated internally match the reference
+// boundaries.
+TEST(FrameMetricsHistogramsTest, RatioBoundariesDirect) {
+  const TestRatioBoundaries kTestRatioBoundaries;
+  std::unique_ptr<BoundaryIterator> ratio_impl =
+      CreateRatioIteratorForTesting();
+  for (uint32_t boundary : kTestRatioBoundaries.boundaries) {
+    if (boundary == 0)
+      continue;
+    EXPECT_EQ(boundary, ratio_impl->Next());
+  }
+}
+
+// Verifies the VSync boundaries generated internally match the reference
+// boundaries.
+TEST(FrameMetricsHistogramsTest, VSyncBoundariesDirect) {
+  std::unique_ptr<BoundaryIterator> vsync_impl =
+      CreateVSyncIteratorForTesting();
+  for (uint32_t boundary : kTestVSyncBoundries) {
+    if (boundary == 0)
+      continue;
+    EXPECT_EQ(boundary, vsync_impl->Next());
+  }
+}
+
+template <typename ReferenceBoundaryT>
+void BoundaryTestCommon(const ReferenceBoundaryT& reference_boundaries,
+                        std::unique_ptr<Histogram> histogram) {
+  PercentileResults percentiles;
+
+  for (size_t i = 0; i < reference_boundaries.size() - 1; i++) {
+    uint64_t bucket_start = reference_boundaries[i];
+    uint64_t bucket_end = reference_boundaries[i + 1];
+
+    // Verify values within the current bucket don't affect percentile.
+    // This also checks the first value in the bucket.
+    uint32_t stride = std::max<uint32_t>(1u, (bucket_end - bucket_start) / 8);
+    for (uint64_t value = bucket_start; value < bucket_end; value += stride) {
+      histogram->AddSample(value, 1);
+      percentiles = histogram->CalculatePercentiles();
+      histogram->Reset();
+      EXPECT_LE(bucket_start, percentiles.values[0]);
+      EXPECT_GT(bucket_end, percentiles.values[0]);
+    }
+
+    // Verify the value just before the next bucket doesn't affect percentile.
+    histogram->AddSample(bucket_end - 1, 1);
+    percentiles = histogram->CalculatePercentiles();
+    histogram->Reset();
+    EXPECT_LE(bucket_start, percentiles.values[0]);
+    EXPECT_GT(bucket_end, percentiles.values[0]);
+  }
+}
+
+TEST(FrameMetricsHistogramsTest, RatioBoundaries) {
+  const TestRatioBoundaries kTestRatioBoundaries;
+  BoundaryTestCommon(kTestRatioBoundaries, std::make_unique<RatioHistogram>());
+}
+
+TEST(FrameMetricsHistogramsTest, VSyncBoundaries) {
+  const TestRatioBoundaries kTestRatioBoundaries;
+  BoundaryTestCommon(kTestVSyncBoundries, std::make_unique<VSyncHistogram>());
+}
+
+template <typename ReferenceBoundaryT>
+void PercentilesTestCommon(const ReferenceBoundaryT& reference_boundaries,
+                           std::unique_ptr<Histogram> histogram,
+                           int percentile_index) {
+  double percentile = PercentileResults::kPercentiles[percentile_index];
+  PercentileResults percentiles;
+  for (size_t i = 0; i < reference_boundaries.size() - 1; i++) {
+    uint64_t bucket_start = reference_boundaries[i];
+    uint64_t bucket_end = reference_boundaries[i + 1];
+
+    // Add samples to current bucket.
+    // Where the samples are added in the current bucket should not affect the
+    // result.
+    uint32_t stride = std::max<uint32_t>(1u, (bucket_end - bucket_start) / 100);
+    int samples_added_inside = 0;
+    for (uint64_t value = bucket_start; value < bucket_end; value += stride) {
+      histogram->AddSample(value, 10);
+      samples_added_inside += 10;
+    }
+
+    // Add samples to left and right of current bucket.
+    // Don't worry about doing this for the left most and right most buckets.
+    int samples_added_left = 0;
+    int samples_added_outside = 0;
+    if (i != 0 && i < reference_boundaries.size() - 2) {
+      samples_added_outside = 10000;
+      samples_added_left = samples_added_outside * percentile;
+      histogram->AddSample(bucket_start / 3, samples_added_left);
+      histogram->AddSample(bucket_start * 3,
+                           samples_added_outside - samples_added_left);
+    }
+
+    percentiles = histogram->CalculatePercentiles();
+    histogram->Reset();
+
+    double index = (samples_added_inside + samples_added_outside) * percentile -
+                   samples_added_left;
+    double w = index / samples_added_inside;
+    double expected_value = bucket_end * w + bucket_start * (1.0 - w);
+    EXPECT_DOUBLE_EQ(expected_value, percentiles.values[percentile_index]);
+  }
+}
+
+TEST(FrameMetricsHistogramsTest, RatioPercentiles50th) {
+  const TestRatioBoundaries kTestRatioBoundaries;
+  PercentilesTestCommon(kTestRatioBoundaries,
+                        std::make_unique<RatioHistogram>(), 0);
+}
+
+TEST(FrameMetricsHistogramsTest, RatioPercentiles99th) {
+  const TestRatioBoundaries kTestRatioBoundaries;
+  PercentilesTestCommon(kTestRatioBoundaries,
+                        std::make_unique<RatioHistogram>(), 1);
+}
+
+TEST(FrameMetricsHistogramsTest, VSyncPercentiles50th) {
+  const TestRatioBoundaries kTestRatioBoundaries;
+  PercentilesTestCommon(kTestVSyncBoundries, std::make_unique<VSyncHistogram>(),
+                        0);
+}
+
+TEST(FrameMetricsHistogramsTest, VSyncPercentiles99th) {
+  const TestRatioBoundaries kTestRatioBoundaries;
+  PercentilesTestCommon(kTestVSyncBoundries, std::make_unique<VSyncHistogram>(),
+                        1);
+}
+
+}  // namespace frame_metrics
+}  // namespace ui
diff --git a/url/gurl.cc b/url/gurl.cc
index cec69c7..7d7aecf 100644
--- a/url/gurl.cc
+++ b/url/gurl.cc
@@ -428,7 +428,12 @@
 }
 
 std::string GURL::GetContent() const {
-  return is_valid_ ? ComponentString(parsed_.GetContent()) : std::string();
+  if (!is_valid_)
+    return std::string();
+  std::string content = ComponentString(parsed_.GetContent());
+  if (!SchemeIs(url::kJavaScriptScheme) && parsed_.ref.len >= 0)
+    content.erase(content.size() - parsed_.ref.len - 1);
+  return content;
 }
 
 bool GURL::HostIsIPAddress() const {
diff --git a/url/gurl.h b/url/gurl.h
index 96ea864..c8d23252 100644
--- a/url/gurl.h
+++ b/url/gurl.h
@@ -25,11 +25,11 @@
 // A parsed canonicalized URL is guaranteed to be UTF-8. Any non-ASCII input
 // characters are UTF-8 encoded and % escaped to ASCII.
 //
-// The string representation of a URL is called the spec(). Getting the
-// spec will assert if the URL is invalid to help protect against malicious
-// URLs. If you want the "best effort" canonicalization of an invalid URL, you
-// can use possibly_invalid_spec(). Test validity with is_valid(). Data and
-// javascript URLs use GetContent() to extract the data.
+// The string representation of a URL is called the spec(). Getting the spec
+// will assert if the URL is invalid to help protect against malicious URLs. If
+// you want the "best effort" canonicalization of an invalid URL, you can use
+// possibly_invalid_spec(). Test validity with is_valid(). Data and javascript
+// URLs use GetContent() to extract the data.
 //
 // This class has existence checkers and getters for the various components of
 // a URL. Existence is different than being nonempty. "http://www.google.com/?"
@@ -258,9 +258,13 @@
     return SchemeIs(url::kBlobScheme);
   }
 
-  // The "content" of the URL is everything after the scheme (skipping the
-  // scheme delimiting colon). It is an error to get the content of an invalid
-  // URL: the result will be an empty string.
+  // For most URLs, the "content" is everything after the scheme (skipping the
+  // scheme delimiting colon) and before the fragment (skipping the fragment
+  // delimiting octothorpe). For javascript URLs the "content" also includes the
+  // fragment delimiter and fragment.
+  //
+  // It is an error to get the content of an invalid URL: the result will be an
+  // empty string.
   std::string GetContent() const;
 
   // Returns true if the hostname is an IP address. Note: this function isn't
diff --git a/url/gurl_unittest.cc b/url/gurl_unittest.cc
index 95e71f23..e686422 100644
--- a/url/gurl_unittest.cc
+++ b/url/gurl_unittest.cc
@@ -780,6 +780,44 @@
       {"blob:http://user:password@example.com/GUID",
        "http://user:password@example.com/GUID"},
 
+      {"http://www.example.com/GUID#ref", "www.example.com/GUID"},
+      {"http://me:secret@example.com/GUID/#ref", "me:secret@example.com/GUID/"},
+      {"data:text/html,Question?<div style=\"color: #bad\">idea</div>",
+       "text/html,Question?<div style=\"color: "},
+
+      // TODO(mkwst): This seems like a bug. https://crbug.com/513600
+      {"filesystem:http://example.com/path", "/"},
+
+      // Javascript URLs include '#' symbols in their content.
+      {"javascript:#", "#"},
+      {"javascript:alert('#');", "alert('#');"},
+  };
+
+  for (const auto& test : cases) {
+    GURL url(test.url);
+    EXPECT_EQ(test.expected, url.GetContent()) << test.url;
+  }
+}
+
+TEST(GURLTest, PathForNonStandardURLs) {
+  struct TestCase {
+    const char* url;
+    const char* expected;
+  } cases[] = {
+      {"null", ""},
+      {"not-a-standard-scheme:this is arbitrary content",
+       "this is arbitrary content"},
+      {"view-source:http://example.com/path", "http://example.com/path"},
+      {"blob:http://example.com/GUID", "http://example.com/GUID"},
+      {"blob://http://example.com/GUID", "//http://example.com/GUID"},
+      {"blob:http://user:password@example.com/GUID",
+       "http://user:password@example.com/GUID"},
+
+      {"http://www.example.com/GUID#ref", "/GUID"},
+      {"http://me:secret@example.com/GUID/#ref", "/GUID/"},
+      {"data:text/html,Question?<div style=\"color: #bad\">idea</div>",
+       "text/html,Question"},
+
       // TODO(mkwst): This seems like a bug. https://crbug.com/513600
       {"filesystem:http://example.com/path", "/"},
   };
@@ -787,7 +825,6 @@
   for (const auto& test : cases) {
     GURL url(test.url);
     EXPECT_EQ(test.expected, url.path()) << test.url;
-    EXPECT_EQ(test.expected, url.GetContent()) << test.url;
   }
 }