diff --git a/DEPS b/DEPS
index 9cc54a0..2d67b774 100644
--- a/DEPS
+++ b/DEPS
@@ -105,11 +105,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'e8161dd154d7b5adc3bce71a615263dc1635dfc8',
+  'skia_revision': 'b090b2b26803179f5a271efb142c747a1bba6225',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'e1220339a6c864b71394ad9f98537df0e55f3796',
+  'v8_revision': '104cb05682bfbac1a4cf15462a4da7b6a590b499',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -117,7 +117,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': '4d42ef39658b9808f4e18c077b10202a7b50b33b',
+  'angle_revision': '801719235b132b3bd53af35fba36dfe9238cf14f',
   # 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.
@@ -125,11 +125,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'fe5861bf5bb1f761b5d765abfb0c371e75202aa2',
+  'swiftshader_revision': '108f3e10f093d8cb27b12adc9cc697bee300c6f9',
   # 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': 'aaff8f8b902ac3058b44b0f11a236df606082383',
+  'pdfium_revision': '1881cb7591c7154c65efaa62a8ead989113a6c23',
   # 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.
@@ -604,7 +604,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'b56a43a9064c35e81be989f5e86a19d9159d5edf',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '515e7fe037902d6434c5d5ff2c077b747c56b0b7',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1098,7 +1098,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '7ca87fb1d3da3b3d2060886e8c58e726d74c8219',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'bb081a62dc30a49fa77cfdbb4ae470c5137fa547',
+    Var('webrtc_git') + '/src.git' + '@' + '7cd2a9514837cfb63f636fa2d213e7941fc76f2e',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/PopupWindowTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/PopupWindowTest.java
index 224a89c..3fd8ec8e 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/PopupWindowTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/PopupWindowTest.java
@@ -257,8 +257,8 @@
 
         // Now long press on some texts and see if the text handles show up.
         DOMUtils.longPressNode(popupContents.getWebContents(), "plain_text");
-        SelectionPopupController controller =
-                SelectionPopupController.fromWebContents(popupContents.getWebContents());
+        SelectionPopupController controller = ThreadUtils.runOnUiThreadBlocking(
+                () -> SelectionPopupController.fromWebContents(popupContents.getWebContents()));
         assertWaitForSelectActionBarStatus(true, controller);
         Assert.assertTrue(ThreadUtils.runOnUiThreadBlocking(() -> controller.hasSelection()));
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/WebViewModalDialogOverrideTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/WebViewModalDialogOverrideTest.java
index 5781fd0..157da35 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/WebViewModalDialogOverrideTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/WebViewModalDialogOverrideTest.java
@@ -20,6 +20,7 @@
 import org.chromium.android_webview.JsPromptResultReceiver;
 import org.chromium.android_webview.JsResultReceiver;
 import org.chromium.android_webview.test.util.AwTestTouchUtils;
+import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Feature;
 import org.chromium.content_public.browser.GestureListenerManager;
@@ -194,8 +195,10 @@
     private void tapViewAndWait(AwTestContainerView view) throws Throwable {
         final TapGestureStateListener tapGestureStateListener = new TapGestureStateListener();
         int callCount = tapGestureStateListener.getCallCount();
-        GestureListenerManager.fromWebContents(view.getWebContents())
-                .addListener(tapGestureStateListener);
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            GestureListenerManager.fromWebContents(view.getWebContents())
+                    .addListener(tapGestureStateListener);
+        });
 
         AwTestTouchUtils.simulateTouchCenterOfView(view);
         tapGestureStateListener.waitForTap(callCount);
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index 1d3f42d..6ec9014 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -272,6 +272,9 @@
     SetState(SHELF_VISIBLE);
   } else if (Shell::Get()->screen_pinning_controller()->IsPinned()) {
     SetState(SHELF_HIDDEN);
+  } else if (Shell::Get()->window_selector_controller() &&
+             Shell::Get()->window_selector_controller()->IsSelecting()) {
+    SetState(SHELF_VISIBLE);
   } else {
     // TODO(zelidrag): Verify shelf drag animation still shows on the device
     // when we are in SHELF_AUTO_HIDE_ALWAYS_HIDDEN.
@@ -497,6 +500,16 @@
   MaybeUpdateShelfBackground(AnimationChangeType::IMMEDIATE);
 }
 
+void ShelfLayoutManager::OnOverviewModeStarting() {
+  UpdateVisibilityState();
+  MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
+}
+
+void ShelfLayoutManager::OnOverviewModeEnded() {
+  UpdateVisibilityState();
+  MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
+}
+
 void ShelfLayoutManager::OnSplitViewModeStarted() {
   MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
 }
@@ -559,6 +572,11 @@
     return SHELF_BACKGROUND_OVERLAP;
   }
 
+  if (Shell::Get()->window_selector_controller() &&
+      Shell::Get()->window_selector_controller()->IsSelecting()) {
+    return SHELF_BACKGROUND_OVERLAP;
+  }
+
   // If split view mode is active, make the shelf fully opapue.
   if (Shell::Get()->IsSplitViewModeActive())
     return SHELF_BACKGROUND_SPLIT_VIEW;
diff --git a/ash/shelf/shelf_layout_manager.h b/ash/shelf/shelf_layout_manager.h
index 3373a0d..04b6bc2 100644
--- a/ash/shelf/shelf_layout_manager.h
+++ b/ash/shelf/shelf_layout_manager.h
@@ -155,6 +155,8 @@
   void OnPinnedStateChanged(aura::Window* pinned_window) override;
   void OnAppListVisibilityChanged(bool shown,
                                   aura::Window* root_window) override;
+  void OnOverviewModeStarting() override;
+  void OnOverviewModeEnded() override;
   void OnSplitViewModeStarted() override;
   void OnSplitViewModeEnded() override;
 
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc
index 08f0297..d35aea0 100644
--- a/ash/shelf/shelf_layout_manager_unittest.cc
+++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -35,6 +35,7 @@
 #include "ash/wallpaper/wallpaper_controller.h"
 #include "ash/window_factory.h"
 #include "ash/wm/lock_state_controller.h"
+#include "ash/wm/overview/window_selector_controller.h"
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/window_state.h"
@@ -1006,6 +1007,36 @@
   EXPECT_EQ(SHELF_BACKGROUND_OVERLAP, GetShelfWidget()->GetBackgroundType());
 }
 
+// Tests that the shelf should be visible when in overview mode.
+TEST_F(ShelfLayoutManagerTest, VisibleInOverview) {
+  std::unique_ptr<aura::Window> window(CreateTestWindow());
+  window->Show();
+  Shelf* shelf = GetPrimaryShelf();
+  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
+
+  // LayoutShelf() forces the animation to completion, at which point the
+  // shelf should go off the screen.
+  GetShelfLayoutManager()->LayoutShelf();
+  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
+  EXPECT_EQ(display.bounds().bottom() - kShelfAutoHideSize,
+            GetShelfWidget()->GetWindowBoundsInScreen().y());
+
+  WindowSelectorController* window_selector_controller =
+      Shell::Get()->window_selector_controller();
+  // Tests that the shelf is visible when in overview mode and its color is
+  // overlap.
+  window_selector_controller->ToggleOverview();
+  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_BACKGROUND_OVERLAP, GetShelfWidget()->GetBackgroundType());
+
+  // Test that on exiting overview mode, the shelf returns to auto hide state.
+  window_selector_controller->ToggleOverview();
+  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
+  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
+}
+
 // Assertions around SetAutoHideBehavior.
 TEST_F(ShelfLayoutManagerTest, SetAutoHideBehavior) {
   Shelf* shelf = GetPrimaryShelf();
diff --git a/base/task/task_scheduler/environment_config.cc b/base/task/task_scheduler/environment_config.cc
index bca9c38..0bb59ab 100644
--- a/base/task/task_scheduler/environment_config.cc
+++ b/base/task/task_scheduler/environment_config.cc
@@ -28,13 +28,13 @@
     return false;
 
 #if !defined(OS_ANDROID)
-  // When thread priority can't be increased, run all threads with a normal
-  // priority to avoid priority inversions on shutdown (TaskScheduler increases
-  // background threads priority to normal on shutdown while resolving remaining
-  // shutdown blocking tasks).
+  // When thread priority can't be increased to NORMAL, run all threads with a
+  // NORMAL priority to avoid priority inversions on shutdown (TaskScheduler
+  // increases BACKGROUND threads priority to NORMAL on shutdown while resolving
+  // remaining shutdown blocking tasks).
   //
   // This is ignored on Android, because it doesn't have a clean shutdown phase.
-  if (!PlatformThread::CanIncreaseCurrentThreadPriority())
+  if (!PlatformThread::CanIncreaseThreadPriority(ThreadPriority::NORMAL))
     return false;
 #endif  // defined(OS_ANDROID)
 
diff --git a/base/threading/platform_thread.h b/base/threading/platform_thread.h
index faeb858..029cca5 100644
--- a/base/threading/platform_thread.h
+++ b/base/threading/platform_thread.h
@@ -196,9 +196,9 @@
   // and |thread_handle| is invalidated after this call.
   static void Detach(PlatformThreadHandle thread_handle);
 
-  // Returns true if SetCurrentThreadPriority() can be used to increase the
-  // priority of the current thread.
-  static bool CanIncreaseCurrentThreadPriority();
+  // Returns true if SetCurrentThreadPriority() should be able to increase the
+  // priority of a thread to |priority|.
+  static bool CanIncreaseThreadPriority(ThreadPriority priority);
 
   // Toggles the current thread's priority at runtime.
   //
diff --git a/base/threading/platform_thread_fuchsia.cc b/base/threading/platform_thread_fuchsia.cc
index eb06795c..2c642ad 100644
--- a/base/threading/platform_thread_fuchsia.cc
+++ b/base/threading/platform_thread_fuchsia.cc
@@ -31,7 +31,7 @@
 }
 
 // static
-bool PlatformThread::CanIncreaseCurrentThreadPriority() {
+bool PlatformThread::CanIncreaseThreadPriority(ThreadPriority priority) {
   return false;
 }
 
diff --git a/base/threading/platform_thread_mac.mm b/base/threading/platform_thread_mac.mm
index 39d979d..c470ce0f 100644
--- a/base/threading/platform_thread_mac.mm
+++ b/base/threading/platform_thread_mac.mm
@@ -148,7 +148,7 @@
 }  // anonymous namespace
 
 // static
-bool PlatformThread::CanIncreaseCurrentThreadPriority() {
+bool PlatformThread::CanIncreaseThreadPriority(ThreadPriority priority) {
   return true;
 }
 
diff --git a/base/threading/platform_thread_posix.cc b/base/threading/platform_thread_posix.cc
index 2466b78..83b61b5 100644
--- a/base/threading/platform_thread_posix.cc
+++ b/base/threading/platform_thread_posix.cc
@@ -240,10 +240,13 @@
 #if !defined(OS_MACOSX) && !defined(OS_FUCHSIA)
 
 // static
-bool PlatformThread::CanIncreaseCurrentThreadPriority() {
+bool PlatformThread::CanIncreaseThreadPriority(ThreadPriority priority) {
 #if defined(OS_NACL)
   return false;
 #else
+  // TODO(fdoray): Also check if the target priority is within the range allowed
+  // by RLIMIT_NICE. https://crbug.com/816389
+
   // Only root can raise thread priority on POSIX environment. On Linux, users
   // who have CAP_SYS_NICE permission also can raise the thread priority, but
   // libcap.so would be needed to check the capability.
diff --git a/base/threading/platform_thread_unittest.cc b/base/threading/platform_thread_unittest.cc
index da6f4eb..e4c50e65 100644
--- a/base/threading/platform_thread_unittest.cc
+++ b/base/threading/platform_thread_unittest.cc
@@ -220,88 +220,69 @@
 
 namespace {
 
-constexpr ThreadPriority kAllThreadPriorities[] = {
-    ThreadPriority::REALTIME_AUDIO, ThreadPriority::DISPLAY,
-    ThreadPriority::NORMAL, ThreadPriority::BACKGROUND};
+bool CanIncreaseThreadPriorityWrapper(ThreadPriority priority) {
+#if defined(OS_ANDROID)
+  // TODO(fdoray): PlatformThread::CanIncreaseThreadPriority(...) incorrectly
+  // returns false on Android. There should be a cross-POSIX implementation of
+  // this method that checks RLIMIT_NICE to determine whether it is possible
+  // to increase thread priority. https://crbug.com/872820
+  return true;
+#else
+  return PlatformThread::CanIncreaseThreadPriority(priority);
+#endif
+}
 
 class ThreadPriorityTestThread : public FunctionTestThread {
  public:
-  explicit ThreadPriorityTestThread() = default;
+  explicit ThreadPriorityTestThread(ThreadPriority from, ThreadPriority to)
+      : from_(from), to_(to) {}
   ~ThreadPriorityTestThread() override = default;
 
  private:
   void RunTest() override {
-#if !defined(OS_ANDROID)
-    // TODO(fdoray): PlatformThread::CanIncreaseCurrentThreadPriority()
-    // incorrectly returns false on Android. There should be a cross-POSIX
-    // implementation of this method that checks RLIMIT_NICE to determine
-    // whether it is possible to increase thread priority.
-    // https://crbug.com/872820
-    if (PlatformThread::CanIncreaseCurrentThreadPriority()) {
-#endif
-      // On platforms that support increasing thread priority, test transition
-      // between every possible pair of priorities.
-      for (auto first_priority : kAllThreadPriorities) {
-        for (auto second_priority : kAllThreadPriorities) {
-          PlatformThread::SetCurrentThreadPriority(first_priority);
-          EXPECT_EQ(first_priority, PlatformThread::GetCurrentThreadPriority());
+    EXPECT_EQ(PlatformThread::GetCurrentThreadPriority(),
+              ThreadPriority::NORMAL);
+    PlatformThread::SetCurrentThreadPriority(from_);
+    EXPECT_EQ(PlatformThread::GetCurrentThreadPriority(), from_);
+    PlatformThread::SetCurrentThreadPriority(to_);
 
-          PlatformThread::SetCurrentThreadPriority(second_priority);
-          EXPECT_EQ(second_priority,
-                    PlatformThread::GetCurrentThreadPriority());
-        }
-      }
-#if !defined(OS_ANDROID)
+    if (static_cast<int>(to_) <= static_cast<int>(from_) ||
+        CanIncreaseThreadPriorityWrapper(to_)) {
+      EXPECT_EQ(PlatformThread::GetCurrentThreadPriority(), to_);
     } else {
-      // On platforms that don't support increasing thread priority, the only
-      // valid transition is NORMAL -> BACKGROUND (it isn't possible to get a
-      // thread running with a higher priority than NORMAL).
-      EXPECT_EQ(ThreadPriority::NORMAL,
-                PlatformThread::GetCurrentThreadPriority());
-
-      // Verify that transition to DISPLAY or REALTIME_AUDIO is impossible.
-      PlatformThread::SetCurrentThreadPriority(ThreadPriority::DISPLAY);
-      EXPECT_EQ(ThreadPriority::NORMAL,
-                PlatformThread::GetCurrentThreadPriority());
-      PlatformThread::SetCurrentThreadPriority(ThreadPriority::REALTIME_AUDIO);
-      EXPECT_EQ(ThreadPriority::NORMAL,
-                PlatformThread::GetCurrentThreadPriority());
-
-      // Verify that transition to BACKGROUND is possible.
-      PlatformThread::SetCurrentThreadPriority(ThreadPriority::BACKGROUND);
-      EXPECT_EQ(ThreadPriority::BACKGROUND,
-                PlatformThread::GetCurrentThreadPriority());
-
-      // Verify that transition to NORMAL, DISPLAY or REALTIME_AUDIO is
-      // impossible.
-      PlatformThread::SetCurrentThreadPriority(ThreadPriority::NORMAL);
-      EXPECT_EQ(ThreadPriority::BACKGROUND,
-                PlatformThread::GetCurrentThreadPriority());
-      PlatformThread::SetCurrentThreadPriority(ThreadPriority::DISPLAY);
-      EXPECT_EQ(ThreadPriority::BACKGROUND,
-                PlatformThread::GetCurrentThreadPriority());
-      PlatformThread::SetCurrentThreadPriority(ThreadPriority::REALTIME_AUDIO);
-      EXPECT_EQ(ThreadPriority::BACKGROUND,
-                PlatformThread::GetCurrentThreadPriority());
+      EXPECT_NE(PlatformThread::GetCurrentThreadPriority(), to_);
     }
-#endif  // !defined(OS_ANDROID)
   }
 
+  const ThreadPriority from_;
+  const ThreadPriority to_;
+
   DISALLOW_COPY_AND_ASSIGN(ThreadPriorityTestThread);
 };
 
 void TestSetCurrentThreadPriority() {
-  ThreadPriorityTestThread thread;
-  PlatformThreadHandle handle;
+  constexpr ThreadPriority kAllThreadPriorities[] = {
+      ThreadPriority::REALTIME_AUDIO, ThreadPriority::DISPLAY,
+      ThreadPriority::NORMAL, ThreadPriority::BACKGROUND};
 
-  ASSERT_FALSE(thread.IsRunning());
-  ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));
-  thread.WaitForTerminationReady();
-  ASSERT_TRUE(thread.IsRunning());
+  for (auto from : kAllThreadPriorities) {
+    if (static_cast<int>(from) <= static_cast<int>(ThreadPriority::NORMAL) ||
+        CanIncreaseThreadPriorityWrapper(from)) {
+      for (auto to : kAllThreadPriorities) {
+        ThreadPriorityTestThread thread(from, to);
+        PlatformThreadHandle handle;
 
-  thread.MarkForTermination();
-  PlatformThread::Join(handle);
-  ASSERT_FALSE(thread.IsRunning());
+        ASSERT_FALSE(thread.IsRunning());
+        ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));
+        thread.WaitForTerminationReady();
+        ASSERT_TRUE(thread.IsRunning());
+
+        thread.MarkForTermination();
+        PlatformThread::Join(handle);
+        ASSERT_FALSE(thread.IsRunning());
+      }
+    }
+  }
 }
 
 }  // namespace
diff --git a/base/threading/platform_thread_win.cc b/base/threading/platform_thread_win.cc
index a40cbb4..0db57c0 100644
--- a/base/threading/platform_thread_win.cc
+++ b/base/threading/platform_thread_win.cc
@@ -271,7 +271,7 @@
 }
 
 // static
-bool PlatformThread::CanIncreaseCurrentThreadPriority() {
+bool PlatformThread::CanIncreaseThreadPriority(ThreadPriority priority) {
   return true;
 }
 
diff --git a/chrome/VERSION b/chrome/VERSION
index 1fb51f9..59dd56b 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=71
 MINOR=0
-BUILD=3545
+BUILD=3546
 PATCH=0
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 10504ec..df0635f4f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -118,6 +118,7 @@
 import org.chromium.chrome.browser.tab.BrowserControlsVisibilityDelegate;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
+import org.chromium.chrome.browser.tab.TabRedirectHandler;
 import org.chromium.chrome.browser.tab.TabStateBrowserControlsVisibilityDelegate;
 import org.chromium.chrome.browser.tabmodel.AsyncTabParamsManager;
 import org.chromium.chrome.browser.tabmodel.ChromeTabCreator;
@@ -1235,7 +1236,7 @@
                     // can be handled by other applications (e.g. www.youtube.com links).
                     Tab currentTab = getActivityTab();
                     if (currentTab != null) {
-                        currentTab.getTabRedirectHandler().updateIntent(intent);
+                        TabRedirectHandler.from(currentTab).updateIntent(intent);
                         int transitionType = PageTransition.LINK | PageTransition.FROM_API;
                         LoadUrlParams loadUrlParams = new LoadUrlParams(url);
                         loadUrlParams.setIntentReceivedTimestamp(mIntentHandlingTimeMs);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
index 4e61db7..5d4e464 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
@@ -275,7 +275,7 @@
 
         mInProductHelp.setParentView(parentView);
 
-        mTabRedirectHandler = new TabRedirectHandler(mActivity);
+        mTabRedirectHandler = TabRedirectHandler.create(mActivity);
 
         mPolicy.initialize();
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index bbd9cc6..e2f818a2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -84,6 +84,7 @@
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
+import org.chromium.chrome.browser.tab.TabRedirectHandler;
 import org.chromium.chrome.browser.tabmodel.AsyncTabParams;
 import org.chromium.chrome.browser.tabmodel.AsyncTabParamsManager;
 import org.chromium.chrome.browser.tabmodel.ChromeTabCreator;
@@ -755,7 +756,7 @@
     }
 
     private void initializeMainTab(Tab tab) {
-        tab.getTabRedirectHandler().updateIntent(getIntent());
+        TabRedirectHandler.from(tab).updateIntent(getIntent());
         tab.getView().requestFocus();
 
         mTabObserver = new CustomTabObserver(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
index a381634..59666ee5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
@@ -45,6 +45,7 @@
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabObserver;
+import org.chromium.chrome.browser.tab.TabRedirectHandler;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.chrome.browser.util.UrlUtilities;
 import org.chromium.chrome.browser.webapps.WebappActivity;
@@ -641,9 +642,8 @@
         if (!hasValidTab() || mTab.getWebContents() == null) return false;
 
         InstantAppsHandler handler = InstantAppsHandler.getInstance();
-        Intent intent = mTab.getTabRedirectHandler() != null
-                ? mTab.getTabRedirectHandler().getInitialIntent()
-                : null;
+        TabRedirectHandler redirect = TabRedirectHandler.getOrNull(mTab);
+        Intent intent = redirect != null ? redirect.getInitialIntent() : null;
         // TODO(mariakhomenko): consider also handling NDEF_DISCOVER action redirects.
         if (isIncomingRedirect && intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
             // Set the URL the redirect was resolved to for checking the existence of the
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
index cdb3b45..6d43da68 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
@@ -23,6 +23,7 @@
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.chrome.browser.fullscreen.FullscreenHtmlApiHandler.FullscreenHtmlApiDelegate;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBrowserControlsOffsetHelper;
 import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
@@ -543,7 +544,8 @@
 
     private boolean shouldShowAndroidControls() {
         if (mBrowserControlsAndroidViewHidden) return false;
-        if (getTab() != null && getTab().getControlsOffsetHelper().isControlsOffsetOverridden()) {
+        if (getTab() != null
+                && TabBrowserControlsOffsetHelper.from(getTab()).isControlsOffsetOverridden()) {
             return true;
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/FullscreenManager.java b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/FullscreenManager.java
index c3679cd3..de73edf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/FullscreenManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/FullscreenManager.java
@@ -10,6 +10,7 @@
 
 import org.chromium.chrome.browser.fullscreen.FullscreenHtmlApiHandler.FullscreenHtmlApiDelegate;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBrowserControlsOffsetHelper;
 
 /**
  * Manages the basic fullscreen functionality required by a Tab.
@@ -111,12 +112,18 @@
         if (mTab == tab) return;
 
         // Remove the fullscreen manager from the old tab before setting the new tab.
-        if (mTab != null) mTab.setFullscreenManager(null);
+        setFullscreenManager(null);
 
         mTab = tab;
 
         // Initialize the new tab with the correct fullscreen manager reference.
-        if (mTab != null) mTab.setFullscreenManager(this);
+        setFullscreenManager(this);
+    }
+
+    private void setFullscreenManager(FullscreenManager manager) {
+        if (mTab == null) return;
+        mTab.setFullscreenManager(manager);
+        TabBrowserControlsOffsetHelper.from(mTab).resetPositions();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
index c221e5d..ff1ef7c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
@@ -109,7 +109,7 @@
         if (mDialogContainer == null) initDialogContainer();
         setBrowserControlsAccess(true);
         // Don't show the dialog container before browser controls are guaranteed fully visible.
-        if (mActiveTab.getControlsOffsetHelper().areBrowserControlsFullyVisible()) {
+        if (TabBrowserControlsOffsetHelper.from(mActiveTab).areBrowserControlsFullyVisible()) {
             runEnterAnimation(dialogView);
         } else {
             mRunEnterAnimationOnCallback = true;
@@ -224,7 +224,7 @@
             assert mActiveTab
                     != null : "Tab modal dialogs should be shown on top of an active tab.";
 
-            mActiveTab.getControlsOffsetHelper().addObserver(this);
+            TabBrowserControlsOffsetHelper.from(mActiveTab).addObserver(this);
             // Hide contextual search panel so that bottom toolbar will not be
             // obscured and back press is not overridden.
             ContextualSearchManager contextualSearchManager =
@@ -259,7 +259,7 @@
             }
             menuButton.setEnabled(false);
         } else {
-            mActiveTab.getControlsOffsetHelper().removeObserver(this);
+            TabBrowserControlsOffsetHelper.from(mActiveTab).removeObserver(this);
             // Show the action bar back if it was dismissed when the dialogs were showing.
             if (mDidClearTextControls) {
                 mDidClearTextControls = false;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorController.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorController.java
index 4dd38aaa..6f6dc493f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/OfflineIndicatorController.java
@@ -228,8 +228,7 @@
             return;
         }
 
-        Drawable icon =
-                AppCompatResources.getDrawable(activity, R.drawable.ic_offline_pin_blue_white);
+        Drawable icon = AppCompatResources.getDrawable(activity, R.drawable.ic_offline_pin_white);
         Snackbar snackbar =
                 Snackbar.make(activity.getString(R.string.offline_indicator_offline_title), this,
                                 Snackbar.TYPE_ACTION, Snackbar.UMA_OFFLINE_INDICATOR)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java
index 2f02662..c84f99c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java
@@ -98,7 +98,7 @@
 
         TabRedirectHandler tabRedirectHandler = null;
         if (navigationParams.isMainFrame) {
-            tabRedirectHandler = mTab.getTabRedirectHandler();
+            tabRedirectHandler = TabRedirectHandler.from(mTab);
         } else if (navigationParams.isExternalProtocol) {
             // Only external protocol navigations are intercepted for iframe navigations.  Since
             // we do not see all previous navigations for the iframe, we can not build a complete
@@ -110,7 +110,7 @@
             // not covering the case where a gesture is carried over via a redirect.  This is
             // currently not feasible because we do not see all navigations for iframes and it is
             // better to error on the side of caution and require direct user gestures for iframes.
-            tabRedirectHandler = new TabRedirectHandler(associatedActivity);
+            tabRedirectHandler = TabRedirectHandler.create(associatedActivity);
         } else {
             assert false;
             return false;
@@ -200,8 +200,9 @@
             // redirect history to be consistent.
             NavigationController navigationController =
                     webContents.getNavigationController();
-            int indexBeforeRedirection = mTab.getTabRedirectHandler()
-                    .getLastCommittedEntryIndexBeforeStartingNavigation();
+            int indexBeforeRedirection =
+                    TabRedirectHandler.from(mTab)
+                            .getLastCommittedEntryIndexBeforeStartingNavigation();
             int lastCommittedEntryIndex = getLastCommittedEntryIndex();
             for (int i = lastCommittedEntryIndex - 1; i > indexBeforeRedirection; --i) {
                 boolean ret = navigationController.removeEntryAtIndex(i);
@@ -229,8 +230,9 @@
         // navigation is invalid, it means that this navigation is the first one since this tab was
         // created.
         // In such case, we would like to close this tab.
-        if (mTab.getTabRedirectHandler().isOnNavigation()) {
-            return mTab.getTabRedirectHandler().getLastCommittedEntryIndexBeforeStartingNavigation()
+        if (TabRedirectHandler.from(mTab).isOnNavigation()) {
+            return TabRedirectHandler.from(mTab)
+                           .getLastCommittedEntryIndexBeforeStartingNavigation()
                     == TabRedirectHandler.INVALID_ENTRY_INDEX;
         }
         return false;
@@ -262,9 +264,10 @@
                     mTab.getTabModelSelector().closeTab(mTab);
                 }
             });
-        } else if (mTab.getTabRedirectHandler().isOnNavigation()) {
-            int lastCommittedEntryIndexBeforeNavigation = mTab.getTabRedirectHandler()
-                    .getLastCommittedEntryIndexBeforeStartingNavigation();
+        } else if (TabRedirectHandler.from(mTab).isOnNavigation()) {
+            int lastCommittedEntryIndexBeforeNavigation =
+                    TabRedirectHandler.from(mTab)
+                            .getLastCommittedEntryIndexBeforeStartingNavigation();
             if (getLastCommittedEntryIndex() > lastCommittedEntryIndexBeforeNavigation) {
                 // http://crbug/426679 : we want to go back to the last committed entry index which
                 // was saved before this navigation, and remove the empty entries from the
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
index 931a0b62..b4b2919 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -349,8 +349,6 @@
      */
     private boolean mIsNativePageCommitPending;
 
-    private TabRedirectHandler mTabRedirectHandler;
-
     private FullscreenManager mFullscreenManager;
     private final TabBrowserControlsOffsetHelper mControlsOffsetHelper;
 
@@ -564,6 +562,10 @@
         return mUserDataHost;
     }
 
+    Context getContext() {
+        return mThemedApplicationContext;
+    }
+
     /**
      * Creates an instance of a {@link Tab}.
      *
@@ -616,7 +618,6 @@
             restoreFieldsFromState(frozenState);
         }
 
-        mTabRedirectHandler = new TabRedirectHandler(mThemedApplicationContext);
         addObserver(mTabObserver);
 
         if (incognito) {
@@ -1301,8 +1302,6 @@
 
             if (mTabUma != null) mTabUma.onHide();
 
-            mTabRedirectHandler.clear();
-
             // Allow this tab's NativePage to be frozen if it stays hidden for a while.
             NativePageAssassin.getInstance().tabHidden(this);
 
@@ -2902,7 +2901,6 @@
      */
     public void setFullscreenManager(FullscreenManager manager) {
         mFullscreenManager = manager;
-        mControlsOffsetHelper.resetPositions();
     }
 
     /**
@@ -3038,22 +3036,6 @@
     }
 
     /**
-     * @return the TabRedirectHandler for the tab.
-     */
-    public TabRedirectHandler getTabRedirectHandler() {
-        return mTabRedirectHandler;
-    }
-
-    /**
-     * Sets the TabRedirectHandler for the tab.
-     *
-     * @param tabRedirectHandler the TabRedirectHandler
-     */
-    public void setTabRedirectHandler(TabRedirectHandler tabRedirectHandler) {
-        mTabRedirectHandler = tabRedirectHandler;
-    }
-
-    /**
      * @return the AppBannerManager.
      */
     public AppBannerManager getAppBannerManager() {
@@ -3377,13 +3359,6 @@
         return nativeAreRendererInputEventsIgnored(mNativeTabAndroid);
     }
 
-    /**
-     * @return The {@link TabBrowserControlsOffsetHelper} for this tab.
-     */
-    public TabBrowserControlsOffsetHelper getControlsOffsetHelper() {
-        return mControlsOffsetHelper;
-    }
-
     @CalledByNative
     private void showMediaDownloadInProductHelp(int x, int y, int width, int height) {
         Rect rect = new Rect(x, y, x + width, y + height);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java
index cffced6..46732ea 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java
@@ -154,7 +154,7 @@
     /**
      * Resets the controls positions in {@link FullscreenManager} to the cached positions.
      */
-    void resetPositions() {
+    public void resetPositions() {
         resetControlsOffsetOverridden();
         if (mTab.getFullscreenManager() == null) return;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabRedirectHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabRedirectHandler.java
index a7a3e9e..2ecddbf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabRedirectHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabRedirectHandler.java
@@ -12,6 +12,8 @@
 import android.provider.Browser;
 import android.text.TextUtils;
 
+import org.chromium.base.UserData;
+import org.chromium.base.UserDataHost;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.LaunchIntentDispatcher;
 import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
@@ -25,7 +27,8 @@
 /**
  * This class contains the logic to determine effective navigation/redirect.
  */
-public class TabRedirectHandler {
+public class TabRedirectHandler extends EmptyTabObserver implements UserData {
+    private static final Class<TabRedirectHandler> USER_DATA_KEY = TabRedirectHandler.class;
     /**
      * An invalid entry index.
      */
@@ -54,10 +57,56 @@
 
     private final Context mContext;
 
-    public TabRedirectHandler(Context context) {
+    /**
+     * Returns {@link TabRedirectHandler} that hangs on to a given {@link Tab}.
+     * If not present, creates a new instance and associate it with the {@link UserDataHost}
+     * that the {@link Tab} manages.
+     * @param tab Tab instance that the TabRedirectHandler hangs on to.
+     * @return TabRedirectHandler for a given Tab.
+     */
+    public static TabRedirectHandler from(Tab tab) {
+        UserDataHost host = tab.getUserDataHost();
+        TabRedirectHandler handler = host.getUserData(USER_DATA_KEY);
+        if (handler == null) {
+            handler = new TabRedirectHandler(tab.getContext());
+            host.setUserData(USER_DATA_KEY, handler);
+            tab.addObserver(handler);
+        }
+        return handler;
+    }
+
+    /**
+     * @return {@link TabRedirectHandler} hanging to the given {@link Tab},
+     *     or {@code null} if there is no instance available.
+     */
+    public static TabRedirectHandler getOrNull(Tab tab) {
+        return tab.getUserDataHost().getUserData(USER_DATA_KEY);
+    }
+
+    /**
+     * Replace {@link TabRedirectHandler} instance for the Tab with the new one.
+     * @return Old {@link TabRedirectHandler} associated with the Tab. Could be {@code null}.
+     */
+    public static TabRedirectHandler swapFor(Tab tab, TabRedirectHandler newHandler) {
+        UserDataHost host = tab.getUserDataHost();
+        TabRedirectHandler oldHandler = host.getUserData(TabRedirectHandler.class);
+        host.setUserData(TabRedirectHandler.class, newHandler);
+        return oldHandler;
+    }
+
+    public static TabRedirectHandler create(Context context) {
+        return new TabRedirectHandler(context);
+    }
+
+    protected TabRedirectHandler(Context context) {
         mContext = context;
     }
 
+    @Override
+    public void onHidden(Tab tab) {
+        clear();
+    }
+
     /**
      * Updates |mIntentHistory| and |mLastIntentUpdatedTime|. If |intent| comes from chrome and
      * currently |mIsOnEffectiveIntentRedirectChain| is true, that means |intent| was sent from
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java
index a2b5779..e6ef88f1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java
@@ -29,13 +29,13 @@
 
     @Override
     public void onTopControlsChanged(float topControlsOffsetY, float topContentOffsetY) {
-        mTab.getControlsOffsetHelper().onOffsetsChanged(
+        TabBrowserControlsOffsetHelper.from(mTab).onOffsetsChanged(
                 topControlsOffsetY, Float.NaN, topContentOffsetY);
     }
 
     @Override
     public void onBottomControlsChanged(float bottomControlsOffsetY, float bottomContentOffsetY) {
-        mTab.getControlsOffsetHelper().onOffsetsChanged(
+        TabBrowserControlsOffsetHelper.from(mTab).onOffsetsChanged(
                 Float.NaN, bottomControlsOffsetY, Float.NaN);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
index 592e4bfd..bd8e2439 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
@@ -19,6 +19,7 @@
 import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
+import org.chromium.chrome.browser.tab.TabRedirectHandler;
 import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType;
 import org.chromium.chrome.browser.util.IntentUtils;
@@ -152,7 +153,7 @@
                 tab.initialize(null, mTabContentManager, delegateFactory, !openInForeground, false);
                 tab.loadUrl(loadUrlParams);
             }
-            tab.getTabRedirectHandler().updateIntent(intent);
+            TabRedirectHandler.from(tab).updateIntent(intent);
             if (intent != null && intent.hasExtra(ServiceTabLauncher.LAUNCH_REQUEST_ID_EXTRA)) {
                 ServiceTabLauncher.onWebContentsForRequestAvailable(
                         intent.getIntExtra(ServiceTabLauncher.LAUNCH_REQUEST_ID_EXTRA, 0),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShell.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShell.java
index f8c990d..cab3d8db 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShell.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShell.java
@@ -494,15 +494,14 @@
     private void initializeTabForVR() {
         if (mTab == null) return;
         // Make sure we are not redirecting to another app, i.e. out of VR mode.
-        mNonVrTabRedirectHandler = mTab.getTabRedirectHandler();
-        mTab.setTabRedirectHandler(mTabRedirectHandler);
+        mNonVrTabRedirectHandler = TabRedirectHandler.swapFor(mTab, mTabRedirectHandler);
         assert mTab.getWindowAndroid() == mContentVrWindowAndroid;
         configWebContentsImeForVr(mTab.getWebContents());
     }
 
     private void restoreTabFromVR() {
         if (mTab == null) return;
-        mTab.setTabRedirectHandler(mNonVrTabRedirectHandler);
+        TabRedirectHandler.swapFor(mTab, mNonVrTabRedirectHandler);
         mNonVrTabRedirectHandler = null;
         restoreWebContentsImeFromVr(mTab.getWebContents());
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java
index cd1500f..8213a8b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java
@@ -30,11 +30,13 @@
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher;
 import org.chromium.content_public.browser.ViewEventSink;
+import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
 import org.chromium.content_public.browser.test.util.TouchCommon;
+import org.chromium.content_public.browser.test.util.WebContentsUtils;
 import org.chromium.ui.test.util.UiRestriction;
 
 import java.util.ArrayDeque;
@@ -197,9 +199,10 @@
     @Test
     @MediumTest
     public void testPauseTriggersBlur() throws Exception {
+        final WebContents webContents = mActivityTestRule.getWebContents();
         final CallbackHelper onTitleUpdatedHelper = new CallbackHelper();
         final WebContentsObserver observer =
-                new WebContentsObserver(mActivityTestRule.getWebContents()) {
+                new WebContentsObserver(webContents) {
                     @Override
                     public void titleWasSet(String title) {
                         mTitle = title;
@@ -210,7 +213,7 @@
         String url = UrlUtils.getIsolatedTestFileUrl(
                 "chrome/test/data/android/content_view_focus/content_view_blur_focus.html");
         mActivityTestRule.loadUrl(url);
-        ViewEventSink eventSink = ViewEventSink.from(mActivityTestRule.getWebContents());
+        ViewEventSink eventSink = WebContentsUtils.getViewEventSink(webContents);
         onTitleUpdatedHelper.waitForCallback(callCount);
         Assert.assertEquals("initial", mTitle);
         callCount = onTitleUpdatedHelper.getCallCount();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ModalDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ModalDialogTest.java
index 62ff9dc70..93a3fc1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ModalDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ModalDialogTest.java
@@ -28,13 +28,13 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.content_public.browser.GestureListenerManager;
 import org.chromium.content_public.browser.GestureStateListener;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper;
 import org.chromium.content_public.browser.test.util.TouchCommon;
+import org.chromium.content_public.browser.test.util.WebContentsUtils;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -241,18 +241,14 @@
         }
     }
 
-    private GestureListenerManager getGestureListenerManager() {
-        return GestureListenerManager.fromWebContents(
-                mActivityTestRule.getActivity().getCurrentWebContents());
-    }
-
     /**
      * Taps on a view and waits for a callback.
      */
     private void tapViewAndWait() throws InterruptedException, TimeoutException {
         final TapGestureStateListener tapGestureStateListener = new TapGestureStateListener();
         int callCount = tapGestureStateListener.getCallCount();
-        getGestureListenerManager().addListener(tapGestureStateListener);
+        WebContentsUtils.getGestureListenerManager(mActivityTestRule.getWebContents())
+                .addListener(tapGestureStateListener);
 
         TouchCommon.singleClickView(mActivityTestRule.getActivity().getActivityTab().getView());
         tapGestureStateListener.waitForTap(callCount);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillPopupTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillPopupTest.java
index 0c44407..99a7fdd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillPopupTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillPopupTest.java
@@ -31,6 +31,7 @@
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.TestInputMethodManagerWrapper;
 import org.chromium.content_public.browser.test.util.TouchCommon;
+import org.chromium.content_public.browser.test.util.WebContentsUtils;
 import org.chromium.ui.DropdownPopupWindowInterface;
 import org.chromium.ui.R;
 
@@ -151,7 +152,7 @@
         // brought up.
         final WebContents webContents = mActivityTestRule.getActivity().getCurrentWebContents();
         final ViewGroup view = webContents.getViewAndroidDelegate().getContainerView();
-        final ImeAdapter imeAdapter = ImeAdapter.fromWebContents(webContents);
+        final ImeAdapter imeAdapter = WebContentsUtils.getImeAdapter(webContents);
         TestInputMethodManagerWrapper immw = TestInputMethodManagerWrapper.create(imeAdapter);
         imeAdapter.setInputMethodManagerWrapper(immw);
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandlerTest.java
index 92d41796..f72542c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandlerTest.java
@@ -455,7 +455,7 @@
         mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
         mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
 
-        TabRedirectHandler redirectHandler = new TabRedirectHandler(mContext);
+        TabRedirectHandler redirectHandler = TabRedirectHandler.create(mContext);
         Intent ytIntent = Intent.parseUri(YOUTUBE_URL, Intent.URI_INTENT_SCHEME);
         Intent fooIntent = Intent.parseUri("http://foo.com/", Intent.URI_INTENT_SCHEME);
         int transTypeLinkFromIntent = PageTransition.LINK
@@ -501,7 +501,7 @@
     testInitialIntentHeadingToChrome() throws URISyntaxException {
         mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
 
-        TabRedirectHandler redirectHandler = new TabRedirectHandler(mContext);
+        TabRedirectHandler redirectHandler = TabRedirectHandler.create(mContext);
         Intent fooIntent = Intent.parseUri("http://foo.com/", Intent.URI_INTENT_SCHEME);
         fooIntent.setPackage(mContext.getPackageName());
         int transTypeLinkFromIntent = PageTransition.LINK
@@ -535,7 +535,7 @@
     testIntentForCustomTab() throws URISyntaxException {
         mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
 
-        TabRedirectHandler redirectHandler = new TabRedirectHandler(mContext);
+        TabRedirectHandler redirectHandler = TabRedirectHandler.create(mContext);
         int transTypeLinkFromIntent = PageTransition.LINK | PageTransition.FROM_API;
 
         // In Custom Tabs, if the first url is not a redirect, stay in chrome.
@@ -611,7 +611,7 @@
     @SmallTest
     public void
     testInstantAppsIntent_customTabRedirect() throws Exception {
-        TabRedirectHandler redirectHandler = new TabRedirectHandler(mContext);
+        TabRedirectHandler redirectHandler = TabRedirectHandler.create(mContext);
         int transTypeLinkFromIntent = PageTransition.LINK | PageTransition.FROM_API;
 
         // In Custom Tabs, if the first url is a redirect, don't allow it to intent out, unless
@@ -637,7 +637,7 @@
     public void testInstantAppsIntent_incomingIntentRedirect() throws Exception {
         int transTypeLinkFromIntent = PageTransition.LINK
                 | PageTransition.FROM_API;
-        TabRedirectHandler redirectHandler = new TabRedirectHandler(mContext);
+        TabRedirectHandler redirectHandler = TabRedirectHandler.create(mContext);
         Intent fooIntent = Intent.parseUri("http://instantappenabled.com",
                 Intent.URI_INTENT_SCHEME);
         redirectHandler.updateIntent(fooIntent);
@@ -815,7 +815,7 @@
     @Test
     @SmallTest
     public void testFallbackUrl_RedirectToIntentToMarket() {
-        TabRedirectHandler redirectHandler = new TabRedirectHandler(mContext);
+        TabRedirectHandler redirectHandler = TabRedirectHandler.create(mContext);
 
         redirectHandler.updateNewUrlLoading(PageTransition.TYPED, false, false, 0, 0);
         checkUrl("http://goo.gl/abcdefg")
@@ -898,7 +898,7 @@
     public void testFallback_UseFallbackUrlForRedirectionFromTypedInUrl() {
         mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
 
-        TabRedirectHandler redirectHandler = new TabRedirectHandler(mContext);
+        TabRedirectHandler redirectHandler = TabRedirectHandler.create(mContext);
 
         redirectHandler.updateNewUrlLoading(PageTransition.TYPED, false, false, 0, 0);
         checkUrl("http://goo.gl/abcdefg")
@@ -924,7 +924,7 @@
         // We cannot resolve any intent, so fall-back URL will be used.
         mDelegate.setCanResolveActivityForExternalSchemes(false);
 
-        TabRedirectHandler redirectHandler = new TabRedirectHandler(mContext);
+        TabRedirectHandler redirectHandler = TabRedirectHandler.create(mContext);
 
         redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0);
         checkUrl(INTENT_URL_WITH_CHAIN_FALLBACK_URL)
@@ -958,7 +958,7 @@
     public void testIgnoreEffectiveRedirectFromUserTyping() {
         mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
 
-        TabRedirectHandler redirectHandler = new TabRedirectHandler(mContext);
+        TabRedirectHandler redirectHandler = TabRedirectHandler.create(mContext);
 
         redirectHandler.updateNewUrlLoading(PageTransition.TYPED, false, false, 0, 0);
         checkUrl(YOUTUBE_MOBILE_URL)
@@ -984,7 +984,7 @@
     public void testNavigationFromLinkWithoutUserGesture() {
         mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
 
-        TabRedirectHandler redirectHandler = new TabRedirectHandler(mContext);
+        TabRedirectHandler redirectHandler = TabRedirectHandler.create(mContext);
 
         redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 1, 0);
         checkUrl(YOUTUBE_MOBILE_URL)
@@ -1156,7 +1156,7 @@
     public void testNavigationFromReload() {
         mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
 
-        TabRedirectHandler redirectHandler = new TabRedirectHandler(mContext);
+        TabRedirectHandler redirectHandler = TabRedirectHandler.create(mContext);
 
         redirectHandler.updateNewUrlLoading(PageTransition.RELOAD, false, false, 1, 0);
         checkUrl(YOUTUBE_MOBILE_URL)
@@ -1180,7 +1180,7 @@
     public void testNavigationWithForwardBack() {
         mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
 
-        TabRedirectHandler redirectHandler = new TabRedirectHandler(mContext);
+        TabRedirectHandler redirectHandler = TabRedirectHandler.create(mContext);
 
         redirectHandler.updateNewUrlLoading(
                 PageTransition.FORM_SUBMIT | PageTransition.FORWARD_BACK, false, false, 1, 0);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java
index 1962b63..948a02a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java
@@ -48,13 +48,13 @@
 import org.chromium.content_public.browser.GestureListenerManager;
 import org.chromium.content_public.browser.GestureStateListener;
 import org.chromium.content_public.browser.SelectionPopupController;
-import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.JavaScriptUtils;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
 import org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.content_public.browser.test.util.UiUtils;
+import org.chromium.content_public.browser.test.util.WebContentsUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.concurrent.TimeUnit;
@@ -279,9 +279,8 @@
         };
 
         Tab tab = mActivityTestRule.getActivity().getActivityTab();
-        WebContents webContents = tab.getWebContents();
         GestureListenerManager gestureListenerManager =
-                GestureListenerManager.fromWebContents(webContents);
+                WebContentsUtils.getGestureListenerManager(tab.getWebContents());
         gestureListenerManager.addListener(scrollListener);
 
         final CallbackHelper viewportCallback = new CallbackHelper();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTestUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTestUtils.java
index f49fa89..c6e22a5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTestUtils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTestUtils.java
@@ -20,6 +20,7 @@
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TouchCommon;
+import org.chromium.content_public.browser.test.util.WebContentsUtils;
 
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
@@ -151,7 +152,7 @@
 
         };
         GestureListenerManager gestureListenerManager =
-                GestureListenerManager.fromWebContents(webContents);
+                WebContentsUtils.getGestureListenerManager(webContents);
         gestureListenerManager.addListener(scrollEndListener);
 
         for (int i = 0; i < 10; i++) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabRedirectHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabRedirectHandlerTest.java
index 1dd594f..1f0fd9e0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabRedirectHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabRedirectHandlerTest.java
@@ -62,7 +62,7 @@
     @SmallTest
     @Feature({"IntentHandling"})
     public void testRealIntentRedirect() {
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
         handler.updateIntent(sYtIntent);
         Assert.assertFalse(handler.isOnNavigation());
 
@@ -82,7 +82,7 @@
     @SmallTest
     @Feature({"IntentHandling"})
     public void testEffectiveIntentRedirect() {
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
         handler.updateIntent(sYtIntent);
         Assert.assertFalse(handler.isOnNavigation());
 
@@ -102,7 +102,7 @@
     @SmallTest
     @Feature({"IntentHandling"})
     public void testNoIntent() {
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
         handler.updateIntent(null);
         Assert.assertFalse(handler.isOnNavigation());
 
@@ -122,7 +122,7 @@
     @SmallTest
     @Feature({"IntentHandling"})
     public void testClear() {
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
         handler.updateIntent(sYtIntent);
         Assert.assertFalse(handler.isOnNavigation());
 
@@ -147,7 +147,7 @@
     @SmallTest
     @Feature({"IntentHandling"})
     public void testNonLinkFromIntent() {
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
         handler.updateIntent(sYtIntent);
         Assert.assertFalse(handler.isOnNavigation());
 
@@ -167,7 +167,7 @@
     @SmallTest
     @Feature({"IntentHandling"})
     public void testUserInteraction() {
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
         handler.updateIntent(sYtIntent);
         Assert.assertFalse(handler.isOnNavigation());
 
@@ -196,7 +196,7 @@
     @SmallTest
     @Feature({"IntentHandling"})
     public void testIntentFromChrome() {
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
         Intent fooIntent = new Intent(sFooIntent);
         fooIntent.putExtra(Browser.EXTRA_APPLICATION_ID, TEST_PACKAGE_NAME);
         handler.updateIntent(fooIntent);
@@ -228,7 +228,7 @@
     @SmallTest
     @Feature({"IntentHandling"})
     public void testNavigationFromUserTyping() {
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
         handler.updateIntent(sYtIntent);
         Assert.assertFalse(handler.isOnNavigation());
         Assert.assertFalse(handler.isNavigationFromUserTyping());
@@ -254,7 +254,7 @@
     @SmallTest
     @Feature({"IntentHandling"})
     public void testIntentHavingChromePackageName() {
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
         Intent fooIntent = new Intent(sFooIntent);
         fooIntent.setPackage(TEST_PACKAGE_NAME);
         handler.updateIntent(fooIntent);
@@ -289,7 +289,7 @@
         /////////////////////////////////////////////////////
         // 1. 3XX redirection should not override URL loading.
         /////////////////////////////////////////////////////
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
         handler.updateIntent(sYtIntent);
         Assert.assertFalse(handler.shouldNotOverrideUrlLoading());
 
@@ -303,7 +303,7 @@
         /////////////////////////////////////////////////////
         // 2. Effective redirection should not override URL loading.
         /////////////////////////////////////////////////////
-        handler = new TabRedirectHandler(mContext);
+        handler = TabRedirectHandler.create(mContext);
         handler.updateIntent(sYtIntent);
         Assert.assertFalse(handler.shouldNotOverrideUrlLoading());
 
@@ -330,7 +330,7 @@
     @Feature({"IntentHandling"})
     @RetryOnFailure
     public void testNavigationFromLinkWithoutUserGesture() {
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
         handler.updateIntent(sYtIntent);
         Assert.assertFalse(handler.isOnNavigation());
         Assert.assertFalse(handler.shouldStayInChrome(false));
@@ -363,7 +363,7 @@
     @Feature({"IntentHandling"})
     @RetryOnFailure
     public void testNavigationFromReload() {
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
         handler.updateIntent(sYtIntent);
         Assert.assertFalse(handler.isOnNavigation());
         Assert.assertFalse(handler.shouldStayInChrome(false));
@@ -396,7 +396,7 @@
     @Feature({"IntentHandling"})
     @RetryOnFailure
     public void testNavigationWithForwardBack() {
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
         handler.updateIntent(sYtIntent);
         Assert.assertFalse(handler.isOnNavigation());
         Assert.assertFalse(handler.shouldStayInChrome(false));
@@ -431,7 +431,7 @@
         // User interaction time could be uninitialized when a new document activity is opened after
         // clicking a link. In that case, the value is 0.
         final long uninitializedUserInteractionTime = 0;
-        TabRedirectHandler handler = new TabRedirectHandler(mContext);
+        TabRedirectHandler handler = TabRedirectHandler.create(mContext);
 
         Assert.assertFalse(handler.isOnNavigation());
         handler.updateNewUrlLoading(PageTransition.LINK, false, true,
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 21b93c7..bb8c9f98 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-71.0.3543.0_rc-r1.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-71.0.3544.0_rc-r1.afdo.bz2
\ No newline at end of file
diff --git a/chrome/app/chrome_manifest.json b/chrome/app/chrome_manifest.json
index 444d1b9e..d8ae07b21f 100644
--- a/chrome/app/chrome_manifest.json
+++ b/chrome/app/chrome_manifest.json
@@ -2,7 +2,8 @@
   "name": "chrome",
   "display_name": "Chrome",
   "options" : {
-    "instance_sharing" : "shared_instance_across_users"
+    "instance_sharing": "shared_instance_across_users",
+    "can_connect_to_other_services_with_any_instance_name": true
   },
   "interface_provider_specs": {
     "service_manager:connector": {
@@ -24,8 +25,7 @@
       "requires": {
         "chrome_renderer": [ "browser" ],
         "service_manager": [
-          "service_manager:client_process",
-          "service_manager:instance_name"
+          "service_manager:client_process"
         ]
       }
     }
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 5153d6e..826d068 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3588,14 +3588,6 @@
      FEATURE_VALUE_TYPE(features::kDirectManipulationStylus)},
 #endif  // defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX)
 
-#if !defined(OS_ANDROID)
-    {"remove-deprecared-gaia-signin-endpoint",
-     flag_descriptions::kRemoveUsageOfDeprecatedGaiaSigninEndpointName,
-     flag_descriptions::kRemoveUsageOfDeprecatedGaiaSigninEndpointDescription,
-     kOsWin | kOsMac | kOsLinux,
-     FEATURE_VALUE_TYPE(features::kRemoveUsageOfDeprecatedGaiaSigninEndpoint)},
-#endif
-
 #if defined(OS_ANDROID)
     {"third-party-doodles", flag_descriptions::kThirdPartyDoodlesName,
      flag_descriptions::kThirdPartyDoodlesDescription, kOsAndroid,
diff --git a/chrome/browser/background_fetch/background_fetch_delegate_impl.cc b/chrome/browser/background_fetch/background_fetch_delegate_impl.cc
index 56026fe..5b65c0c 100644
--- a/chrome/browser/background_fetch/background_fetch_delegate_impl.cc
+++ b/chrome/browser/background_fetch/background_fetch_delegate_impl.cc
@@ -46,7 +46,9 @@
   offline_content_aggregator_->RegisterProvider(provider_namespace_, this);
 }
 
-BackgroundFetchDelegateImpl::~BackgroundFetchDelegateImpl() = default;
+BackgroundFetchDelegateImpl::~BackgroundFetchDelegateImpl() {
+  offline_content_aggregator_->UnregisterProvider(provider_namespace_);
+}
 
 download::DownloadService* BackgroundFetchDelegateImpl::GetDownloadService() {
   if (download_service_)
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index ec30cd0d..3e7abde 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -4493,7 +4493,8 @@
     content::RenderFrameHost* frame,
     bool is_navigation,
     const GURL& url,
-    network::mojom::URLLoaderFactoryRequest* factory_request) {
+    network::mojom::URLLoaderFactoryRequest* factory_request,
+    bool* bypass_redirect_checks) {
   DCHECK(base::FeatureList::IsEnabled(network::features::kNetworkService));
   bool use_proxy = false;
 
@@ -4505,8 +4506,12 @@
   // NOTE: Some unit test environments do not initialize
   // BrowserContextKeyedAPI factories for e.g. WebRequest.
   if (web_request_api) {
-    use_proxy |= web_request_api->MaybeProxyURLLoaderFactory(
-        frame, is_navigation, factory_request);
+    bool use_proxy_for_web_request =
+        web_request_api->MaybeProxyURLLoaderFactory(frame, is_navigation,
+                                                    factory_request);
+    if (bypass_redirect_checks)
+      *bypass_redirect_checks = use_proxy_for_web_request;
+    use_proxy |= use_proxy_for_web_request;
   }
 #endif
 
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index a063582..ad21627 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -439,7 +439,8 @@
       content::RenderFrameHost* frame,
       bool is_navigation,
       const GURL& url,
-      network::mojom::URLLoaderFactoryRequest* factory_request) override;
+      network::mojom::URLLoaderFactoryRequest* factory_request,
+      bool* bypass_redirect_checks) override;
   std::vector<std::unique_ptr<content::URLLoaderRequestInterceptor>>
   WillCreateURLLoaderRequestInterceptors(
       content::NavigationUIData* navigation_ui_data,
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 0bcd2fb..b4e1b72 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -562,6 +562,8 @@
     "chrome_service_name.h",
     "crostini/crostini_manager.cc",
     "crostini/crostini_manager.h",
+    "crostini/crostini_manager_factory.cc",
+    "crostini/crostini_manager_factory.h",
     "crostini/crostini_mime_types_service.cc",
     "crostini/crostini_mime_types_service.h",
     "crostini/crostini_mime_types_service_factory.cc",
diff --git a/chrome/browser/chromeos/android_sms/BUILD.gn b/chrome/browser/chromeos/android_sms/BUILD.gn
index f0026bd9..3531206c 100644
--- a/chrome/browser/chromeos/android_sms/BUILD.gn
+++ b/chrome/browser/chromeos/android_sms/BUILD.gn
@@ -35,6 +35,8 @@
     "//chrome/browser/chromeos:chromeos",
     "//chromeos:chromeos",
     "//chromeos/components/proximity_auth/logging",
+    "//chromeos/services/multidevice_setup/public/cpp:cpp",
+    "//chromeos/services/multidevice_setup/public/cpp:prefs",
     "//components/keyed_service/content:content",
     "//components/keyed_service/core:core",
     "//components/session_manager/core:core",
@@ -68,6 +70,7 @@
     ":android_sms",
     ":android_sms_urls",
     ":test_support",
+    "//chromeos/services/multidevice_setup/public/cpp:test_support",
     "//content/public/browser",
     "//content/test:test_support",
     "//testing/gtest",
diff --git a/chrome/browser/chromeos/android_sms/android_sms_service.cc b/chrome/browser/chromeos/android_sms/android_sms_service.cc
index 420c7ca9..1611ded 100644
--- a/chrome/browser/chromeos/android_sms/android_sms_service.cc
+++ b/chrome/browser/chromeos/android_sms/android_sms_service.cc
@@ -5,9 +5,15 @@
 #include "chrome/browser/chromeos/android_sms/android_sms_service.h"
 #include "chrome/browser/chromeos/android_sms/android_sms_urls.h"
 #include "chrome/browser/chromeos/android_sms/connection_establisher_impl.h"
+#include "chrome/browser/chromeos/multidevice_setup/multidevice_setup_client_factory.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chromeos/services/multidevice_setup/public/cpp/prefs.h"
 #include "components/session_manager/core/session_manager.h"
 #include "content/public/browser/storage_partition.h"
 
+using chromeos::multidevice_setup::MultiDeviceSetupClient;
+using chromeos::multidevice_setup::MultiDeviceSetupClientFactory;
+
 namespace chromeos {
 
 namespace android_sms {
@@ -37,8 +43,14 @@
           browser_context_, GetAndroidMessagesURL());
   content::ServiceWorkerContext* service_worker_context =
       storage_partition->GetServiceWorkerContext();
+
+  MultiDeviceSetupClient* multidevice_setup_client =
+      MultiDeviceSetupClientFactory::GetForProfile(
+          Profile::FromBrowserContext(browser_context_));
+
   connection_manager_ = std::make_unique<ConnectionManager>(
-      service_worker_context, std::make_unique<ConnectionEstablisherImpl>());
+      service_worker_context, std::make_unique<ConnectionEstablisherImpl>(),
+      multidevice_setup_client);
 }
 
 }  // namespace android_sms
diff --git a/chrome/browser/chromeos/android_sms/android_sms_service_factory.cc b/chrome/browser/chromeos/android_sms/android_sms_service_factory.cc
index 30f78ae..6e4622aa 100644
--- a/chrome/browser/chromeos/android_sms/android_sms_service_factory.cc
+++ b/chrome/browser/chromeos/android_sms/android_sms_service_factory.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/chromeos/android_sms/android_sms_service_factory.h"
+#include "chrome/browser/chromeos/multidevice_setup/multidevice_setup_client_factory.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chromeos/chromeos_features.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
@@ -28,7 +29,10 @@
 AndroidSmsServiceFactory::AndroidSmsServiceFactory()
     : BrowserContextKeyedServiceFactory(
           "AndroidSmsService",
-          BrowserContextDependencyManager::GetInstance()) {}
+          BrowserContextDependencyManager::GetInstance()) {
+  DependsOn(chromeos::multidevice_setup::MultiDeviceSetupClientFactory::
+                GetInstance());
+}
 
 AndroidSmsServiceFactory::~AndroidSmsServiceFactory() = default;
 
diff --git a/chrome/browser/chromeos/android_sms/connection_manager.cc b/chrome/browser/chromeos/android_sms/connection_manager.cc
index 9021b0b..f5122d53 100644
--- a/chrome/browser/chromeos/android_sms/connection_manager.cc
+++ b/chrome/browser/chromeos/android_sms/connection_manager.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/chromeos/android_sms/connection_manager.h"
 #include "chrome/browser/chromeos/android_sms/android_sms_urls.h"
+#include "chromeos/components/proximity_auth/logging/logging.h"
 #include "components/session_manager/core/session_manager.h"
 
 namespace chromeos {
@@ -12,15 +13,20 @@
 
 ConnectionManager::ConnectionManager(
     content::ServiceWorkerContext* service_worker_context,
-    std::unique_ptr<ConnectionEstablisher> connection_establisher)
+    std::unique_ptr<ConnectionEstablisher> connection_establisher,
+    MultiDeviceSetupClient* multidevice_setup_client)
     : service_worker_context_(service_worker_context),
-      connection_establisher_(std::move(connection_establisher)) {
+      connection_establisher_(std::move(connection_establisher)),
+      multidevice_setup_client_(multidevice_setup_client) {
   service_worker_context_->AddObserver(this);
-  connection_establisher_->EstablishConnection(service_worker_context_);
+  multidevice_setup_client_->AddObserver(this);
+  UpdateAndroidSmsFeatureState(multidevice_setup_client->GetFeatureState(
+      multidevice_setup::mojom::Feature::kMessages));
 }
 
 ConnectionManager::~ConnectionManager() {
   service_worker_context_->RemoveObserver(this);
+  multidevice_setup_client_->RemoveObserver(this);
 }
 
 void ConnectionManager::OnVersionActivated(int64_t version_id,
@@ -30,7 +36,8 @@
 
   prev_active_version_id_ = active_version_id_;
   active_version_id_ = version_id;
-  connection_establisher_->EstablishConnection(service_worker_context_);
+  if (is_android_sms_enabled_)
+    connection_establisher_->EstablishConnection(service_worker_context_);
 }
 
 void ConnectionManager::OnVersionRedundant(int64_t version_id,
@@ -60,7 +67,36 @@
   if (active_version_id_ != version_id)
     return;
 
-  connection_establisher_->EstablishConnection(service_worker_context_);
+  if (is_android_sms_enabled_)
+    connection_establisher_->EstablishConnection(service_worker_context_);
+}
+
+void ConnectionManager::OnFeatureStatesChanged(
+    const MultiDeviceSetupClient::FeatureStatesMap& feature_states_map) {
+  const auto it =
+      feature_states_map.find(multidevice_setup::mojom::Feature::kMessages);
+  if (it == feature_states_map.end())
+    return;
+
+  UpdateAndroidSmsFeatureState(it->second);
+}
+
+void ConnectionManager::UpdateAndroidSmsFeatureState(
+    multidevice_setup::mojom::FeatureState feature_state) {
+  bool is_enabled =
+      feature_state == multidevice_setup::mojom::FeatureState::kEnabledByUser;
+  if (is_android_sms_enabled_ == is_enabled)
+    return;
+
+  PA_LOG(INFO) << "ConnectionManager::UpdateAndroidSmsFeatureState enabled: "
+               << is_enabled;
+  if (is_enabled) {
+    connection_establisher_->EstablishConnection(service_worker_context_);
+  } else {
+    service_worker_context_->StopAllServiceWorkersForOrigin(
+        GetAndroidMessagesURL());
+  }
+  is_android_sms_enabled_ = is_enabled;
 }
 
 }  // namespace android_sms
diff --git a/chrome/browser/chromeos/android_sms/connection_manager.h b/chrome/browser/chromeos/android_sms/connection_manager.h
index 6b11077..8209ed4 100644
--- a/chrome/browser/chromeos/android_sms/connection_manager.h
+++ b/chrome/browser/chromeos/android_sms/connection_manager.h
@@ -7,10 +7,13 @@
 
 #include "base/gtest_prod_util.h"
 #include "chrome/browser/chromeos/android_sms/connection_establisher.h"
+#include "chromeos/services/multidevice_setup/public/cpp/multidevice_setup_client.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/service_worker_context.h"
 #include "content/public/browser/service_worker_context_observer.h"
 
+using chromeos::multidevice_setup::MultiDeviceSetupClient;
+
 namespace chromeos {
 
 namespace android_sms {
@@ -33,11 +36,13 @@
 // connection as required. E.g., The service worker will not establish a
 // connection if it's is already connected to the Android Messages for Web page
 // or if a connection already exists.
-class ConnectionManager : public content::ServiceWorkerContextObserver {
+class ConnectionManager : public content::ServiceWorkerContextObserver,
+                          public MultiDeviceSetupClient::Observer {
  public:
   ConnectionManager(
       content::ServiceWorkerContext* service_worker_context,
-      std::unique_ptr<ConnectionEstablisher> connection_establisher);
+      std::unique_ptr<ConnectionEstablisher> connection_establisher,
+      MultiDeviceSetupClient* multidevice_setup_client);
   ~ConnectionManager() override;
 
  private:
@@ -46,8 +51,16 @@
   void OnVersionRedundant(int64_t version_id, const GURL& scope) override;
   void OnNoControllees(int64_t version_id, const GURL& scope) override;
 
+  // MultideviceSetupClient::Observer:
+  void OnFeatureStatesChanged(const MultiDeviceSetupClient::FeatureStatesMap&
+                                  feature_state_map) override;
+
+  void UpdateAndroidSmsFeatureState(
+      multidevice_setup::mojom::FeatureState feature_state);
+
   content::ServiceWorkerContext* service_worker_context_;
   std::unique_ptr<ConnectionEstablisher> connection_establisher_;
+  MultiDeviceSetupClient* multidevice_setup_client_;
 
   // Version ID of the Android Messages for Web service worker that's currently
   // active i.e., capable of handling messages and controlling pages.
@@ -57,6 +70,8 @@
   // service worker.
   base::Optional<int64_t> prev_active_version_id_;
 
+  bool is_android_sms_enabled_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(ConnectionManager);
 };
 
diff --git a/chrome/browser/chromeos/android_sms/connection_manager_unittest.cc b/chrome/browser/chromeos/android_sms/connection_manager_unittest.cc
index c16bda7..badb971 100644
--- a/chrome/browser/chromeos/android_sms/connection_manager_unittest.cc
+++ b/chrome/browser/chromeos/android_sms/connection_manager_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/chromeos/android_sms/connection_manager.h"
 #include "chrome/browser/chromeos/android_sms/android_sms_urls.h"
 #include "chrome/browser/chromeos/android_sms/fake_connection_establisher.h"
+#include "chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
 #include "content/public/test/fake_service_worker_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -23,14 +24,21 @@
   ~ConnectionManagerTest() override = default;
 
   void SetUp() override {
+    fake_service_worker_context_ =
+        std::make_unique<content::FakeServiceWorkerContext>();
+    fake_multidevice_setup_client_ =
+        std::make_unique<multidevice_setup::FakeMultiDeviceSetupClient>();
+  }
+
+  void SetupConnectionManager(bool initial_feature_state) {
+    SetFeatureState(initial_feature_state);
     std::unique_ptr<FakeConnectionEstablisher> fake_connection_establisher =
         std::make_unique<FakeConnectionEstablisher>();
     fake_connection_establisher_ = fake_connection_establisher.get();
-    fake_service_worker_context_ =
-        std::make_unique<content::FakeServiceWorkerContext>();
     connection_manager_ = std::make_unique<ConnectionManager>(
         fake_service_worker_context_.get(),
-        std::move(fake_connection_establisher));
+        std::move(fake_connection_establisher),
+        fake_multidevice_setup_client_.get());
   }
 
   void VerifyNumEstablishConnectionCalls(size_t count) {
@@ -42,11 +50,20 @@
       EXPECT_EQ(fake_service_worker_context_.get(), service_worker_context);
   }
 
+  void SetFeatureState(bool enable) {
+    fake_multidevice_setup_client_->SetFeatureState(
+        multidevice_setup::mojom::Feature::kMessages,
+        enable ? multidevice_setup::mojom::FeatureState::kEnabledByUser
+               : multidevice_setup::mojom::FeatureState::kDisabledByUser);
+  }
+
   content::FakeServiceWorkerContext* fake_service_worker_context() const {
     return fake_service_worker_context_.get();
   }
 
  private:
+  std::unique_ptr<multidevice_setup::FakeMultiDeviceSetupClient>
+      fake_multidevice_setup_client_;
   FakeConnectionEstablisher* fake_connection_establisher_;
   std::unique_ptr<content::FakeServiceWorkerContext>
       fake_service_worker_context_;
@@ -55,6 +72,7 @@
 };
 
 TEST_F(ConnectionManagerTest, ConnectOnActivate) {
+  SetupConnectionManager(true /* initial_feature_state */);
   fake_service_worker_context()->NotifyObserversOnVersionActivated(
       kDummyVersionId, GetAndroidMessagesURL());
 
@@ -65,6 +83,7 @@
 }
 
 TEST_F(ConnectionManagerTest, ConnectOnNoControllees) {
+  SetupConnectionManager(true /* initial_feature_state */);
   // Notify Activation so that Connection manager is tracking the version ID.
   fake_service_worker_context()->NotifyObserversOnVersionActivated(
       kDummyVersionId, GetAndroidMessagesURL());
@@ -78,6 +97,7 @@
 }
 
 TEST_F(ConnectionManagerTest, IgnoreRedundantVersion) {
+  SetupConnectionManager(true /* initial_feature_state */);
   fake_service_worker_context()->NotifyObserversOnVersionActivated(
       kDummyVersionId, GetAndroidMessagesURL());
 
@@ -94,6 +114,7 @@
 }
 
 TEST_F(ConnectionManagerTest, ConnectOnNoControlleesWithNoActive) {
+  SetupConnectionManager(true /* initial_feature_state */);
   // Verify that connection establishing is attempted when there are no
   // controllees for a version ID even if the activate notification was missed.
   fake_service_worker_context()->NotifyObserversOnNoControllees(
@@ -103,6 +124,7 @@
 }
 
 TEST_F(ConnectionManagerTest, IgnoreOnNoControlleesInvalidId) {
+  SetupConnectionManager(true /* initial_feature_state */);
   fake_service_worker_context()->NotifyObserversOnVersionActivated(
       kDummyVersionId, GetAndroidMessagesURL());
 
@@ -114,6 +136,7 @@
 }
 
 TEST_F(ConnectionManagerTest, InvalidScope) {
+  SetupConnectionManager(true /* initial_feature_state */);
   GURL invalid_scope("https://example.com");
   // Verify that OnVersionActivated and OnNoControllees with invalid scope
   // are ignored
@@ -134,6 +157,39 @@
   VerifyNumEstablishConnectionCalls(3u);
 }
 
+TEST_F(ConnectionManagerTest, FeatureStateInitDisabled) {
+  // Verify that connection is not established on initialization
+  // if the feature is not enabled.
+  SetupConnectionManager(false /* initial_feature_state */);
+  VerifyNumEstablishConnectionCalls(0u);
+
+  SetFeatureState(true);
+  VerifyNumEstablishConnectionCalls(1u);
+}
+
+TEST_F(ConnectionManagerTest, FeatureStateChange) {
+  SetupConnectionManager(true /* initial_feature_state */);
+  fake_service_worker_context()->NotifyObserversOnVersionActivated(
+      kDummyVersionId, GetAndroidMessagesURL());
+
+  // Verify that disabling feature stops the service worker.
+  SetFeatureState(false);
+  VerifyNumEstablishConnectionCalls(2u);
+  const auto& stop_calls = fake_service_worker_context()
+                               ->stop_all_service_workers_for_origin_calls();
+  ASSERT_EQ(1u, stop_calls.size());
+  EXPECT_EQ(GetAndroidMessagesURL(), stop_calls[0]);
+
+  // Verify that subsequent service worker events do not trigger connection.
+  fake_service_worker_context()->NotifyObserversOnNoControllees(
+      kDummyVersionId, GetAndroidMessagesURL());
+  VerifyNumEstablishConnectionCalls(2u);
+
+  // Verify that enabling feature establishes connection again.
+  SetFeatureState(true);
+  VerifyNumEstablishConnectionCalls(3u);
+}
+
 }  // namespace android_sms
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.cc b/chrome/browser/chromeos/crostini/crostini_manager.cc
index 517d6335..3991925 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager.cc
@@ -14,6 +14,7 @@
 #include "base/sys_info.h"
 #include "base/task/post_task.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/crostini/crostini_manager_factory.h"
 #include "chrome/browser/chromeos/crostini/crostini_remover.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/chromeos/file_manager/path_util.h"
@@ -55,95 +56,47 @@
   return chromeos::DBusThreadManager::Get()->GetConciergeClient();
 }
 
-class CrostiniRestarter;
-
-class CrostiniRestarterService : public KeyedService {
- public:
-  CrostiniRestarterService() = default;
-  ~CrostiniRestarterService() override = default;
-
-  CrostiniManager::RestartId Register(
-      Profile* profile,
-      std::string vm_name,
-      std::string container_name,
-      CrostiniManager::RestartCrostiniCallback callback,
-      CrostiniManager::RestartObserver* observer);
-
-  void RunPendingCallbacks(CrostiniRestarter* restarter,
-                           ConciergeClientResult result);
-
-  // Aborts restart_id. A "next" restarter with the same  <vm_name,
-  // container_name> will run, if there is one.
-  void Abort(CrostiniManager::RestartId restart_id);
-
- private:
-  void ErasePending(CrostiniRestarter* restarter);
-
-  std::map<CrostiniManager::RestartId, scoped_refptr<CrostiniRestarter>>
-      restarter_map_;
-
-  // Restarts by <vm_name, container_name>. Only one restarter flow is actually
-  // running. Other restarters will just have their callback called when the
-  // running restarter completes.
-  std::multimap<std::pair<std::string, std::string>, CrostiniManager::RestartId>
-      pending_map_;
-};
-
-class CrostiniRestarterServiceFactory
-    : public BrowserContextKeyedServiceFactory {
- public:
-  static CrostiniRestarterService* GetForProfile(Profile* profile) {
-    return static_cast<CrostiniRestarterService*>(
-        GetInstance()->GetServiceForBrowserContext(profile, true));
+void OnConciergeServiceAvailable(
+    CrostiniManager::StartConciergeCallback callback,
+    bool success) {
+  if (!success) {
+    LOG(ERROR) << "Concierge service did not become available";
+    std::move(callback).Run(success);
+    return;
   }
-  static CrostiniRestarterServiceFactory* GetInstance() {
-    static base::NoDestructor<CrostiniRestarterServiceFactory> factory;
-    return factory.get();
-  }
+  VLOG(1) << "Concierge service announced availability";
+  VLOG(1) << "Waiting for Cicerone to announce availability.";
 
- private:
-  friend class base::NoDestructor<CrostiniRestarterServiceFactory>;
+  GetCiceroneClient()->WaitForServiceToBeAvailable(std::move(callback));
+}
 
-  CrostiniRestarterServiceFactory()
-      : BrowserContextKeyedServiceFactory(
-            "CrostiniRestarterService",
-            BrowserContextDependencyManager::GetInstance()) {}
-  ~CrostiniRestarterServiceFactory() override = default;
+}  // namespace
 
-  // BrowserContextKeyedServiceFactory:
-  KeyedService* BuildServiceInstanceFor(
-      content::BrowserContext* context) const override {
-    return new CrostiniRestarterService();
-  }
-};
-
-class CrostiniRestarter : public base::RefCountedThreadSafe<CrostiniRestarter>,
-                          public chromeos::disks::DiskMountManager::Observer {
+class CrostiniManager::CrostiniRestarter
+    : public base::RefCountedThreadSafe<CrostiniRestarter>,
+      public chromeos::disks::DiskMountManager::Observer {
  public:
-  CrostiniRestarter(CrostiniRestarterService* restarter_service,
-                    Profile* profile,
+  CrostiniRestarter(Profile* profile,
+                    base::WeakPtr<CrostiniManager> crostini_manager,
                     std::string vm_name,
-                    std::string cryptohome_id,
                     std::string container_name,
                     std::string container_username,
                     CrostiniManager::RestartCrostiniCallback callback)
       : profile_(profile),
+        crostini_manager_(crostini_manager),
         vm_name_(std::move(vm_name)),
-        cryptohome_id_(std::move(cryptohome_id)),
         container_name_(std::move(container_name)),
         container_username_(std::move(container_username)),
         callback_(std::move(callback)),
-        restart_id_(next_restart_id_++),
-        restarter_service_(restarter_service) {}
+        restart_id_(next_restart_id_++) {}
 
   void Restart() {
     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
     if (is_aborted_)
       return;
-
-    CrostiniManager* crostini_manager = CrostiniManager::GetInstance();
+    is_running_ = true;
     // Skip to the end immediately if testing.
-    if (crostini_manager->skip_restart_for_testing()) {
+    if (crostini_manager_->skip_restart_for_testing()) {
       content::BrowserThread::PostTask(
           content::BrowserThread::UI, FROM_HERE,
           base::BindOnce(&CrostiniRestarter::SetUpLxdContainerUserFinished,
@@ -152,7 +105,7 @@
       return;
     }
 
-    crostini_manager->InstallTerminaComponent(base::BindOnce(
+    crostini_manager_->InstallTerminaComponent(base::BindOnce(
         &CrostiniRestarter::LoadComponentFinished, base::WrapRefCounted(this)));
   }
 
@@ -169,6 +122,16 @@
     observer_list_.Clear();
   }
 
+  void OnContainerDownloading(int download_percent) {
+    if (!is_running_) {
+      return;
+    }
+    // Tell observers.
+    for (auto& observer : observer_list_) {
+      observer.OnContainerDownloading(download_percent);
+    }
+  }
+
   CrostiniManager::RestartId restart_id() const { return restart_id_; }
   std::string vm_name() const { return vm_name_; }
   std::string container_name() const { return container_name_; }
@@ -183,7 +146,7 @@
   }
 
   void FinishRestart(ConciergeClientResult result) {
-    restarter_service_->RunPendingCallbacks(this, result);
+    crostini_manager_->FinishRestart(this, result);
   }
 
   void LoadComponentFinished(bool is_successful) {
@@ -200,7 +163,7 @@
       FinishRestart(client_result);
       return;
     }
-    CrostiniManager::GetInstance()->StartConcierge(
+    crostini_manager_->StartConcierge(
         base::BindOnce(&CrostiniRestarter::ConciergeStarted, this));
   }
 
@@ -220,8 +183,8 @@
       FinishRestart(client_result);
       return;
     }
-    CrostiniManager::GetInstance()->CreateDiskImage(
-        cryptohome_id_, base::FilePath(vm_name_),
+    crostini_manager_->CreateDiskImage(
+        base::FilePath(vm_name_),
         vm_tools::concierge::StorageLocation::STORAGE_CRYPTOHOME_ROOT,
         base::BindOnce(&CrostiniRestarter::CreateDiskImageFinished, this));
   }
@@ -240,8 +203,8 @@
       FinishRestart(result);
       return;
     }
-    CrostiniManager::GetInstance()->StartTerminaVm(
-        cryptohome_id_, vm_name_, result_path,
+    crostini_manager_->StartTerminaVm(
+        vm_name_, result_path,
         base::BindOnce(&CrostiniRestarter::StartTerminaVmFinished, this));
   }
 
@@ -258,14 +221,17 @@
       FinishRestart(result);
       return;
     }
-    CrostiniManager::GetInstance()->CreateLxdContainer(
-        vm_name_, container_name_, cryptohome_id_,
+    crostini_manager_->CreateLxdContainer(
+        vm_name_, container_name_,
         base::BindOnce(&CrostiniRestarter::CreateLxdContainerFinished, this));
   }
 
   void CreateLxdContainerFinished(ConciergeClientResult result) {
     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-    // TODO(timloh): Does this need an observer callback?
+    // Tell observers.
+    for (auto& observer : observer_list_) {
+      observer.OnContainerCreated(result);
+    }
     if (is_aborted_)
       return;
     if (result != ConciergeClientResult::SUCCESS) {
@@ -273,8 +239,8 @@
       FinishRestart(result);
       return;
     }
-    CrostiniManager::GetInstance()->StartLxdContainer(
-        vm_name_, container_name_, cryptohome_id_,
+    crostini_manager_->StartLxdContainer(
+        vm_name_, container_name_,
         base::BindOnce(&CrostiniRestarter::StartLxdContainerFinished, this));
   }
 
@@ -288,8 +254,8 @@
       FinishRestart(result);
       return;
     }
-    CrostiniManager::GetInstance()->SetUpLxdContainerUser(
-        vm_name_, container_name_, cryptohome_id_, container_username_,
+    crostini_manager_->SetUpLxdContainerUser(
+        vm_name_, container_name_, container_username_,
         base::BindOnce(&CrostiniRestarter::SetUpLxdContainerUserFinished,
                        this));
   }
@@ -311,8 +277,8 @@
     // If default termina/penguin, then do sshfs mount, else we are finished.
     if (vm_name_ == kCrostiniDefaultVmName &&
         container_name_ == kCrostiniDefaultContainerName) {
-      CrostiniManager::GetInstance()->GetContainerSshKeys(
-          vm_name_, container_name_, cryptohome_id_,
+      crostini_manager_->GetContainerSshKeys(
+          vm_name_, container_name_,
           base::BindOnce(&CrostiniRestarter::GetContainerSshKeysFinished,
                          this));
     } else {
@@ -394,8 +360,11 @@
   }
 
   Profile* profile_;
+  // This isn't accessed after the CrostiniManager is destroyed, but we still
+  // use a WeakPtr for safety.
+  base::WeakPtr<CrostiniManager> crostini_manager_;
+
   std::string vm_name_;
-  std::string cryptohome_id_;
   std::string container_name_;
   std::string container_username_;
   std::string source_path_;
@@ -403,148 +372,56 @@
   base::ObserverList<CrostiniManager::RestartObserver>::Unchecked
       observer_list_;
   CrostiniManager::RestartId restart_id_;
-  CrostiniRestarterService* restarter_service_;
   bool is_aborted_ = false;
+  bool is_running_ = false;
 
   static CrostiniManager::RestartId next_restart_id_;
 };
 
-CrostiniManager::RestartId CrostiniRestarter::next_restart_id_ = 0;
+CrostiniManager::RestartId
+    CrostiniManager::CrostiniRestarter::next_restart_id_ = 0;
 
-CrostiniManager::RestartId CrostiniRestarterService::Register(
-    Profile* profile,
-    std::string vm_name,
-    std::string container_name,
-    CrostiniManager::RestartCrostiniCallback callback,
-    CrostiniManager::RestartObserver* observer) {
-  auto restarter = base::MakeRefCounted<CrostiniRestarter>(
-      this, profile, std::move(vm_name), CryptohomeIdForProfile(profile),
-      std::move(container_name), ContainerUserNameForProfile(profile),
-      std::move(callback));
-  if (observer)
-    restarter->AddObserver(observer);
-  auto key = std::make_pair(restarter->vm_name(), restarter->container_name());
-  pending_map_.emplace(key, restarter->restart_id());
-  restarter_map_[restarter->restart_id()] = restarter;
-  if (pending_map_.count(key) > 1) {
-    VLOG(1) << "Already restarting vm " << vm_name << ", container "
-            << container_name;
-  } else {
-    restarter->Restart();
-  }
-  return restarter->restart_id();
-}
-
-void CrostiniRestarterService::RunPendingCallbacks(
-    CrostiniRestarter* restarter,
-    ConciergeClientResult result) {
-  auto key = std::make_pair(restarter->vm_name(), restarter->container_name());
-  auto range = pending_map_.equal_range(key);
-  std::vector<scoped_refptr<CrostiniRestarter>> pending_restarters;
-  // Erase first, because restarter->RunCallback() may modify our maps.
-  for (auto it = range.first; it != range.second; ++it) {
-    CrostiniManager::RestartId restart_id = it->second;
-    pending_restarters.emplace_back(restarter_map_[restart_id]);
-    restarter_map_.erase(restart_id);
-  }
-  pending_map_.erase(range.first, range.second);
-  for (const auto& pending_restarter : pending_restarters) {
-    pending_restarter->RunCallback(result);
-  }
-}
-
-void CrostiniRestarterService::Abort(CrostiniManager::RestartId restart_id) {
-  auto it = restarter_map_.find(restart_id);
-  if (it == restarter_map_.end()) {
-    // This can happen if a user cancels the install flow at the exact right
-    // moment, for example.
-    LOG(ERROR) << "Aborting a restarter that already finished";
+void CrostiniManager::SetVmState(std::string vm_name, VmState vm_state) {
+  auto vm_info = running_vms_.find(std::move(vm_name));
+  if (vm_info != running_vms_.end()) {
+    vm_info->second.first = vm_state;
     return;
   }
-  it->second->Abort();
-  ErasePending(it->second.get());
-  // Erasing |it| also invalidates |it|, so make a key from |it| now.
-  auto key =
-      std::make_pair(it->second->vm_name(), it->second->container_name());
-  restarter_map_.erase(it);
-  // Kick off the "next" (in no order) pending Restart() if any.
-  auto pending_it = pending_map_.find(key);
-  if (pending_it != pending_map_.end()) {
-    auto restarter = restarter_map_[pending_it->second];
-    restarter->Restart();
+  // This can happen normally when StopVm is called right after start up.
+  LOG(WARNING) << "Attempted to set state for unknown vm: " << vm_name;
+}
+
+bool CrostiniManager::IsVmRunning(std::string vm_name) {
+  auto vm_info = running_vms_.find(std::move(vm_name));
+  if (vm_info != running_vms_.end()) {
+    return vm_info->second.first == VmState::STARTED;
   }
-}
-
-void CrostiniRestarterService::ErasePending(CrostiniRestarter* restarter) {
-  // Erase from pending_map_
-  auto key = std::make_pair(restarter->vm_name(), restarter->container_name());
-  auto range = pending_map_.equal_range(key);
-  for (auto it = range.first; it != range.second; ++it) {
-    if (it->second == restarter->restart_id()) {
-      pending_map_.erase(it);
-      return;
-    }
-  }
-  NOTREACHED();
-}
-
-void OnConciergeServiceAvailable(
-    CrostiniManager::StartConciergeCallback callback,
-    bool success) {
-  if (!success) {
-    LOG(ERROR) << "Concierge service did not become available";
-    std::move(callback).Run(success);
-    return;
-  }
-  VLOG(1) << "Concierge service announced availability";
-  VLOG(1) << "Waiting for Cicerone to announce availability.";
-
-  GetCiceroneClient()->WaitForServiceToBeAvailable(std::move(callback));
-}
-
-}  // namespace
-
-// static
-CrostiniManager* CrostiniManager::GetInstance() {
-  return base::Singleton<CrostiniManager>::get();
-}
-
-bool CrostiniManager::IsVmRunning(Profile* profile, std::string vm_name) {
-  return running_vms_.find(std::make_pair(CryptohomeIdForProfile(profile),
-                                          std::move(vm_name))) !=
-         running_vms_.end();
+  return false;
 }
 
 base::Optional<vm_tools::concierge::VmInfo> CrostiniManager::GetVmInfo(
-    Profile* profile,
     std::string vm_name) {
-  auto it = running_vms_.find(
-      std::make_pair(CryptohomeIdForProfile(profile), std::move(vm_name)));
+  auto it = running_vms_.find(std::move(vm_name));
   if (it != running_vms_.end())
-    return it->second;
+    return it->second.second;
   return base::nullopt;
 }
 
 void CrostiniManager::AddRunningVmForTesting(
-    std::string owner_id,
     std::string vm_name,
     vm_tools::concierge::VmInfo vm_info) {
-  auto key = std::make_pair(std::move(owner_id), std::move(vm_name));
-  running_vms_[key] = std::move(vm_info);
+  running_vms_[std::move(vm_name)] =
+      std::make_pair(VmState::STARTED, std::move(vm_info));
 }
 
-bool CrostiniManager::IsContainerRunning(Profile* profile,
-                                         std::string vm_name,
+bool CrostiniManager::IsContainerRunning(std::string vm_name,
                                          std::string container_name) {
-  return IsContainerRunning(CryptohomeIdForProfile(profile), std::move(vm_name),
-                            std::move(container_name));
-}
-
-bool CrostiniManager::IsContainerRunning(std::string owner_id,
-                                         std::string vm_name,
-                                         std::string container_name) {
-  auto range = running_containers_.equal_range(
-      std::make_pair(std::move(owner_id), std::move(vm_name)));
+  if (!IsVmRunning(vm_name)) {
+    return false;
+  }
+  // TODO(jopra): Ensure the container not marked running if the vm is not
+  // running.
+  auto range = running_containers_.equal_range(std::move(vm_name));
   for (auto it = range.first; it != range.second; ++it) {
     if (it->second == container_name) {
       return true;
@@ -553,27 +430,30 @@
   return false;
 }
 
-void CrostiniManager::ResetForTesting() {
-  running_vms_.clear();
-  running_containers_.clear();
+CrostiniManager* CrostiniManager::GetForProfile(Profile* profile) {
+  return CrostiniManagerFactory::GetForProfile(profile);
 }
 
-CrostiniManager::CrostiniManager() : weak_ptr_factory_(this) {
-  // Cicerone/ConciergeClient and its observer_list_ will be destroyed together.
-  // We add, but don't need to remove the observer. (Doing so would force a
-  // "destroyed before" dependency on the owner of Cicerone/ConciergeClient).
+CrostiniManager::CrostiniManager(Profile* profile)
+    : profile_(profile),
+      owner_id_(CryptohomeIdForProfile(profile)),
+      weak_ptr_factory_(this) {
+  DCHECK(!profile_->IsOffTheRecord());
   GetCiceroneClient()->AddObserver(this);
   GetConciergeClient()->AddObserver(this);
 }
 
-CrostiniManager::~CrostiniManager() {}
+CrostiniManager::~CrostiniManager() {
+  GetCiceroneClient()->RemoveObserver(this);
+  GetConciergeClient()->RemoveObserver(this);
+}
 
 bool CrostiniManager::IsCrosTerminaInstalled() const {
   return is_cros_termina_registered_;
 }
 
-void CrostiniManager::MaybeUpgradeCrostini(Profile* profile) {
-  if (!IsCrostiniAllowedForProfile(profile)) {
+void CrostiniManager::MaybeUpgradeCrostini() {
+  if (!IsCrostiniAllowedForProfile(profile_)) {
     return;
   }
   auto* component_manager =
@@ -725,17 +605,9 @@
 }
 
 void CrostiniManager::CreateDiskImage(
-    const std::string& cryptohome_id,
     const base::FilePath& disk_path,
     vm_tools::concierge::StorageLocation storage_location,
     CreateDiskImageCallback callback) {
-  if (cryptohome_id.empty()) {
-    LOG(ERROR) << "Cryptohome id cannot be empty";
-    std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR,
-                            base::FilePath());
-    return;
-  }
-
   std::string disk_path_string = disk_path.AsUTF8Unsafe();
   if (disk_path_string.empty()) {
     LOG(ERROR) << "Disk path cannot be empty";
@@ -745,7 +617,7 @@
   }
 
   vm_tools::concierge::CreateDiskImageRequest request;
-  request.set_cryptohome_id(std::move(cryptohome_id));
+  request.set_cryptohome_id(CryptohomeIdForProfile(profile_));
   request.set_disk_path(std::move(disk_path_string));
   // The type of disk image to be created.
   request.set_image_type(vm_tools::concierge::DISK_IMAGE_QCOW2);
@@ -793,16 +665,9 @@
 }
 
 void CrostiniManager::DestroyDiskImage(
-    const std::string& cryptohome_id,
     const base::FilePath& disk_path,
     vm_tools::concierge::StorageLocation storage_location,
     DestroyDiskImageCallback callback) {
-  if (cryptohome_id.empty()) {
-    LOG(ERROR) << "Cryptohome id cannot be empty";
-    std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
-    return;
-  }
-
   std::string disk_path_string = disk_path.AsUTF8Unsafe();
   if (disk_path_string.empty()) {
     LOG(ERROR) << "Disk path cannot be empty";
@@ -811,7 +676,7 @@
   }
 
   vm_tools::concierge::DestroyDiskImageRequest request;
-  request.set_cryptohome_id(std::move(cryptohome_id));
+  request.set_cryptohome_id(CryptohomeIdForProfile(profile_));
   request.set_disk_path(std::move(disk_path_string));
 
   if (storage_location != vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT &&
@@ -829,18 +694,9 @@
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
-void CrostiniManager::ListVmDisks(
-    // The cryptohome id for the user's encrypted storage.
-    const std::string& cryptohome_id,
-    ListVmDisksCallback callback) {
-  if (cryptohome_id.empty()) {
-    LOG(ERROR) << "Cryptohome id cannot be empty";
-    std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR, 0);
-    return;
-  }
-
+void CrostiniManager::ListVmDisks(ListVmDisksCallback callback) {
   vm_tools::concierge::ListVmDisksRequest request;
-  request.set_cryptohome_id(std::move(cryptohome_id));
+  request.set_cryptohome_id(CryptohomeIdForProfile(profile_));
   request.set_storage_location(vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT);
 
   GetConciergeClient()->ListVmDisks(
@@ -849,16 +705,9 @@
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
-void CrostiniManager::StartTerminaVm(std::string owner_id,
-                                     std::string name,
+void CrostiniManager::StartTerminaVm(std::string name,
                                      const base::FilePath& disk_path,
                                      StartTerminaVmCallback callback) {
-  if (owner_id.empty()) {
-    LOG(ERROR) << "owner_id is required";
-    std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
-    return;
-  }
-
   if (name.empty()) {
     LOG(ERROR) << "name is required";
     std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
@@ -875,7 +724,7 @@
   vm_tools::concierge::StartVmRequest request;
   request.set_name(std::move(name));
   request.set_start_termina(true);
-  request.set_owner_id(std::move(owner_id));
+  request.set_owner_id(owner_id_);
 
   vm_tools::concierge::DiskImage* disk_image = request.add_disks();
   disk_image->set_path(std::move(disk_path_string));
@@ -884,37 +733,32 @@
   disk_image->set_do_mount(false);
 
   GetConciergeClient()->StartTerminaVm(
-      request,
-      base::BindOnce(&CrostiniManager::OnStartTerminaVm,
-                     weak_ptr_factory_.GetWeakPtr(), request.owner_id(),
-                     request.name(), std::move(callback)));
+      request, base::BindOnce(&CrostiniManager::OnStartTerminaVm,
+                              weak_ptr_factory_.GetWeakPtr(), request.name(),
+                              std::move(callback)));
 }
 
-void CrostiniManager::StopVm(Profile* profile,
-                             std::string name,
-                             StopVmCallback callback) {
+void CrostiniManager::StopVm(std::string name, StopVmCallback callback) {
   if (name.empty()) {
     LOG(ERROR) << "name is required";
     std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
     return;
   }
 
-  std::string owner_id = CryptohomeIdForProfile(profile);
+  SetVmState(name, VmState::STOPPING);
 
   vm_tools::concierge::StopVmRequest request;
-  request.set_owner_id(owner_id);
+  request.set_owner_id(owner_id_);
   request.set_name(name);
 
   GetConciergeClient()->StopVm(
       std::move(request),
       base::BindOnce(&CrostiniManager::OnStopVm, weak_ptr_factory_.GetWeakPtr(),
-                     std::move(owner_id), std::move(name),
-                     std::move(callback)));
+                     std::move(name), std::move(callback)));
 }
 
 void CrostiniManager::CreateLxdContainer(std::string vm_name,
                                          std::string container_name,
-                                         std::string owner_id,
                                          CrostiniResultCallback callback) {
   if (vm_name.empty()) {
     LOG(ERROR) << "vm_name is required";
@@ -926,11 +770,6 @@
     std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
     return;
   }
-  if (owner_id.empty()) {
-    LOG(ERROR) << "cryptohome_id is required";
-    std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
-    return;
-  }
   if (!GetCiceroneClient()->IsLxdContainerCreatedSignalConnected() ||
       !GetCiceroneClient()->IsLxdContainerDownloadingSignalConnected()) {
     LOG(ERROR)
@@ -942,20 +781,18 @@
   vm_tools::cicerone::CreateLxdContainerRequest request;
   request.set_vm_name(std::move(vm_name));
   request.set_container_name(std::move(container_name));
-  request.set_owner_id(std::move(owner_id));
+  request.set_owner_id(owner_id_);
   request.set_image_server(kCrostiniDefaultImageServerUrl);
   request.set_image_alias(kCrostiniDefaultImageAlias);
   GetCiceroneClient()->CreateLxdContainer(
       std::move(request),
       base::BindOnce(&CrostiniManager::OnCreateLxdContainer,
-                     weak_ptr_factory_.GetWeakPtr(), request.owner_id(),
-                     request.vm_name(), request.container_name(),
-                     std::move(callback)));
+                     weak_ptr_factory_.GetWeakPtr(), request.vm_name(),
+                     request.container_name(), std::move(callback)));
 }
 
 void CrostiniManager::StartLxdContainer(std::string vm_name,
                                         std::string container_name,
-                                        std::string owner_id,
                                         CrostiniResultCallback callback) {
   if (vm_name.empty()) {
     LOG(ERROR) << "vm_name is required";
@@ -967,11 +804,6 @@
     std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
     return;
   }
-  if (owner_id.empty()) {
-    LOG(ERROR) << "cryptohome_id is required";
-    std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
-    return;
-  }
   if (!GetCiceroneClient()->IsContainerStartedSignalConnected() ||
       !GetCiceroneClient()->IsContainerShutdownSignalConnected()) {
     LOG(ERROR) << "Async call to StartLxdContainer can't complete when signals "
@@ -982,18 +814,16 @@
   vm_tools::cicerone::StartLxdContainerRequest request;
   request.set_vm_name(std::move(vm_name));
   request.set_container_name(std::move(container_name));
-  request.set_owner_id(std::move(owner_id));
+  request.set_owner_id(owner_id_);
   GetCiceroneClient()->StartLxdContainer(
       std::move(request),
       base::BindOnce(&CrostiniManager::OnStartLxdContainer,
-                     weak_ptr_factory_.GetWeakPtr(), request.owner_id(),
-                     request.vm_name(), request.container_name(),
-                     std::move(callback)));
+                     weak_ptr_factory_.GetWeakPtr(), request.vm_name(),
+                     request.container_name(), std::move(callback)));
 }
 
 void CrostiniManager::SetUpLxdContainerUser(std::string vm_name,
                                             std::string container_name,
-                                            std::string owner_id,
                                             std::string container_username,
                                             CrostiniResultCallback callback) {
   if (vm_name.empty()) {
@@ -1006,11 +836,6 @@
     std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
     return;
   }
-  if (owner_id.empty()) {
-    LOG(ERROR) << "cryptohome_id is required";
-    std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
-    return;
-  }
   if (container_username.empty()) {
     LOG(ERROR) << "container_username is required";
     std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
@@ -1019,25 +844,23 @@
   vm_tools::cicerone::SetUpLxdContainerUserRequest request;
   request.set_vm_name(std::move(vm_name));
   request.set_container_name(std::move(container_name));
-  request.set_owner_id(std::move(owner_id));
+  request.set_owner_id(owner_id_);
   request.set_container_username(std::move(container_username));
   GetCiceroneClient()->SetUpLxdContainerUser(
       std::move(request),
       base::BindOnce(&CrostiniManager::OnSetUpLxdContainerUser,
-                     weak_ptr_factory_.GetWeakPtr(), request.owner_id(),
-                     request.vm_name(), request.container_name(),
-                     std::move(callback)));
+                     weak_ptr_factory_.GetWeakPtr(), request.vm_name(),
+                     request.container_name(), std::move(callback)));
 }
 
 void CrostiniManager::LaunchContainerApplication(
-    Profile* profile,
     std::string vm_name,
     std::string container_name,
     std::string desktop_file_id,
     const std::vector<std::string>& files,
     LaunchContainerApplicationCallback callback) {
   vm_tools::cicerone::LaunchContainerApplicationRequest request;
-  request.set_owner_id(CryptohomeIdForProfile(profile));
+  request.set_owner_id(owner_id_);
   request.set_vm_name(std::move(vm_name));
   request.set_container_name(std::move(container_name));
   request.set_desktop_file_id(std::move(desktop_file_id));
@@ -1052,7 +875,6 @@
 }
 
 void CrostiniManager::GetContainerAppIcons(
-    Profile* profile,
     std::string vm_name,
     std::string container_name,
     std::vector<std::string> desktop_file_ids,
@@ -1060,7 +882,7 @@
     int scale,
     GetContainerAppIconsCallback callback) {
   vm_tools::cicerone::ContainerAppIconRequest request;
-  request.set_owner_id(CryptohomeIdForProfile(profile));
+  request.set_owner_id(owner_id_);
   request.set_vm_name(std::move(vm_name));
   request.set_container_name(std::move(container_name));
   google::protobuf::RepeatedPtrField<std::string> ids(
@@ -1077,7 +899,6 @@
 }
 
 void CrostiniManager::InstallLinuxPackage(
-    Profile* profile,
     std::string vm_name,
     std::string container_name,
     std::string package_path,
@@ -1093,7 +914,7 @@
   }
 
   vm_tools::cicerone::InstallLinuxPackageRequest request;
-  request.set_owner_id(CryptohomeIdForProfile(profile));
+  request.set_owner_id(owner_id_);
   request.set_vm_name(std::move(vm_name));
   request.set_container_name(std::move(container_name));
   request.set_file_path(std::move(package_path));
@@ -1107,12 +928,11 @@
 void CrostiniManager::GetContainerSshKeys(
     std::string vm_name,
     std::string container_name,
-    std::string cryptohome_id,
     GetContainerSshKeysCallback callback) {
   vm_tools::concierge::ContainerSshKeysRequest request;
   request.set_vm_name(std::move(vm_name));
   request.set_container_name(std::move(container_name));
-  request.set_cryptohome_id(std::move(cryptohome_id));
+  request.set_cryptohome_id(CryptohomeIdForProfile(profile_));
 
   GetConciergeClient()->GetContainerSshKeys(
       std::move(request),
@@ -1169,12 +989,14 @@
   return launch_params;
 }
 
+// static
 Browser* CrostiniManager::CreateContainerTerminal(
     const AppLaunchParams& launch_params,
     const GURL& vsh_in_crosh_url) {
   return CreateApplicationWindow(launch_params, vsh_in_crosh_url);
 }
 
+// static
 void CrostiniManager::ShowContainerTerminal(
     const AppLaunchParams& launch_params,
     const GURL& vsh_in_crosh_url,
@@ -1183,65 +1005,89 @@
 }
 
 void CrostiniManager::LaunchContainerTerminal(
-    Profile* profile,
     const std::string& vm_name,
     const std::string& container_name,
     const std::vector<std::string>& terminal_args) {
   GURL vsh_in_crosh_url =
-      GenerateVshInCroshUrl(profile, vm_name, container_name, terminal_args);
-  AppLaunchParams launch_params = GenerateTerminalAppLaunchParams(profile);
+      GenerateVshInCroshUrl(profile_, vm_name, container_name, terminal_args);
+  AppLaunchParams launch_params = GenerateTerminalAppLaunchParams(profile_);
   OpenApplicationWindow(launch_params, vsh_in_crosh_url);
 }
 
 CrostiniManager::RestartId CrostiniManager::RestartCrostini(
-    Profile* profile,
     std::string vm_name,
     std::string container_name,
     RestartCrostiniCallback callback,
     RestartObserver* observer) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK(!profile->IsOffTheRecord());
-  return CrostiniRestarterServiceFactory::GetForProfile(profile)->Register(
-      profile, std::move(vm_name), std::move(container_name),
-      std::move(callback), observer);
+
+  auto restarter = base::MakeRefCounted<CrostiniRestarter>(
+      profile_, weak_ptr_factory_.GetWeakPtr(), std::move(vm_name),
+      std::move(container_name), ContainerUserNameForProfile(profile_),
+      std::move(callback));
+  if (observer)
+    restarter->AddObserver(observer);
+  auto key = std::make_pair(restarter->vm_name(), restarter->container_name());
+  restarters_by_container_.emplace(key, restarter->restart_id());
+  restarters_by_id_[restarter->restart_id()] = restarter;
+  if (restarters_by_container_.count(key) > 1) {
+    VLOG(1) << "Already restarting vm " << vm_name << ", container "
+            << container_name;
+  } else {
+    restarter->Restart();
+  }
+  return restarter->restart_id();
 }
 
 void CrostiniManager::AbortRestartCrostini(
-    Profile* profile,
     CrostiniManager::RestartId restart_id) {
-  CrostiniRestarterServiceFactory::GetForProfile(profile)->Abort(restart_id);
+  auto restarter_it = restarters_by_id_.find(restart_id);
+  if (restarter_it == restarters_by_id_.end()) {
+    // This can happen if a user cancels the install flow at the exact right
+    // moment, for example.
+    LOG(ERROR) << "Aborting a restarter that already finished";
+    return;
+  }
+  restarter_it->second->Abort();
+
+  auto key = std::make_pair(restarter_it->second->vm_name(),
+                            restarter_it->second->container_name());
+  auto range = restarters_by_container_.equal_range(key);
+  for (auto it = range.first; it != range.second; ++it) {
+    if (it->second == restart_id) {
+      restarters_by_container_.erase(it);
+      break;
+    }
+  }
+
+  // This invalidates the iterator and potentially destroys the restarter, so
+  // those shouldn't be accessed after this.
+  restarters_by_id_.erase(restarter_it);
+
+  // Kick off the "next" (in no order) pending Restart() if any.
+  auto pending_it = restarters_by_container_.find(key);
+  if (pending_it != restarters_by_container_.end()) {
+    auto restarter = restarters_by_id_[pending_it->second];
+    restarter->Restart();
+  }
 }
 
 void CrostiniManager::AddShutdownContainerCallback(
-    Profile* profile,
     std::string vm_name,
     std::string container_name,
     ShutdownContainerCallback shutdown_callback) {
   shutdown_container_callbacks_.emplace(
-      std::make_tuple(CryptohomeIdForProfile(profile), vm_name, container_name),
-      std::move(shutdown_callback));
+      std::make_tuple(vm_name, container_name), std::move(shutdown_callback));
 }
 
 void CrostiniManager::AddInstallLinuxPackageProgressObserver(
-    Profile* profile,
     InstallLinuxPackageProgressObserver* observer) {
-  install_linux_package_progress_observers_.emplace(
-      CryptohomeIdForProfile(profile), observer);
+  install_linux_package_progress_observers_.AddObserver(observer);
 }
 
 void CrostiniManager::RemoveInstallLinuxPackageProgressObserver(
-    Profile* profile,
     InstallLinuxPackageProgressObserver* observer) {
-  auto range = install_linux_package_progress_observers_.equal_range(
-      CryptohomeIdForProfile(profile));
-  for (auto it = range.first; it != range.second; ++it) {
-    if (it->second == observer) {
-      install_linux_package_progress_observers_.erase(it);
-      return;
-    }
-  }
-
-  NOTREACHED();
+  install_linux_package_progress_observers_.RemoveObserver(observer);
 }
 
 void CrostiniManager::OnCreateDiskImage(
@@ -1309,7 +1155,6 @@
 }
 
 void CrostiniManager::OnStartTerminaVm(
-    std::string owner_id,
     std::string vm_name,
     StartTerminaVmCallback callback,
     base::Optional<vm_tools::concierge::StartVmResponse> reply) {
@@ -1325,38 +1170,40 @@
     std::move(callback).Run(ConciergeClientResult::VM_START_FAILED);
     return;
   }
-  // Wait for the Tremplin signal if the vm isn't already marked "running".
-  auto key = std::make_pair(owner_id, vm_name);
-  if (running_vms_.find(key) == running_vms_.end()) {
-    VLOG(1) << "Awaiting TremplinStartedSignal for " << owner_id << ", "
-            << vm_name;
 
-    // Record the container start and run the callback after the VM starts.
-    tremplin_started_callbacks_.emplace(
-        key, base::BindOnce(&CrostiniManager::OnStartTremplin,
-                            weak_ptr_factory_.GetWeakPtr(), key,
-                            std::move(response.vm_info()), std::move(callback),
-                            ConciergeClientResult::SUCCESS));
+  // If the vm is already marked "running" run the callback.
+  if (IsVmRunning(vm_name)) {
+    std::move(callback).Run(ConciergeClientResult::SUCCESS);
     return;
   }
-  std::move(callback).Run(ConciergeClientResult::SUCCESS);
+
+  // Otherwise, record the container start and run the callback after the VM
+  // starts.
+  VLOG(1) << "Awaiting TremplinStartedSignal for " << owner_id_ << ", "
+          << vm_name;
+  running_vms_[vm_name] =
+      std::make_pair(VmState::STARTING, std::move(response.vm_info()));
+
+  tremplin_started_callbacks_.emplace(
+      vm_name,
+      base::BindOnce(&CrostiniManager::OnStartTremplin,
+                     weak_ptr_factory_.GetWeakPtr(), vm_name,
+                     std::move(callback), ConciergeClientResult::SUCCESS));
 }
 
-void CrostiniManager::OnStartTremplin(std::pair<std::string, std::string> key,
-                                      vm_tools::concierge::VmInfo vm_info,
+void CrostiniManager::OnStartTremplin(std::string vm_name,
                                       StartTerminaVmCallback callback,
                                       ConciergeClientResult result) {
   // Record the running vm.
-  VLOG(1) << "Received TremplinStartedSignal, VM: " << key.first << ", "
-          << key.second;
-  running_vms_[key] = std::move(vm_info);
+  VLOG(1) << "Received TremplinStartedSignal, VM: " << owner_id_ << ", "
+          << vm_name;
+  SetVmState(vm_name, VmState::STARTED);
 
   // Run the original callback.
   std::move(callback).Run(result);
 }
 
 void CrostiniManager::OnStopVm(
-    std::string owner_id,
     std::string vm_name,
     StopVmCallback callback,
     base::Optional<vm_tools::concierge::StopVmResponse> reply) {
@@ -1381,32 +1228,33 @@
     }
   }
   // Remove from running_vms_.
-  auto key = std::make_pair(std::move(owner_id), std::move(vm_name));
-  running_vms_.erase(key);
+  running_vms_.erase(vm_name);
   // Remove containers from running_containers_
-  running_containers_.erase(key);
+  running_containers_.erase(std::move(vm_name));
   std::move(callback).Run(ConciergeClientResult::SUCCESS);
 }
 
 void CrostiniManager::OnContainerStarted(
     const vm_tools::cicerone::ContainerStartedSignal& signal) {
+  if (signal.owner_id() != owner_id_)
+    return;
   // Find the callbacks to call, then erase them from the map.
-  auto range = start_container_callbacks_.equal_range(std::make_tuple(
-      signal.owner_id(), signal.vm_name(), signal.container_name()));
+  auto range = start_container_callbacks_.equal_range(
+      std::make_tuple(signal.vm_name(), signal.container_name()));
   for (auto it = range.first; it != range.second; ++it) {
     std::move(it->second).Run(ConciergeClientResult::SUCCESS);
   }
   start_container_callbacks_.erase(range.first, range.second);
-  running_containers_.emplace(
-      std::make_pair(signal.owner_id(), signal.vm_name()),
-      signal.container_name());
+  running_containers_.emplace(signal.vm_name(), signal.container_name());
 }
 
 void CrostiniManager::OnContainerStartupFailed(
     const vm_tools::concierge::ContainerStartedSignal& signal) {
+  if (signal.owner_id() != owner_id_)
+    return;
   // Find the callbacks to call, then erase them from the map.
-  auto range = start_container_callbacks_.equal_range(std::make_tuple(
-      signal.owner_id(), signal.vm_name(), signal.container_name()));
+  auto range = start_container_callbacks_.equal_range(
+      std::make_tuple(signal.vm_name(), signal.container_name()));
   for (auto it = range.first; it != range.second; ++it) {
     std::move(it->second).Run(ConciergeClientResult::CONTAINER_START_FAILED);
   }
@@ -1415,9 +1263,11 @@
 
 void CrostiniManager::OnContainerShutdown(
     const vm_tools::cicerone::ContainerShutdownSignal& signal) {
+  if (signal.owner_id() != owner_id_)
+    return;
   // Find the callbacks to call, then erase them from the map.
-  auto range = shutdown_container_callbacks_.equal_range(std::make_tuple(
-      signal.owner_id(), signal.vm_name(), signal.container_name()));
+  auto range = shutdown_container_callbacks_.equal_range(
+      std::make_tuple(signal.vm_name(), signal.container_name()));
   for (auto it = range.first; it != range.second; ++it) {
     std::move(it->second).Run();
   }
@@ -1426,6 +1276,8 @@
 
 void CrostiniManager::OnInstallLinuxPackageProgress(
     const vm_tools::cicerone::InstallLinuxPackageProgressSignal& signal) {
+  if (signal.owner_id() != owner_id_)
+    return;
   if (signal.progress_percent() < 0 || signal.progress_percent() > 100) {
     LOG(ERROR) << "Received install progress with invalid progress of "
                << signal.progress_percent() << "%.";
@@ -1450,17 +1302,14 @@
       NOTREACHED();
   }
 
-  auto range =
-      install_linux_package_progress_observers_.equal_range(signal.owner_id());
-  for (auto it = range.first; it != range.second; ++it) {
-    it->second->OnInstallLinuxPackageProgress(
+  for (auto& observer : install_linux_package_progress_observers_) {
+    observer.OnInstallLinuxPackageProgress(
         signal.vm_name(), signal.container_name(), status,
         signal.progress_percent(), signal.failure_details());
   }
 }
 
 void CrostiniManager::OnCreateLxdContainer(
-    std::string owner_id,
     std::string vm_name,
     std::string container_name,
     CrostiniResultCallback callback,
@@ -1473,13 +1322,12 @@
   vm_tools::cicerone::CreateLxdContainerResponse response = reply.value();
   if (response.status() ==
       vm_tools::cicerone::CreateLxdContainerResponse::CREATING) {
-    VLOG(1) << "Awaiting LxdContainerCreatedSignal for " << owner_id << ", "
+    VLOG(1) << "Awaiting LxdContainerCreatedSignal for " << owner_id_ << ", "
             << vm_name << ", " << container_name;
     // The callback will be called when we receive the LxdContainerCreated
     // signal.
     create_lxd_container_callbacks_.emplace(
-        std::make_tuple(owner_id, vm_name, container_name),
-        std::move(callback));
+        std::make_tuple(vm_name, container_name), std::move(callback));
     return;
   }
   if (response.status() !=
@@ -1492,7 +1340,6 @@
 }
 
 void CrostiniManager::OnStartLxdContainer(
-    std::string owner_id,
     std::string vm_name,
     std::string container_name,
     CrostiniResultCallback callback,
@@ -1516,7 +1363,6 @@
 }
 
 void CrostiniManager::OnSetUpLxdContainerUser(
-    std::string owner_id,
     std::string vm_name,
     std::string container_name,
     CrostiniResultCallback callback,
@@ -1538,10 +1384,9 @@
     return;
   }
 
-  if (!IsContainerRunning(owner_id, vm_name, container_name)) {
-    start_container_callbacks_.emplace(
-        std::make_tuple(owner_id, vm_name, container_name),
-        std::move(callback));
+  if (!IsContainerRunning(vm_name, container_name)) {
+    start_container_callbacks_.emplace(std::make_tuple(vm_name, container_name),
+                                       std::move(callback));
     return;
   }
   std::move(callback).Run(ConciergeClientResult::SUCCESS);
@@ -1549,9 +1394,11 @@
 
 void CrostiniManager::OnLxdContainerCreated(
     const vm_tools::cicerone::LxdContainerCreatedSignal& signal) {
+  if (signal.owner_id() != owner_id_)
+    return;
   // Find the callbacks to call, then erase them from the map.
-  auto range = create_lxd_container_callbacks_.equal_range(std::make_tuple(
-      signal.owner_id(), signal.vm_name(), signal.container_name()));
+  auto range = create_lxd_container_callbacks_.equal_range(
+      std::make_tuple(signal.vm_name(), signal.container_name()));
   for (auto it = range.first; it != range.second; ++it) {
     std::move(it->second).Run(ConciergeClientResult::SUCCESS);
   }
@@ -1559,13 +1406,24 @@
 }
 
 void CrostiniManager::OnLxdContainerDownloading(
-    const vm_tools::cicerone::LxdContainerDownloadingSignal& signal) {}
+    const vm_tools::cicerone::LxdContainerDownloadingSignal& signal) {
+  if (owner_id_ != signal.owner_id()) {
+    return;
+  }
+  auto range = restarters_by_container_.equal_range(
+      std::make_pair(signal.vm_name(), signal.container_name()));
+  for (auto it = range.first; it != range.second; ++it) {
+    restarters_by_id_[it->second]->OnContainerDownloading(
+        signal.download_progress());
+  }
+}
 
 void CrostiniManager::OnTremplinStarted(
     const vm_tools::cicerone::TremplinStartedSignal& signal) {
+  if (signal.owner_id() != owner_id_)
+    return;
   // Find the callbacks to call, then erase them from the map.
-  auto range = tremplin_started_callbacks_.equal_range(
-      std::make_pair(signal.owner_id(), signal.vm_name()));
+  auto range = tremplin_started_callbacks_.equal_range(signal.vm_name());
   for (auto it = range.first; it != range.second; ++it) {
     std::move(it->second).Run();
   }
@@ -1659,14 +1517,30 @@
                           response.host_private_key(), response.hostname());
 }
 
-void CrostiniManager::RemoveCrostini(Profile* profile,
-                                     std::string vm_name,
+void CrostiniManager::RemoveCrostini(std::string vm_name,
                                      std::string container_name,
                                      RemoveCrostiniCallback callback) {
   auto crostini_remover = base::MakeRefCounted<CrostiniRemover>(
-      profile, std::move(vm_name), std::move(container_name),
+      profile_, std::move(vm_name), std::move(container_name),
       std::move(callback));
   crostini_remover->RemoveCrostini();
 }
 
+void CrostiniManager::FinishRestart(CrostiniRestarter* restarter,
+                                    ConciergeClientResult result) {
+  auto key = std::make_pair(restarter->vm_name(), restarter->container_name());
+  auto range = restarters_by_container_.equal_range(key);
+  std::vector<scoped_refptr<CrostiniRestarter>> pending_restarters;
+  // Erase first, because restarter->RunCallback() may modify our maps.
+  for (auto it = range.first; it != range.second; ++it) {
+    CrostiniManager::RestartId restart_id = it->second;
+    pending_restarters.emplace_back(restarters_by_id_[restart_id]);
+    restarters_by_id_.erase(restart_id);
+  }
+  restarters_by_container_.erase(range.first, range.second);
+  for (const auto& pending_restarter : pending_restarters) {
+    pending_restarter->RunCallback(result);
+  }
+}
+
 }  // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.h b/chrome/browser/chromeos/crostini/crostini_manager.h
index 93ac231..77d5bbd 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.h
+++ b/chrome/browser/chromeos/crostini/crostini_manager.h
@@ -8,7 +8,6 @@
 #include <map>
 #include <set>
 #include <string>
-#include <tuple>
 #include <utility>
 #include <vector>
 
@@ -23,6 +22,7 @@
 #include "chromeos/dbus/cicerone_client.h"
 #include "chromeos/dbus/concierge/service.pb.h"
 #include "chromeos/dbus/concierge_client.h"
+#include "components/keyed_service/core/keyed_service.h"
 
 class Profile;
 
@@ -55,6 +55,12 @@
   INSTALLING,
 };
 
+enum class VmState {
+  STARTING,
+  STARTED,
+  STOPPING,
+};
+
 // Return type when getting app icons from within a container.
 struct Icon {
   std::string desktop_file_id;
@@ -84,7 +90,8 @@
 // communication with the Cicerone service and both should remain as thin as
 // possible. The existence of Cicerone is abstracted behind this class and
 // only the Concierge name is exposed outside of here.
-class CrostiniManager : public chromeos::ConciergeClient::Observer,
+class CrostiniManager : public KeyedService,
+                        public chromeos::ConciergeClient::Observer,
                         public chromeos::CiceroneClient::Observer {
  public:
   using ConciergeClientCallback =
@@ -145,10 +152,17 @@
     virtual void OnConciergeStarted(ConciergeClientResult result) = 0;
     virtual void OnDiskImageCreated(ConciergeClientResult result) = 0;
     virtual void OnVmStarted(ConciergeClientResult result) = 0;
+    virtual void OnContainerDownloading(int32_t download_percent) = 0;
+    virtual void OnContainerCreated(ConciergeClientResult result) = 0;
     virtual void OnContainerStarted(ConciergeClientResult result) = 0;
     virtual void OnSshKeysFetched(ConciergeClientResult result) = 0;
   };
 
+  static CrostiniManager* GetForProfile(Profile* profile);
+
+  explicit CrostiniManager(Profile* profile);
+  ~CrostiniManager() override;
+
   // Checks if the cros-termina component is installed.
   bool IsCrosTerminaInstalled() const;
 
@@ -163,7 +177,7 @@
   static AppLaunchParams GenerateTerminalAppLaunchParams(Profile* profile);
 
   // Upgrades cros-termina component if the current version is not compatible.
-  void MaybeUpgradeCrostini(Profile* profile);
+  void MaybeUpgradeCrostini();
 
   // Installs the current version of cros-termina component. Attempts to apply
   // pending upgrades if a MaybeUpgradeCrostini failed.
@@ -182,8 +196,6 @@
   // |callback| is called if the arguments are bad, or after the method call
   // finishes.
   void CreateDiskImage(
-      // The cryptohome id for the user's encrypted storage.
-      const std::string& cryptohome_id,
       // The path to the disk image, including the name of
       // the image itself. The image name should match the
       // name of the VM that it will be used for.
@@ -197,8 +209,6 @@
   // |callback| is called if the arguments are bad, or after the method call
   // finishes.
   void DestroyDiskImage(
-      // The cryptohome id for the user's encrypted storage.
-      const std::string& cryptohome_id,
       // The path to the disk image, including the name of
       // the image itself.
       const base::FilePath& disk_path,
@@ -206,32 +216,28 @@
       vm_tools::concierge::StorageLocation storage_location,
       DestroyDiskImageCallback callback);
 
-  void ListVmDisks(
-      // The cryptohome id for the user's encrypted storage.
-      const std::string& cryptohome_id,
-      ListVmDisksCallback callback);
+  void ListVmDisks(ListVmDisksCallback callback);
 
   // Checks the arguments for starting a Termina VM. Starts a Termina VM via
   // ConciergeClient::StartTerminaVm. |callback| is called if the arguments
   // are bad, or after the method call finishes.
-  void StartTerminaVm(std::string owner_id,
-                      // The human-readable name to be assigned to this VM.
-                      std::string name,
-                      // Path to the disk image on the host.
-                      const base::FilePath& disk_path,
-                      StartTerminaVmCallback callback);
+  void StartTerminaVm(
+      // The human-readable name to be assigned to this VM.
+      std::string name,
+      // Path to the disk image on the host.
+      const base::FilePath& disk_path,
+      StartTerminaVmCallback callback);
 
   // Checks the arguments for stopping a Termina VM. Stops the Termina VM via
   // ConciergeClient::StopVm. |callback| is called if the arguments are bad,
   // or after the method call finishes.
-  void StopVm(Profile* profile, std::string name, StopVmCallback callback);
+  void StopVm(std::string name, StopVmCallback callback);
 
   // Checks the arguments for creating an Lxd container via
   // CiceroneClient::CreateLxdContainer. |callback| is called immediately if the
   // arguments are bad, or once the container has been created.
   void CreateLxdContainer(std::string vm_name,
                           std::string container_name,
-                          std::string owner_id,
                           CrostiniResultCallback callback);
 
   // Checks the arguments for starting an Lxd container via
@@ -239,7 +245,6 @@
   // arguments are bad, or once the container has been created.
   void StartLxdContainer(std::string vm_name,
                          std::string container_name,
-                         std::string owner_id,
                          CrostiniResultCallback callback);
 
   // Checks the arguments for setting up an Lxd container user via
@@ -247,15 +252,13 @@
   // the arguments are bad, or once garcon has been started.
   void SetUpLxdContainerUser(std::string vm_name,
                              std::string container_name,
-                             std::string owner_id,
                              std::string container_username,
                              CrostiniResultCallback callback);
 
   // Asynchronously launches an app as specified by its desktop file id.
   // |callback| is called with SUCCESS when the relevant process is started
   // or LAUNCH_CONTAINER_APPLICATION_FAILED if there was an error somewhere.
-  void LaunchContainerApplication(Profile* profile,
-                                  std::string vm_name,
+  void LaunchContainerApplication(std::string vm_name,
                                   std::string container_name,
                                   std::string desktop_file_id,
                                   const std::vector<std::string>& files,
@@ -263,8 +266,7 @@
 
   // Asynchronously gets app icons as specified by their desktop file ids.
   // |callback| is called after the method call finishes.
-  void GetContainerAppIcons(Profile* profile,
-                            std::string vm_name,
+  void GetContainerAppIcons(std::string vm_name,
                             std::string container_name,
                             std::vector<std::string> desktop_file_ids,
                             int icon_size,
@@ -274,8 +276,7 @@
   // Begin installation of a Linux Package inside the container. If the
   // installation is successfully started, further updates will be sent to
   // added InstallLinuxPackageProgressObservers.
-  void InstallLinuxPackage(Profile* profile,
-                           std::string vm_name,
+  void InstallLinuxPackage(std::string vm_name,
                            std::string container_name,
                            std::string package_path,
                            InstallLinuxPackageCallback callback);
@@ -285,24 +286,22 @@
   // |callback| is called after the method call finishes.
   void GetContainerSshKeys(std::string vm_name,
                            std::string container_name,
-                           std::string cryptohome_id,
                            GetContainerSshKeysCallback callback);
 
   // Create the crosh-in-a-window that displays a shell in an container on a VM.
-  Browser* CreateContainerTerminal(const AppLaunchParams& launch_params,
-                                   const GURL& vsh_in_crosh_url);
+  static Browser* CreateContainerTerminal(const AppLaunchParams& launch_params,
+                                          const GURL& vsh_in_crosh_url);
 
   // Shows the already created crosh-in-a-window that displays a shell in an
   // already running container on a VM.
-  void ShowContainerTerminal(const AppLaunchParams& launch_params,
-                             const GURL& vsh_in_crosh_url,
-                             Browser* browser);
+  static void ShowContainerTerminal(const AppLaunchParams& launch_params,
+                                    const GURL& vsh_in_crosh_url,
+                                    Browser* browser);
 
   // Launches the crosh-in-a-window that displays a shell in an already running
   // container on a VM and passes |terminal_args| as parameters to that shell
   // which will cause them to be executed as program inside that shell.
-  void LaunchContainerTerminal(Profile* profile,
-                               const std::string& vm_name,
+  void LaunchContainerTerminal(const std::string& vm_name,
                                const std::string& container_name,
                                const std::vector<std::string>& terminal_args);
 
@@ -310,27 +309,25 @@
   static const RestartId kUninitializedRestartId = -1;
   // Runs all the steps required to restart the given crostini vm and container.
   // The optional |observer| tracks progress.
-  RestartId RestartCrostini(Profile* profile,
-                            std::string vm_name,
+  RestartId RestartCrostini(std::string vm_name,
                             std::string container_name,
                             RestartCrostiniCallback callback,
                             RestartObserver* observer = nullptr);
 
-  void AbortRestartCrostini(Profile* profile, RestartId id);
+  // Aborts a restart. A "next" restarter with the same <vm_name,
+  // container_name> will run, if there is one.
+  void AbortRestartCrostini(RestartId id);
 
   // Adds a callback to receive notification of container shutdown.
   void AddShutdownContainerCallback(
-      Profile* profile,
       std::string vm_name,
       std::string container_name,
       ShutdownContainerCallback shutdown_callback);
 
   // Add/remove observers for package install progress.
   void AddInstallLinuxPackageProgressObserver(
-      Profile* profile,
       InstallLinuxPackageProgressObserver* observer);
   void RemoveInstallLinuxPackageProgressObserver(
-      Profile* profile,
       InstallLinuxPackageProgressObserver* observer);
 
   // ConciergeClient::Observer:
@@ -352,38 +349,26 @@
   void OnTremplinStarted(
       const vm_tools::cicerone::TremplinStartedSignal& signal) override;
 
-  void RemoveCrostini(Profile* profile,
-                      std::string vm_name,
+  void RemoveCrostini(std::string vm_name,
                       std::string container_name,
                       RemoveCrostiniCallback callback);
 
-  // Returns the singleton instance of CrostiniManager.
-  static CrostiniManager* GetInstance();
+  void SetVmState(std::string vm_name, VmState vm_state);
+  bool IsVmRunning(std::string vm_name);
 
-  bool IsVmRunning(Profile* profile, std::string vm_name);
   // Returns null if VM is not running.
-  base::Optional<vm_tools::concierge::VmInfo> GetVmInfo(Profile* profile,
-                                                        std::string vm_name);
-  void AddRunningVmForTesting(std::string owner_id,
-                              std::string vm_name,
+  base::Optional<vm_tools::concierge::VmInfo> GetVmInfo(std::string vm_name);
+  void AddRunningVmForTesting(std::string vm_name,
                               vm_tools::concierge::VmInfo vm_info);
-  bool IsContainerRunning(Profile* profile,
-                          std::string vm_name,
-                          std::string container_name);
+  bool IsContainerRunning(std::string vm_name, std::string container_name);
 
   // Clear the lists of running VMs and containers.
-  // TODO(timloh): This is fragile. We should make the CrostiniManager a keyed
-  // service so that separate tests get new instances of it.
-  void ResetForTesting();
   // Can be called for testing to skip restart.
   void set_skip_restart_for_testing() { skip_restart_for_testing_ = true; }
   bool skip_restart_for_testing() { return skip_restart_for_testing_; }
 
  private:
-  friend struct base::DefaultSingletonTraits<CrostiniManager>;
-
-  CrostiniManager();
-  ~CrostiniManager() override;
+  class CrostiniRestarter;
 
   // Callback for ConciergeClient::CreateDiskImage. Called after the Concierge
   // service method finishes.
@@ -404,10 +389,10 @@
       base::Optional<vm_tools::concierge::ListVmDisksResponse> reply);
 
   // Callback for ConciergeClient::StartTerminaVm. Called after the Concierge
-  // service method finishes. |callback| is called if the container has already
-  // been started, otherwise it is passed to OnStartTremplin.
+  // service method finishes.  Updates running containers list then calls the
+  // |callback| if the container has already been started, otherwise passes the
+  // callback to OnStartTremplin.
   void OnStartTerminaVm(
-      std::string owner_id,
       std::string vm_name,
       StartTerminaVmCallback callback,
       base::Optional<vm_tools::concierge::StartVmResponse> reply);
@@ -415,15 +400,13 @@
   // Callback for ConciergeClient::TremplinStartedSignal. Called after the
   // Tremplin service starts. Updates running containers list and then calls the
   // |callback|.
-  void OnStartTremplin(std::pair<std::string, std::string> key,
-                       vm_tools::concierge::VmInfo vm_info,
+  void OnStartTremplin(std::string vm_name,
                        StartTerminaVmCallback callback,
                        ConciergeClientResult result);
 
   // Callback for ConciergeClient::StopVm. Called after the Concierge
   // service method finishes.
-  void OnStopVm(std::string owner_id,
-                std::string vm_name,
+  void OnStopVm(std::string vm_name,
                 StopVmCallback callback,
                 base::Optional<vm_tools::concierge::StopVmResponse> reply);
 
@@ -447,7 +430,6 @@
   // is still being created, in which case we will wait for an
   // OnLxdContainerCreated event.
   void OnCreateLxdContainer(
-      std::string owner_id,
       std::string vm_name,
       std::string container_name,
       CrostiniResultCallback callback,
@@ -455,7 +437,6 @@
 
   // Callback for CiceroneClient::StartLxdContainer.
   void OnStartLxdContainer(
-      std::string owner_id,
       std::string vm_name,
       std::string container_name,
       CrostiniResultCallback callback,
@@ -463,7 +444,6 @@
 
   // Callback for CiceroneClient::SetUpLxdContainerUser.
   void OnSetUpLxdContainerUser(
-      std::string owner_id,
       std::string vm_name,
       std::string container_name,
       CrostiniResultCallback callback,
@@ -503,51 +483,55 @@
       CreateDiskImageCallback callback,
       int64_t free_disk_size);
 
-  bool IsContainerRunning(std::string owner_id,
-                          std::string vm_name,
-                          std::string container_name);
+  void FinishRestart(CrostiniRestarter* restarter,
+                     ConciergeClientResult result);
+
+  Profile* profile_;
+  std::string owner_id_;
 
   bool skip_restart_for_testing_ = false;
   bool is_cros_termina_registered_ = false;
   bool termina_update_check_needed_ = false;
 
-  // Pending container started callbacks are keyed by <owner_id, vm_name,
-  // container_name> string tuples.
-  std::multimap<std::tuple<std::string, std::string, std::string>,
-                StartContainerCallback>
+  // Pending container started callbacks are keyed by <vm_name, container_name>
+  // string pairs.
+  std::multimap<std::pair<std::string, std::string>, StartContainerCallback>
       start_container_callbacks_;
 
-  // Pending ShutdownContainer callbacks are keyed by <owner_id, vm_name,
-  // container_name> string tuples.
-  std::multimap<std::tuple<std::string, std::string, std::string>,
-                ShutdownContainerCallback>
+  // Pending ShutdownContainer callbacks are keyed by <vm_name, container_name>
+  // string pairs.
+  std::multimap<std::pair<std::string, std::string>, ShutdownContainerCallback>
       shutdown_container_callbacks_;
 
-  // Pending CreateLxdContainer callbacks are keyed by <owner_id, vm_name,
-  // container_name> string tuples. These are used if CreateLxdContainer
-  // indicates we need to wait for an LxdContainerCreate signal.
-  std::multimap<std::tuple<std::string, std::string, std::string>,
-                CrostiniResultCallback>
+  // Pending CreateLxdContainer callbacks are keyed by <vm_name, container_name>
+  // string pairs. These are used if CreateLxdContainer indicates we need to
+  // wait for an LxdContainerCreate signal.
+  std::multimap<std::pair<std::string, std::string>, CrostiniResultCallback>
       create_lxd_container_callbacks_;
 
-  // Callbacks to run after Tremplin is started, keyed by <owner_id, vm_name>
-  // pairs. These are used if StartTerminaVm completes but we need to wait from
-  // Tremplin to start.
-  std::multimap<std::pair<std::string, std::string>, base::OnceClosure>
-      tremplin_started_callbacks_;
+  // Callbacks to run after Tremplin is started, keyed by vm_name. These are
+  // used if StartTerminaVm completes but we need to wait from Tremplin to
+  // start.
+  std::multimap<std::string, base::OnceClosure> tremplin_started_callbacks_;
 
-  // Running vms as <owner_id, vm_name> pairs.
-  std::map<std::pair<std::string, std::string>, vm_tools::concierge::VmInfo>
+  std::map<std::string, std::pair<VmState, vm_tools::concierge::VmInfo>>
       running_vms_;
 
-  // Running containers as keyed by <owner_id, vm_name> string pairs.
-  std::multimap<std::pair<std::string, std::string>, std::string>
-      running_containers_;
+  // Running containers as keyed by vm name.
+  std::multimap<std::string, std::string> running_containers_;
 
-  // Keyed by owner_id.
-  std::multimap<std::string, InstallLinuxPackageProgressObserver*>
+  base::ObserverList<InstallLinuxPackageProgressObserver>::Unchecked
       install_linux_package_progress_observers_;
 
+  // Restarts by <vm_name, container_name>. Only one restarter flow is actually
+  // running for a given container, other restarters will just have their
+  // callback called when the running restarter completes.
+  std::multimap<std::pair<std::string, std::string>, CrostiniManager::RestartId>
+      restarters_by_container_;
+
+  std::map<CrostiniManager::RestartId, scoped_refptr<CrostiniRestarter>>
+      restarters_by_id_;
+
   // Note: This should remain the last member so it'll be destroyed and
   // invalidate its weak pointers before any other members are destroyed.
   base::WeakPtrFactory<CrostiniManager> weak_ptr_factory_;
diff --git a/chrome/browser/chromeos/crostini/crostini_manager_factory.cc b/chrome/browser/chromeos/crostini/crostini_manager_factory.cc
new file mode 100644
index 0000000..238acf9
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_manager_factory.cc
@@ -0,0 +1,38 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/crostini/crostini_manager_factory.h"
+
+#include "chrome/browser/chromeos/crostini/crostini_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+
+namespace crostini {
+
+// static
+CrostiniManager* CrostiniManagerFactory::GetForProfile(Profile* profile) {
+  return static_cast<CrostiniManager*>(
+      GetInstance()->GetServiceForBrowserContext(profile, true));
+}
+
+// static
+CrostiniManagerFactory* CrostiniManagerFactory::GetInstance() {
+  static base::NoDestructor<CrostiniManagerFactory> factory;
+  return factory.get();
+}
+
+CrostiniManagerFactory::CrostiniManagerFactory()
+    : BrowserContextKeyedServiceFactory(
+          "CrostiniManager",
+          BrowserContextDependencyManager::GetInstance()) {}
+
+CrostiniManagerFactory::~CrostiniManagerFactory() = default;
+
+KeyedService* CrostiniManagerFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  Profile* profile = Profile::FromBrowserContext(context);
+  return new CrostiniManager(profile);
+}
+
+}  // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_manager_factory.h b/chrome/browser/chromeos/crostini/crostini_manager_factory.h
new file mode 100644
index 0000000..533b3a7
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_manager_factory.h
@@ -0,0 +1,38 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_MANAGER_FACTORY_H_
+#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_MANAGER_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/no_destructor.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+class Profile;
+
+namespace crostini {
+
+class CrostiniManager;
+
+class CrostiniManagerFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  static CrostiniManager* GetForProfile(Profile* profile);
+  static CrostiniManagerFactory* GetInstance();
+
+ private:
+  friend class base::NoDestructor<CrostiniManagerFactory>;
+
+  CrostiniManagerFactory();
+  ~CrostiniManagerFactory() override;
+
+  // BrowserContextKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+
+  DISALLOW_COPY_AND_ASSIGN(CrostiniManagerFactory);
+};
+
+}  // namespace crostini
+
+#endif  // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_MANAGER_FACTORY_H_
diff --git a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
index 3b758c1..fc6b2ebe 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
@@ -22,7 +22,6 @@
 namespace crostini {
 
 namespace {
-const char kOwnerId[] = "owner_id";
 const char kVmName[] = "vm_name";
 const char kContainerName[] = "container_name";
 }  // namespace
@@ -95,8 +94,7 @@
   void OnStartTremplinRecordsRunningVmCallback(base::OnceClosure closure,
                                                ConciergeClientResult result) {
     // Check that running_vms_ contains the running vm.
-    EXPECT_TRUE(
-        CrostiniManager::GetInstance()->IsVmRunning(profile(), kVmName));
+    EXPECT_TRUE(crostini_manager()->IsVmRunning(kVmName));
     std::move(closure).Run();
   }
 
@@ -117,25 +115,15 @@
   }
 
   CrostiniManagerTest()
-      : fake_cicerone_client_(new chromeos::FakeCiceroneClient()),
-        fake_concierge_client_(new chromeos::FakeConciergeClient()),
-        scoped_task_environment_(
+      : scoped_task_environment_(
             base::test::ScopedTaskEnvironment::MainThreadType::UI),
         test_browser_thread_bundle_(
             content::TestBrowserThreadBundle::REAL_IO_THREAD) {
-    // Initialization between D-Bus, fake clients, and the singleton
-    // CrostiniManager is tricky since CrostiniManager is a global singleton and
-    // doesn't add itself as an observer for the new Concierge/Cicerone clients
-    // created for each test.  We must first get a handle on the D-Bus setter
-    // and initialize it, then reset CrostiniManager and add it as an observer
-    // for the clients in this test, then set the fake clients into D-Bus.
-    auto dbus_setter = chromeos::DBusThreadManager::GetSetterForTesting();
     chromeos::DBusThreadManager::Initialize();
-    CrostiniManager::GetInstance()->ResetForTesting();
-    fake_concierge_client_->AddObserver(CrostiniManager::GetInstance());
-    fake_cicerone_client_->AddObserver(CrostiniManager::GetInstance());
-    dbus_setter->SetConciergeClient(base::WrapUnique(fake_concierge_client_));
-    dbus_setter->SetCiceroneClient(base::WrapUnique(fake_cicerone_client_));
+    fake_cicerone_client_ = static_cast<chromeos::FakeCiceroneClient*>(
+        chromeos::DBusThreadManager::Get()->GetCiceroneClient());
+    fake_concierge_client_ = static_cast<chromeos::FakeConciergeClient*>(
+        chromeos::DBusThreadManager::Get()->GetConciergeClient());
   }
 
   ~CrostiniManagerTest() override { chromeos::DBusThreadManager::Shutdown(); }
@@ -143,16 +131,19 @@
   void SetUp() override {
     run_loop_ = std::make_unique<base::RunLoop>();
     profile_ = std::make_unique<TestingProfile>();
+    crostini_manager_ = std::make_unique<CrostiniManager>(profile_.get());
   }
 
   void TearDown() override {
-    run_loop_.reset();
+    crostini_manager_.reset();
     profile_.reset();
+    run_loop_.reset();
   }
 
  protected:
   base::RunLoop* run_loop() { return run_loop_.get(); }
   Profile* profile() { return profile_.get(); }
+  CrostiniManager* crostini_manager() { return crostini_manager_.get(); }
 
   // Owned by chromeos::DBusThreadManager
   chromeos::FakeCiceroneClient* fake_cicerone_client_;
@@ -161,6 +152,7 @@
   std::unique_ptr<base::RunLoop>
       run_loop_;  // run_loop_ must be created on the UI thread.
   std::unique_ptr<TestingProfile> profile_;
+  std::unique_ptr<CrostiniManager> crostini_manager_;
 
  private:
   base::test::ScopedTaskEnvironment scoped_task_environment_;
@@ -171,19 +163,8 @@
 TEST_F(CrostiniManagerTest, CreateDiskImageNameError) {
   const base::FilePath& disk_path = base::FilePath("");
 
-  CrostiniManager::GetInstance()->CreateDiskImage(
-      "a_cryptohome_id", disk_path,
-      vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT,
-      base::BindOnce(&CrostiniManagerTest::CreateDiskImageClientErrorCallback,
-                     base::Unretained(this), run_loop()->QuitClosure()));
-  run_loop()->Run();
-}
-
-TEST_F(CrostiniManagerTest, CreateDiskImageCryptohomeError) {
-  const base::FilePath& disk_path = base::FilePath(kVmName);
-
-  CrostiniManager::GetInstance()->CreateDiskImage(
-      "", disk_path, vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT,
+  crostini_manager()->CreateDiskImage(
+      disk_path, vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT,
       base::BindOnce(&CrostiniManagerTest::CreateDiskImageClientErrorCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
   run_loop()->Run();
@@ -192,8 +173,8 @@
 TEST_F(CrostiniManagerTest, CreateDiskImageStorageLocationError) {
   const base::FilePath& disk_path = base::FilePath(kVmName);
 
-  CrostiniManager::GetInstance()->CreateDiskImage(
-      "a_cryptohome_id", disk_path,
+  crostini_manager()->CreateDiskImage(
+      disk_path,
       vm_tools::concierge::StorageLocation_INT_MIN_SENTINEL_DO_NOT_USE_,
       base::BindOnce(&CrostiniManagerTest::CreateDiskImageClientErrorCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
@@ -203,9 +184,8 @@
 TEST_F(CrostiniManagerTest, CreateDiskImageSuccess) {
   const base::FilePath& disk_path = base::FilePath(kVmName);
 
-  CrostiniManager::GetInstance()->CreateDiskImage(
-      "a_cryptohome_id", disk_path,
-      vm_tools::concierge::STORAGE_CRYPTOHOME_DOWNLOADS,
+  crostini_manager()->CreateDiskImage(
+      disk_path, vm_tools::concierge::STORAGE_CRYPTOHOME_DOWNLOADS,
       base::BindOnce(&CrostiniManagerTest::CreateDiskImageSuccessCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
   run_loop()->Run();
@@ -214,19 +194,8 @@
 TEST_F(CrostiniManagerTest, DestroyDiskImageNameError) {
   const base::FilePath& disk_path = base::FilePath("");
 
-  CrostiniManager::GetInstance()->DestroyDiskImage(
-      "a_cryptohome_id", disk_path,
-      vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT,
-      base::BindOnce(&CrostiniManagerTest::DestroyDiskImageClientErrorCallback,
-                     base::Unretained(this), run_loop()->QuitClosure()));
-  run_loop()->Run();
-}
-
-TEST_F(CrostiniManagerTest, DestroyDiskImageCryptohomeError) {
-  const base::FilePath& disk_path = base::FilePath(kVmName);
-
-  CrostiniManager::GetInstance()->DestroyDiskImage(
-      "", disk_path, vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT,
+  crostini_manager()->DestroyDiskImage(
+      disk_path, vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT,
       base::BindOnce(&CrostiniManagerTest::DestroyDiskImageClientErrorCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
   run_loop()->Run();
@@ -235,8 +204,8 @@
 TEST_F(CrostiniManagerTest, DestroyDiskImageStorageLocationError) {
   const base::FilePath& disk_path = base::FilePath(kVmName);
 
-  CrostiniManager::GetInstance()->DestroyDiskImage(
-      "a_cryptohome_id", disk_path,
+  crostini_manager()->DestroyDiskImage(
+      disk_path,
       vm_tools::concierge::StorageLocation_INT_MIN_SENTINEL_DO_NOT_USE_,
       base::BindOnce(&CrostiniManagerTest::DestroyDiskImageClientErrorCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
@@ -246,44 +215,25 @@
 TEST_F(CrostiniManagerTest, DestroyDiskImageSuccess) {
   const base::FilePath& disk_path = base::FilePath(kVmName);
 
-  CrostiniManager::GetInstance()->DestroyDiskImage(
-      "a_cryptohome_id", disk_path,
-      vm_tools::concierge::STORAGE_CRYPTOHOME_DOWNLOADS,
+  crostini_manager()->DestroyDiskImage(
+      disk_path, vm_tools::concierge::STORAGE_CRYPTOHOME_DOWNLOADS,
       base::BindOnce(&CrostiniManagerTest::DestroyDiskImageSuccessCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
   run_loop()->Run();
 }
 
-TEST_F(CrostiniManagerTest, ListVmDisksCryptohomeError) {
-  CrostiniManager::GetInstance()->ListVmDisks(
-      "", base::BindOnce(&CrostiniManagerTest::ListVmDisksClientErrorCallback,
-                         base::Unretained(this), run_loop()->QuitClosure()));
-  run_loop()->Run();
-}
-
 TEST_F(CrostiniManagerTest, ListVmDisksSuccess) {
-  CrostiniManager::GetInstance()->ListVmDisks(
-      "a_cryptohome_id",
+  crostini_manager()->ListVmDisks(
       base::BindOnce(&CrostiniManagerTest::ListVmDisksSuccessCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
   run_loop()->Run();
 }
 
-TEST_F(CrostiniManagerTest, StartTerminaVmOwnerIdError) {
-  const base::FilePath& disk_path = base::FilePath(kVmName);
-
-  CrostiniManager::GetInstance()->StartTerminaVm(
-      "", kVmName, disk_path,
-      base::BindOnce(&CrostiniManagerTest::StartTerminaVmClientErrorCallback,
-                     base::Unretained(this), run_loop()->QuitClosure()));
-  run_loop()->Run();
-}
-
 TEST_F(CrostiniManagerTest, StartTerminaVmNameError) {
   const base::FilePath& disk_path = base::FilePath(kVmName);
 
-  CrostiniManager::GetInstance()->StartTerminaVm(
-      kOwnerId, "", disk_path,
+  crostini_manager()->StartTerminaVm(
+      "", disk_path,
       base::BindOnce(&CrostiniManagerTest::StartTerminaVmClientErrorCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
   run_loop()->Run();
@@ -292,8 +242,8 @@
 TEST_F(CrostiniManagerTest, StartTerminaVmDiskPathError) {
   const base::FilePath& disk_path = base::FilePath();
 
-  CrostiniManager::GetInstance()->StartTerminaVm(
-      kOwnerId, kVmName, disk_path,
+  crostini_manager()->StartTerminaVm(
+      kVmName, disk_path,
       base::BindOnce(&CrostiniManagerTest::StartTerminaVmClientErrorCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
   run_loop()->Run();
@@ -302,8 +252,8 @@
 TEST_F(CrostiniManagerTest, StartTerminaVmSuccess) {
   const base::FilePath& disk_path = base::FilePath(kVmName);
 
-  CrostiniManager::GetInstance()->StartTerminaVm(
-      kOwnerId, kVmName, disk_path,
+  crostini_manager()->StartTerminaVm(
+      kVmName, disk_path,
       base::BindOnce(&CrostiniManagerTest::StartTerminaVmSuccessCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
   run_loop()->Run();
@@ -314,29 +264,28 @@
   const std::string owner_id = CryptohomeIdForProfile(profile());
 
   // Start the Vm.
-  CrostiniManager::GetInstance()->StartTerminaVm(
-      owner_id, kVmName, disk_path,
+  crostini_manager()->StartTerminaVm(
+      kVmName, disk_path,
       base::BindOnce(
           &CrostiniManagerTest::OnStartTremplinRecordsRunningVmCallback,
           base::Unretained(this), run_loop()->QuitClosure()));
 
   // Check that the Vm start is not recorded (without tremplin start).
-  EXPECT_FALSE(CrostiniManager::GetInstance()->IsVmRunning(profile(), kVmName));
+  EXPECT_FALSE(crostini_manager()->IsVmRunning(kVmName));
 
   run_loop()->Run();
 }
 
 TEST_F(CrostiniManagerTest, StopVmNameError) {
-  CrostiniManager::GetInstance()->StopVm(
-      profile(), "",
-      base::BindOnce(&CrostiniManagerTest::StopVmClientErrorCallback,
-                     base::Unretained(this), run_loop()->QuitClosure()));
+  crostini_manager()->StopVm(
+      "", base::BindOnce(&CrostiniManagerTest::StopVmClientErrorCallback,
+                         base::Unretained(this), run_loop()->QuitClosure()));
   run_loop()->Run();
 }
 
 TEST_F(CrostiniManagerTest, StopVmSuccess) {
-  CrostiniManager::GetInstance()->StopVm(
-      profile(), kVmName,
+  crostini_manager()->StopVm(
+      kVmName,
       base::BindOnce(&CrostiniManagerTest::StopVmSuccessCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
   run_loop()->Run();
@@ -345,8 +294,8 @@
 TEST_F(CrostiniManagerTest, InstallLinuxPackageSignalNotConnectedError) {
   fake_cicerone_client_->set_install_linux_package_progress_signal_connected(
       false);
-  CrostiniManager::GetInstance()->InstallLinuxPackage(
-      profile(), kVmName, kContainerName, "/tmp/package.deb",
+  crostini_manager()->InstallLinuxPackage(
+      kVmName, kContainerName, "/tmp/package.deb",
       base::BindOnce(&CrostiniManagerTest::InstallLinuxPackageCallback,
                      base::Unretained(this), run_loop()->QuitClosure(),
                      ConciergeClientResult::INSTALL_LINUX_PACKAGE_FAILED,
@@ -358,8 +307,8 @@
   vm_tools::cicerone::InstallLinuxPackageResponse response;
   response.set_status(vm_tools::cicerone::InstallLinuxPackageResponse::STARTED);
   fake_cicerone_client_->set_install_linux_package_response(response);
-  CrostiniManager::GetInstance()->InstallLinuxPackage(
-      profile(), kVmName, kContainerName, "/tmp/package.deb",
+  crostini_manager()->InstallLinuxPackage(
+      kVmName, kContainerName, "/tmp/package.deb",
       base::BindOnce(&CrostiniManagerTest::InstallLinuxPackageCallback,
                      base::Unretained(this), run_loop()->QuitClosure(),
                      ConciergeClientResult::SUCCESS, std::string()));
@@ -372,8 +321,8 @@
   response.set_status(vm_tools::cicerone::InstallLinuxPackageResponse::FAILED);
   response.set_failure_reason(failure_reason);
   fake_cicerone_client_->set_install_linux_package_response(response);
-  CrostiniManager::GetInstance()->InstallLinuxPackage(
-      profile(), kVmName, kContainerName, "/tmp/package.deb",
+  crostini_manager()->InstallLinuxPackage(
+      kVmName, kContainerName, "/tmp/package.deb",
       base::BindOnce(&CrostiniManagerTest::InstallLinuxPackageCallback,
                      base::Unretained(this), run_loop()->QuitClosure(),
                      ConciergeClientResult::INSTALL_LINUX_PACKAGE_FAILED,
@@ -386,7 +335,6 @@
  public:
   void SetUp() override {
     CrostiniManagerTest::SetUp();
-    owner_id_ = CryptohomeIdForProfile(profile());
   }
 
   void RestartCrostiniCallback(base::OnceClosure closure,
@@ -420,6 +368,14 @@
     }
   }
 
+  void OnContainerDownloading(int32_t download_percent) override {}
+
+  void OnContainerCreated(ConciergeClientResult result) override {
+    if (abort_on_container_created_) {
+      Abort();
+    }
+  }
+
   void OnContainerStarted(ConciergeClientResult result) override {
     if (abort_on_container_started_) {
       Abort();
@@ -434,8 +390,7 @@
 
  protected:
   void Abort() {
-    CrostiniManager::GetInstance()->AbortRestartCrostini(profile(),
-                                                         restart_id_);
+    crostini_manager()->AbortRestartCrostini(restart_id_);
     run_loop()->Quit();
   }
 
@@ -456,11 +411,11 @@
 
   CrostiniManager::RestartId restart_id_ =
       CrostiniManager::kUninitializedRestartId;
-  std::string owner_id_;
   bool abort_on_component_loaded_ = false;
   bool abort_on_concierge_started_ = false;
   bool abort_on_disk_image_created_ = false;
   bool abort_on_vm_started_ = false;
+  bool abort_on_container_created_ = false;
   bool abort_on_container_started_ = false;
   bool abort_on_ssh_keys_fetched_ = false;
   int restart_crostini_callback_count_ = 0;
@@ -468,8 +423,8 @@
 };
 
 TEST_F(CrostiniManagerRestartTest, RestartSuccess) {
-  restart_id_ = CrostiniManager::GetInstance()->RestartCrostini(
-      profile(), kVmName, kContainerName,
+  restart_id_ = crostini_manager()->RestartCrostini(
+      kVmName, kContainerName,
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
                      base::Unretained(this), run_loop()->QuitClosure()),
       this);
@@ -483,8 +438,8 @@
 
 TEST_F(CrostiniManagerRestartTest, AbortOnComponentLoaded) {
   abort_on_component_loaded_ = true;
-  restart_id_ = CrostiniManager::GetInstance()->RestartCrostini(
-      profile(), kVmName, kContainerName,
+  restart_id_ = crostini_manager()->RestartCrostini(
+      kVmName, kContainerName,
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
                      base::Unretained(this), run_loop()->QuitClosure()),
       this);
@@ -497,8 +452,8 @@
 
 TEST_F(CrostiniManagerRestartTest, AbortOnConciergeStarted) {
   abort_on_concierge_started_ = true;
-  restart_id_ = CrostiniManager::GetInstance()->RestartCrostini(
-      profile(), kVmName, kContainerName,
+  restart_id_ = crostini_manager()->RestartCrostini(
+      kVmName, kContainerName,
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
                      base::Unretained(this), run_loop()->QuitClosure()),
       this);
@@ -511,8 +466,8 @@
 
 TEST_F(CrostiniManagerRestartTest, AbortOnDiskImageCreated) {
   abort_on_disk_image_created_ = true;
-  restart_id_ = CrostiniManager::GetInstance()->RestartCrostini(
-      profile(), kVmName, kContainerName,
+  restart_id_ = crostini_manager()->RestartCrostini(
+      kVmName, kContainerName,
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
                      base::Unretained(this), run_loop()->QuitClosure()),
       this);
@@ -525,8 +480,23 @@
 
 TEST_F(CrostiniManagerRestartTest, AbortOnVmStarted) {
   abort_on_vm_started_ = true;
-  restart_id_ = CrostiniManager::GetInstance()->RestartCrostini(
-      profile(), kVmName, kContainerName,
+  restart_id_ = crostini_manager()->RestartCrostini(
+      kVmName, kContainerName,
+      base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
+                     base::Unretained(this), run_loop()->QuitClosure()),
+      this);
+  run_loop()->Run();
+  EXPECT_TRUE(fake_concierge_client_->create_disk_image_called());
+  EXPECT_TRUE(fake_concierge_client_->start_termina_vm_called());
+  EXPECT_FALSE(fake_concierge_client_->get_container_ssh_keys_called());
+  EXPECT_EQ(0, restart_crostini_callback_count_);
+}
+
+TEST_F(CrostiniManagerRestartTest, AbortOnContainerCreated) {
+  abort_on_container_created_ = true;
+  // Use termina/penguin names to allow fetch ssh keys.
+  restart_id_ = crostini_manager()->RestartCrostini(
+      kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
                      base::Unretained(this), run_loop()->QuitClosure()),
       this);
@@ -540,8 +510,8 @@
 TEST_F(CrostiniManagerRestartTest, AbortOnContainerStarted) {
   abort_on_container_started_ = true;
   // Use termina/penguin names to allow fetch ssh keys.
-  restart_id_ = CrostiniManager::GetInstance()->RestartCrostini(
-      profile(), kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
+  restart_id_ = crostini_manager()->RestartCrostini(
+      kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
                      base::Unretained(this), run_loop()->QuitClosure()),
       this);
@@ -554,8 +524,8 @@
 
 TEST_F(CrostiniManagerRestartTest, OnlyMountTerminaPenguin) {
   // Use names other than termina/penguin.  Will not mount sshfs.
-  restart_id_ = CrostiniManager::GetInstance()->RestartCrostini(
-      profile(), kVmName, kContainerName,
+  restart_id_ = crostini_manager()->RestartCrostini(
+      kVmName, kContainerName,
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
                      base::Unretained(this), run_loop()->QuitClosure()),
       this);
@@ -567,16 +537,16 @@
 }
 
 TEST_F(CrostiniManagerRestartTest, MultiRestartAllowed) {
-  CrostiniManager::GetInstance()->RestartCrostini(
-      profile(), kVmName, kContainerName,
+  crostini_manager()->RestartCrostini(
+      kVmName, kContainerName,
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
-  CrostiniManager::GetInstance()->RestartCrostini(
-      profile(), kVmName, kContainerName,
+  crostini_manager()->RestartCrostini(
+      kVmName, kContainerName,
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
-  CrostiniManager::GetInstance()->RestartCrostini(
-      profile(), kVmName, kContainerName,
+  crostini_manager()->RestartCrostini(
+      kVmName, kContainerName,
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
   run_loop()->Run();
@@ -609,8 +579,8 @@
           &CrostiniManagerRestartTest_MountForTerminaPenguin_Test::SshfsMount));
 
   // Use termina/penguin to perform mount.
-  restart_id_ = CrostiniManager::GetInstance()->RestartCrostini(
-      profile(), kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
+  restart_id_ = crostini_manager()->RestartCrostini(
+      kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
       base::BindOnce(&CrostiniManagerRestartTest::RestartCrostiniCallback,
                      base::Unretained(this), run_loop()->QuitClosure()));
   run_loop()->Run();
diff --git a/chrome/browser/chromeos/crostini/crostini_package_installer_service.cc b/chrome/browser/chromeos/crostini/crostini_package_installer_service.cc
index 9af3055..47f505e6 100644
--- a/chrome/browser/chromeos/crostini/crostini_package_installer_service.cc
+++ b/chrome/browser/chromeos/crostini/crostini_package_installer_service.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/no_destructor.h"
+#include "chrome/browser/chromeos/crostini/crostini_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
@@ -34,7 +35,10 @@
   CrostiniPackageInstallerServiceFactory()
       : BrowserContextKeyedServiceFactory(
             "CrostiniPackageInstallerService",
-            BrowserContextDependencyManager::GetInstance()) {}
+            BrowserContextDependencyManager::GetInstance()) {
+    DependsOn(CrostiniManagerFactory::GetInstance());
+  }
+
   ~CrostiniPackageInstallerServiceFactory() override = default;
 
   // BrowserContextKeyedServiceFactory:
@@ -55,13 +59,15 @@
 CrostiniPackageInstallerService::CrostiniPackageInstallerService(
     Profile* profile)
     : profile_(profile), weak_ptr_factory_(this) {
-  CrostiniManager::GetInstance()->AddInstallLinuxPackageProgressObserver(
-      profile, this);
+  CrostiniManager::GetForProfile(profile)
+      ->AddInstallLinuxPackageProgressObserver(this);
 }
 
-CrostiniPackageInstallerService::~CrostiniPackageInstallerService() {
-  CrostiniManager::GetInstance()->RemoveInstallLinuxPackageProgressObserver(
-      profile_, this);
+CrostiniPackageInstallerService::~CrostiniPackageInstallerService() = default;
+
+void CrostiniPackageInstallerService::Shutdown() {
+  CrostiniManager::GetForProfile(profile_)
+      ->RemoveInstallLinuxPackageProgressObserver(this);
 }
 
 void CrostiniPackageInstallerService::NotificationClosed(
@@ -90,8 +96,8 @@
     const std::string& container_name,
     const std::string& package_path,
     CrostiniManager::InstallLinuxPackageCallback callback) {
-  CrostiniManager::GetInstance()->InstallLinuxPackage(
-      profile_, vm_name, container_name, package_path,
+  CrostiniManager::GetForProfile(profile_)->InstallLinuxPackage(
+      vm_name, container_name, package_path,
       base::BindOnce(&CrostiniPackageInstallerService::OnInstallLinuxPackage,
                      weak_ptr_factory_.GetWeakPtr(), vm_name, container_name,
                      std::move(callback)));
diff --git a/chrome/browser/chromeos/crostini/crostini_package_installer_service.h b/chrome/browser/chromeos/crostini/crostini_package_installer_service.h
index ff5b487..474ce0d 100644
--- a/chrome/browser/chromeos/crostini/crostini_package_installer_service.h
+++ b/chrome/browser/chromeos/crostini/crostini_package_installer_service.h
@@ -25,6 +25,9 @@
   explicit CrostiniPackageInstallerService(Profile* profile);
   ~CrostiniPackageInstallerService() override;
 
+  // KeyedService:
+  void Shutdown() override;
+
   void NotificationClosed(CrostiniPackageInstallerNotification* notification);
 
   // Install a Linux package. If successfully started, a system notification
diff --git a/chrome/browser/chromeos/crostini/crostini_registry_service.cc b/chrome/browser/chromeos/crostini/crostini_registry_service.cc
index edc952d..c27bfd2 100644
--- a/chrome/browser/chromeos/crostini/crostini_registry_service.cc
+++ b/chrome/browser/chromeos/crostini/crostini_registry_service.cc
@@ -791,9 +791,8 @@
       break;
   }
 
-  crostini::CrostiniManager::GetInstance()->GetContainerAppIcons(
-      profile_, registration->VmName(), registration->ContainerName(),
-      desktop_file_ids,
+  crostini::CrostiniManager::GetForProfile(profile_)->GetContainerAppIcons(
+      registration->VmName(), registration->ContainerName(), desktop_file_ids,
       app_list::AppListConfig::instance().grid_icon_dimension(), icon_scale,
       base::BindOnce(&CrostiniRegistryService::OnContainerAppIcon,
                      weak_ptr_factory_.GetWeakPtr(), app_id, scale_factor));
diff --git a/chrome/browser/chromeos/crostini/crostini_remover.cc b/chrome/browser/chromeos/crostini/crostini_remover.cc
index 16d2f8b..c0e66792 100644
--- a/chrome/browser/chromeos/crostini/crostini_remover.cc
+++ b/chrome/browser/chromeos/crostini/crostini_remover.cc
@@ -35,7 +35,7 @@
 
 void CrostiniRemover::RemoveCrostini() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  CrostiniManager::GetInstance()->InstallTerminaComponent(
+  CrostiniManager::GetForProfile(profile_)->InstallTerminaComponent(
       base::BindOnce(&CrostiniRemover::OnComponentLoaded, this));
 }
 
@@ -44,7 +44,7 @@
     std::move(callback_).Run(ConciergeClientResult::UNKNOWN_ERROR);
     return;
   }
-  CrostiniManager::GetInstance()->StartConcierge(
+  CrostiniManager::GetForProfile(profile_)->StartConcierge(
       base::BindOnce(&CrostiniRemover::OnConciergeStarted, this));
 }
 
@@ -53,9 +53,8 @@
     std::move(callback_).Run(ConciergeClientResult::UNKNOWN_ERROR);
     return;
   }
-  CrostiniManager::GetInstance()->StopVm(
-      profile_, vm_name_,
-      base::BindOnce(&CrostiniRemover::StopVmFinished, this));
+  CrostiniManager::GetForProfile(profile_)->StopVm(
+      vm_name_, base::BindOnce(&CrostiniRemover::StopVmFinished, this));
 }
 
 void CrostiniRemover::StopVmFinished(ConciergeClientResult result) {
@@ -68,8 +67,8 @@
       vm_name_, container_name_);
   CrostiniMimeTypesServiceFactory::GetForProfile(profile_)->ClearMimeTypes(
       vm_name_, container_name_);
-  CrostiniManager::GetInstance()->DestroyDiskImage(
-      CryptohomeIdForProfile(profile_), base::FilePath(vm_name_),
+  CrostiniManager::GetForProfile(profile_)->DestroyDiskImage(
+      base::FilePath(vm_name_),
       vm_tools::concierge::StorageLocation::STORAGE_CRYPTOHOME_ROOT,
       base::BindOnce(&CrostiniRemover::DestroyDiskImageFinished, this));
 }
@@ -81,7 +80,7 @@
     return;
   }
   // Only set kCrostiniEnabled to false once cleanup is completely finished.
-  CrostiniManager::GetInstance()->StopConcierge(
+  CrostiniManager::GetForProfile(profile_)->StopConcierge(
       base::BindOnce(&CrostiniRemover::StopConciergeFinished, this));
 }
 
diff --git a/chrome/browser/chromeos/crostini/crostini_share_path.cc b/chrome/browser/chromeos/crostini/crostini_share_path.cc
index eaea49e..d63b948 100644
--- a/chrome/browser/chromeos/crostini/crostini_share_path.cc
+++ b/chrome/browser/chromeos/crostini/crostini_share_path.cc
@@ -29,8 +29,8 @@
     std::string path,
     base::OnceCallback<void(bool, std::string)> callback) {
   base::Optional<vm_tools::concierge::VmInfo> vm_info =
-      crostini::CrostiniManager::GetInstance()->GetVmInfo(profile,
-                                                          std::move(vm_name));
+      crostini::CrostiniManager::GetForProfile(profile)->GetVmInfo(
+          std::move(vm_name));
   if (!vm_info) {
     std::move(callback).Run(false, "Cannot share, VM not running");
     return;
diff --git a/chrome/browser/chromeos/crostini/crostini_share_path_unittest.cc b/chrome/browser/chromeos/crostini/crostini_share_path_unittest.cc
index 9366008..5e1a2bff 100644
--- a/chrome/browser/chromeos/crostini/crostini_share_path_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_share_path_unittest.cc
@@ -67,27 +67,15 @@
   }
 
   CrostiniSharePathTest()
-      : fake_seneschal_client_(new chromeos::FakeSeneschalClient()),
-        fake_concierge_client_(new chromeos::FakeConciergeClient()),
-        fake_cicerone_client_(new chromeos::FakeCiceroneClient()),
-        scoped_task_environment_(
+      : scoped_task_environment_(
             base::test::ScopedTaskEnvironment::MainThreadType::UI),
         test_browser_thread_bundle_(
             content::TestBrowserThreadBundle::REAL_IO_THREAD) {
-    // Initialization between D-Bus, fake clients, and the singleton
-    // CrostiniManager is tricky since CrostiniManager is a global singleton and
-    // doesn't add itself as an observer for the new Concierge/Cicerone clients
-    // created for each test.  We must first get a handle on the D-Bus setter
-    // and initialize it, then reset CrostiniManager and add it as an observer
-    // for the clients in this test, then set the fake clients into D-Bus.
-    auto dbus_setter = chromeos::DBusThreadManager::GetSetterForTesting();
     chromeos::DBusThreadManager::Initialize();
-    CrostiniManager::GetInstance()->ResetForTesting();
-    fake_concierge_client_->AddObserver(CrostiniManager::GetInstance());
-    fake_cicerone_client_->AddObserver(CrostiniManager::GetInstance());
-    dbus_setter->SetConciergeClient(base::WrapUnique(fake_concierge_client_));
-    dbus_setter->SetCiceroneClient(base::WrapUnique(fake_cicerone_client_));
-    dbus_setter->SetSeneschalClient(base::WrapUnique(fake_seneschal_client_));
+    fake_concierge_client_ = static_cast<chromeos::FakeConciergeClient*>(
+        chromeos::DBusThreadManager::Get()->GetConciergeClient());
+    fake_seneschal_client_ = static_cast<chromeos::FakeSeneschalClient*>(
+        chromeos::DBusThreadManager::Get()->GetSeneschalClient());
   }
 
   ~CrostiniSharePathTest() override { chromeos::DBusThreadManager::Shutdown(); }
@@ -109,7 +97,6 @@
   // Owned by chromeos::DBusThreadManager
   chromeos::FakeSeneschalClient* fake_seneschal_client_;
   chromeos::FakeConciergeClient* fake_concierge_client_;
-  chromeos::FakeCiceroneClient* fake_cicerone_client_;
 
   std::unique_ptr<base::RunLoop>
       run_loop_;  // run_loop_ must be created on the UI thread.
@@ -127,8 +114,8 @@
   start_vm_response.mutable_vm_info()->set_seneschal_server_handle(123);
   fake_concierge_client_->set_start_vm_response(start_vm_response);
 
-  CrostiniManager::GetInstance()->StartTerminaVm(
-      "test", "vm-running-success", base::FilePath("path"),
+  CrostiniManager::GetForProfile(profile())->StartTerminaVm(
+      "vm-running-success", base::FilePath("path"),
       base::BindOnce(
           &CrostiniSharePathTest::SharePathSuccessStartTerminaVmCallback,
           base::Unretained(this)));
@@ -146,8 +133,8 @@
   share_path_response.set_failure_reason("test failure");
   fake_seneschal_client_->set_share_path_response(share_path_response);
 
-  CrostiniManager::GetInstance()->StartTerminaVm(
-      "test", "vm-running-error", base::FilePath("path"),
+  CrostiniManager::GetForProfile(profile())->StartTerminaVm(
+      "vm-running-error", base::FilePath("path"),
       base::BindOnce(
           &CrostiniSharePathTest::SharePathErrorStartTerminaVmCallback,
           base::Unretained(this)));
diff --git a/chrome/browser/chromeos/crostini/crostini_util.cc b/chrome/browser/chromeos/crostini/crostini_util.cc
index 659f95a..be7814ae 100644
--- a/chrome/browser/chromeos/crostini/crostini_util.cc
+++ b/chrome/browser/chromeos/crostini/crostini_util.cc
@@ -92,15 +92,15 @@
 
 Browser* CreateTerminal(const AppLaunchParams& launch_params,
                         const GURL& vsh_in_crosh_url) {
-  return crostini::CrostiniManager::GetInstance()->CreateContainerTerminal(
-      launch_params, vsh_in_crosh_url);
+  return crostini::CrostiniManager::CreateContainerTerminal(launch_params,
+                                                            vsh_in_crosh_url);
 }
 
 void ShowTerminal(const AppLaunchParams& launch_params,
                   const GURL& vsh_in_crosh_url,
                   Browser* browser) {
-  crostini::CrostiniManager::GetInstance()->ShowContainerTerminal(
-      launch_params, vsh_in_crosh_url, browser);
+  crostini::CrostiniManager::ShowContainerTerminal(launch_params,
+                                                   vsh_in_crosh_url, browser);
   browser->window()->GetNativeWindow()->SetProperty(
       kOverrideWindowIconResourceIdKey, IDR_LOGO_CROSTINI_TERMINAL);
 }
@@ -118,8 +118,8 @@
       chrome_launcher_controller->crostini_app_window_shelf_controller();
   DCHECK_NE(observer, nullptr);
   observer->OnAppLaunchRequested(app_id, display_id);
-  crostini::CrostiniManager::GetInstance()->LaunchContainerApplication(
-      profile, registration.VmName(), registration.ContainerName(),
+  crostini::CrostiniManager::GetForProfile(profile)->LaunchContainerApplication(
+      registration.VmName(), registration.ContainerName(),
       registration.DesktopFileId(), files,
       base::BindOnce(OnContainerApplicationLaunched, app_id));
 }
@@ -238,8 +238,8 @@
 }
 
 bool IsCrostiniRunning(Profile* profile) {
-  return crostini::CrostiniManager::GetInstance()->IsVmRunning(
-      profile, kCrostiniDefaultVmName);
+  return crostini::CrostiniManager::GetForProfile(profile)->IsVmRunning(
+      kCrostiniDefaultVmName);
 }
 
 void LaunchCrostiniApp(Profile* profile,
@@ -255,8 +255,8 @@
   ChromeLauncherController* chrome_controller =
       ChromeLauncherController::instance();
   if (chrome_controller &&
-      !crostini::CrostiniManager::GetInstance()->IsContainerRunning(
-          profile, vm_name, container_name)) {
+      !crostini::CrostiniManager::GetForProfile(profile)->IsContainerRunning(
+          vm_name, container_name)) {
     chrome_controller->GetShelfSpinnerController()->AddSpinnerToShelf(
         app_id, std::make_unique<ShelfSpinnerItemController>(app_id));
   }
@@ -266,7 +266,11 @@
                        const std::string& app_id,
                        int64_t display_id,
                        const std::vector<std::string>& files) {
-  auto* crostini_manager = crostini::CrostiniManager::GetInstance();
+  // Policies can change under us, and crostini may now be forbidden.
+  if (!IsCrostiniUIAllowedForProfile(profile)) {
+    return;
+  }
+  auto* crostini_manager = crostini::CrostiniManager::GetForProfile(profile);
   crostini::CrostiniRegistryService* registry_service =
       crostini::CrostiniRegistryServiceFactory::GetForProfile(profile);
   base::Optional<crostini::CrostiniRegistryService::Registration> registration =
@@ -318,7 +322,7 @@
       base::BindOnce(&AddSpinner, app_id, profile, vm_name, container_name),
       base::TimeDelta::FromMilliseconds(kDelayBeforeSpinnerMs));
   crostini_manager->RestartCrostini(
-      profile, vm_name, container_name,
+      vm_name, container_name,
       base::BindOnce(OnCrostiniRestarted, app_id, browser,
                      std::move(launch_closure)));
 }
diff --git a/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc b/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc
index 21965ea..57fbf16 100644
--- a/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc
+++ b/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc
@@ -106,8 +106,8 @@
   Profile* profile = ProfileManager::GetPrimaryUserProfile();
   if (IsCrostiniEnabled(profile) &&
       request.owner_id() == CryptohomeIdForProfile(profile)) {
-    crostini::CrostiniManager::GetInstance()->LaunchContainerTerminal(
-        profile, request.vm_name(), request.container_name(),
+    crostini::CrostiniManager::GetForProfile(profile)->LaunchContainerTerminal(
+        request.vm_name(), request.container_name(),
         std::vector<std::string>(request.params().begin(),
                                  request.params().end()));
   }
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
index 4c50be9..bfa4065bb 100644
--- a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
@@ -540,11 +540,12 @@
   EnableCrostiniForProfile(&scoped_feature_list);
 
   // Setup CrostiniManager for testing.
-  crostini::CrostiniManager::GetInstance()->set_skip_restart_for_testing();
+  crostini::CrostiniManager* crostini_manager =
+      crostini::CrostiniManager::GetForProfile(browser()->profile());
+  crostini_manager->set_skip_restart_for_testing();
   vm_tools::concierge::VmInfo vm_info;
-  crostini::CrostiniManager::GetInstance()->AddRunningVmForTesting(
-      CryptohomeIdForProfile(browser()->profile()), kCrostiniDefaultVmName,
-      std::move(vm_info));
+  crostini_manager->AddRunningVmForTesting(kCrostiniDefaultVmName,
+                                           std::move(vm_info));
 
   ExpectCrostiniMount();
 
@@ -565,7 +566,8 @@
 IN_PROC_BROWSER_TEST_F(FileManagerPrivateApiTest, CrostiniIncognito) {
   base::test::ScopedFeatureList scoped_feature_list;
   EnableCrostiniForProfile(&scoped_feature_list);
-  crostini::CrostiniManager::GetInstance()->set_skip_restart_for_testing();
+  crostini::CrostiniManager::GetForProfile(browser()->profile())
+      ->set_skip_restart_for_testing();
   ExpectCrostiniMount();
 
   scoped_refptr<extensions::FileManagerPrivateMountCrostiniContainerFunction>
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
index 9fab10e44..a7b752c9 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
@@ -661,8 +661,8 @@
   Profile* profile =
       Profile::FromBrowserContext(browser_context())->GetOriginalProfile();
   DCHECK(IsCrostiniEnabled(profile));
-  crostini::CrostiniManager::GetInstance()->RestartCrostini(
-      profile, kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
+  crostini::CrostiniManager::GetForProfile(profile)->RestartCrostini(
+      kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
       base::BindOnce(
           &FileManagerPrivateMountCrostiniContainerFunction::RestartCallback,
           this));
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
index 00382c2..175a9eb9 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
@@ -1027,7 +1027,8 @@
     // crostini interface allowed for testing without such tight coupling.
     crostini_volume_ = std::make_unique<CrostiniTestVolume>();
     profile()->GetPrefs()->SetBoolean(crostini::prefs::kCrostiniEnabled, true);
-    crostini::CrostiniManager::GetInstance()->set_skip_restart_for_testing();
+    crostini::CrostiniManager::GetForProfile(profile()->GetOriginalProfile())
+        ->set_skip_restart_for_testing();
     chromeos::DBusThreadManager* dbus_thread_manager =
         chromeos::DBusThreadManager::Get();
     static_cast<chromeos::FakeCrosDisksClient*>(
diff --git a/chrome/browser/chromeos/file_manager/file_manager_jstest.cc b/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
index cd77754..815bfe6 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
@@ -121,8 +121,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ThumbnailLoader) {
-  RunTest(base::FilePath(
-      FILE_PATH_LITERAL("foreground/js/thumbnail_loader_unittest.html")));
+  RunGeneratedTest("/foreground/js/thumbnail_loader_unittest.html");
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, MetadataCacheItem) {
diff --git a/chrome/browser/chromeos/file_manager/image_loader_jstest.cc b/chrome/browser/chromeos/file_manager/image_loader_jstest.cc
index 2de91a72..a2ef3b55 100644
--- a/chrome/browser/chromeos/file_manager/image_loader_jstest.cc
+++ b/chrome/browser/chromeos/file_manager/image_loader_jstest.cc
@@ -11,18 +11,17 @@
 };
 
 IN_PROC_BROWSER_TEST_F(ImageLoaderJsTest, ImageLoaderClientTest) {
-  RunTest(base::FilePath(FILE_PATH_LITERAL(
-      "image_loader_client_unittest.html")));
+  RunGeneratedTest("/image_loader_client_unittest.html");
 }
 
 IN_PROC_BROWSER_TEST_F(ImageLoaderJsTest, CacheTest) {
-  RunTest(base::FilePath(FILE_PATH_LITERAL("cache_unittest.html")));
+  RunGeneratedTest("/cache_unittest.html");
 }
 
 IN_PROC_BROWSER_TEST_F(ImageLoaderJsTest, ImageLoaderTest) {
-  RunTest(base::FilePath(FILE_PATH_LITERAL("image_loader_unittest.html")));
+  RunGeneratedTest("/image_loader_unittest.html");
 }
 
 IN_PROC_BROWSER_TEST_F(ImageLoaderJsTest, PiexLoaderTest) {
-  RunTest(base::FilePath(FILE_PATH_LITERAL("piex_loader_unittest.html")));
+  RunGeneratedTest("/piex_loader_unittest.html");
 }
diff --git a/chrome/browser/chromeos/file_manager/volume_manager.cc b/chrome/browser/chromeos/file_manager/volume_manager.cc
index d894f62f..c4b5444 100644
--- a/chrome/browser/chromeos/file_manager/volume_manager.cc
+++ b/chrome/browser/chromeos/file_manager/volume_manager.cc
@@ -553,10 +553,11 @@
   DoMountEvent(chromeos::MOUNT_ERROR_NONE, std::move(volume));
 
   // Listen for crostini container shutdown and remove volume.
-  crostini::CrostiniManager::GetInstance()->AddShutdownContainerCallback(
-      profile_, kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
-      base::BindOnce(&VolumeManager::RemoveSshfsCrostiniVolume,
-                     weak_ptr_factory_.GetWeakPtr(), sshfs_mount_path));
+  crostini::CrostiniManager::GetForProfile(profile_)
+      ->AddShutdownContainerCallback(
+          kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
+          base::BindOnce(&VolumeManager::RemoveSshfsCrostiniVolume,
+                         weak_ptr_factory_.GetWeakPtr(), sshfs_mount_path));
 }
 
 void VolumeManager::RemoveSshfsCrostiniVolume(
diff --git a/chrome/browser/chromeos/login/session/chrome_session_manager.cc b/chrome/browser/chromeos/login/session/chrome_session_manager.cc
index 5b8afb0..6c9e7fe 100644
--- a/chrome/browser/chromeos/login/session/chrome_session_manager.cc
+++ b/chrome/browser/chromeos/login/session/chrome_session_manager.cc
@@ -144,8 +144,11 @@
       policy::AppInstallEventLogManagerWrapper::CreateForProfile(user_profile);
     }
     arc::ArcServiceLauncher::Get()->OnPrimaryUserProfilePrepared(user_profile);
-    crostini::CrostiniManager::GetInstance()->MaybeUpgradeCrostini(
-        user_profile);
+
+    crostini::CrostiniManager* crostini_manager =
+        crostini::CrostiniManager::GetForProfile(user_profile);
+    if (crostini_manager)
+      crostini_manager->MaybeUpgradeCrostini();
 
     if (user->GetType() == user_manager::USER_TYPE_CHILD) {
       ScreenTimeControllerFactory::GetForBrowserContext(user_profile);
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.cc b/chrome/browser/chromeos/login/session/user_session_manager.cc
index f4469d8..8a01995 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager.cc
+++ b/chrome/browser/chromeos/login/session/user_session_manager.cc
@@ -1422,7 +1422,11 @@
       policy::AppInstallEventLogManagerWrapper::CreateForProfile(profile);
     }
     arc::ArcServiceLauncher::Get()->OnPrimaryUserProfilePrepared(profile);
-    crostini::CrostiniManager::GetInstance()->MaybeUpgradeCrostini(profile);
+
+    crostini::CrostiniManager* crostini_manager =
+        crostini::CrostiniManager::GetForProfile(profile);
+    if (crostini_manager)
+      crostini_manager->MaybeUpgradeCrostini();
 
     TetherService* tether_service = TetherService::Get(profile);
     if (tether_service)
diff --git a/chrome/browser/devtools/chrome_devtools_manager_delegate.cc b/chrome/browser/devtools/chrome_devtools_manager_delegate.cc
index 5719461..b78a02e 100644
--- a/chrome/browser/devtools/chrome_devtools_manager_delegate.cc
+++ b/chrome/browser/devtools/chrome_devtools_manager_delegate.cc
@@ -26,6 +26,7 @@
 #include "components/guest_view/browser/guest_view_base.h"
 #include "content/public/browser/devtools_agent_host.h"
 #include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/extension_host.h"
 #include "extensions/browser/extension_registry.h"
@@ -129,11 +130,12 @@
   return extension_name;
 }
 
-bool ChromeDevToolsManagerDelegate::AllowInspectingWebContents(
-    content::WebContents* web_contents) {
-  return AllowInspection(
-      Profile::FromBrowserContext(web_contents->GetBrowserContext()),
-      web_contents);
+bool ChromeDevToolsManagerDelegate::AllowInspectingRenderFrameHost(
+    content::RenderFrameHost* rfh) {
+  Profile* profile =
+      Profile::FromBrowserContext(rfh->GetProcess()->GetBrowserContext());
+  return AllowInspection(profile, extensions::ProcessManager::Get(profile)
+                                      ->GetExtensionForRenderFrameHost(rfh));
 }
 
 // static
diff --git a/chrome/browser/devtools/chrome_devtools_manager_delegate.h b/chrome/browser/devtools/chrome_devtools_manager_delegate.h
index 7be478c..f5ac9d7b 100644
--- a/chrome/browser/devtools/chrome_devtools_manager_delegate.h
+++ b/chrome/browser/devtools/chrome_devtools_manager_delegate.h
@@ -65,7 +65,7 @@
   void DisposeBrowserContext(content::BrowserContext*,
                              DisposeCallback callback) override;
 
-  bool AllowInspectingWebContents(content::WebContents* web_contents) override;
+  bool AllowInspectingRenderFrameHost(content::RenderFrameHost* rfh) override;
   void ClientAttached(content::DevToolsAgentHost* agent_host,
                       content::DevToolsAgentHostClient* client) override;
   void ClientDetached(content::DevToolsAgentHost* agent_host,
diff --git a/chrome/browser/devtools/device/adb/mock_adb_server.cc b/chrome/browser/devtools/device/adb/mock_adb_server.cc
index f4bd58f..c844f515 100644
--- a/chrome/browser/devtools/device/adb/mock_adb_server.cc
+++ b/chrome/browser/devtools/device/adb/mock_adb_server.cc
@@ -623,17 +623,18 @@
 }
 
 void StartMockAdbServer(FlushMode flush_mode) {
+  base::RunLoop run_loop;
   BrowserThread::PostTaskAndReply(
       BrowserThread::IO, FROM_HERE,
       base::BindOnce(&StartMockAdbServerOnIOThread, flush_mode),
-      base::RunLoop::QuitCurrentWhenIdleClosureDeprecated());
-  content::RunMessageLoop();
+      run_loop.QuitClosure());
+  run_loop.Run();
 }
 
 void StopMockAdbServer() {
-  BrowserThread::PostTaskAndReply(
-      BrowserThread::IO, FROM_HERE,
-      base::BindOnce(&StopMockAdbServerOnIOThread),
-      base::RunLoop::QuitCurrentWhenIdleClosureDeprecated());
-  content::RunMessageLoop();
+  base::RunLoop run_loop;
+  BrowserThread::PostTaskAndReply(BrowserThread::IO, FROM_HERE,
+                                  base::BindOnce(&StopMockAdbServerOnIOThread),
+                                  run_loop.QuitClosure());
+  run_loop.Run();
 }
diff --git a/chrome/browser/devtools/devtools_sanity_browsertest.cc b/chrome/browser/devtools/devtools_sanity_browsertest.cc
index d5defb18..7641feb3 100644
--- a/chrome/browser/devtools/devtools_sanity_browsertest.cc
+++ b/chrome/browser/devtools/devtools_sanity_browsertest.cc
@@ -1898,9 +1898,8 @@
   // Installs an extensions, emulating that it has been force-installed by
   // policy.
   // Contains assertions - callers should wrap calls of this method in
-  // |ASSERT_NO_FATAL_FAILURE|. Fills |*out_web_contents| with a |WebContents|
-  // that belongs to the force-installed extension.
-  void ForceInstallExtension(content::WebContents** out_web_contents) {
+  // |ASSERT_NO_FATAL_FAILURE|.
+  void ForceInstallExtension(std::string* extension_id) {
     base::FilePath crx_path;
     base::PathService::Get(chrome::DIR_TEST_DATA, &crx_path);
     crx_path = crx_path.AppendASCII("devtools")
@@ -1909,8 +1908,15 @@
     const Extension* extension = InstallExtension(
         crx_path, 1, extensions::Manifest::EXTERNAL_POLICY_DOWNLOAD);
     ASSERT_TRUE(extension);
+    *extension_id = extension->id();
+  }
 
-    GURL url("chrome-extension://" + extension->id() + "/options.html");
+  // Same as above, but also fills |*out_web_contents| with a |WebContents|
+  // that has been navigated to the force-installed extension.
+  void ForceInstallExtensionAndOpen(content::WebContents** out_web_contents) {
+    std::string extension_id;
+    ForceInstallExtension(&extension_id);
+    GURL url("chrome-extension://" + extension_id + "/options.html");
     ui_test_utils::NavigateToURL(browser(), url);
     content::WebContents* web_contents =
         browser()->tab_strip_model()->GetWebContentsAt(0);
@@ -1926,13 +1932,38 @@
                            kDisallowedForForceInstalledExtensions));
 
   content::WebContents* web_contents = nullptr;
-  ASSERT_NO_FATAL_FAILURE(ForceInstallExtension(&web_contents));
+  ASSERT_NO_FATAL_FAILURE(ForceInstallExtensionAndOpen(&web_contents));
 
   DevToolsWindow::OpenDevToolsWindow(web_contents);
   auto agent_host = content::DevToolsAgentHost::GetOrCreateFor(web_contents);
   ASSERT_FALSE(DevToolsWindow::FindDevToolsWindow(agent_host.get()));
 }
 
+IN_PROC_BROWSER_TEST_F(
+    DevToolsSanityExtensionTest,
+    PolicyDisallowedForForceInstalledExtensionsAfterNavigation) {
+  browser()->profile()->GetPrefs()->SetInteger(
+      prefs::kDevToolsAvailability,
+      static_cast<int>(policy::DeveloperToolsPolicyHandler::Availability::
+                           kDisallowedForForceInstalledExtensions));
+
+  std::string extension_id;
+  ASSERT_NO_FATAL_FAILURE(ForceInstallExtension(&extension_id));
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetWebContentsAt(0);
+
+  // It's possible to open DevTools for about:blank.
+  ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
+  DevToolsWindow::OpenDevToolsWindow(web_contents);
+  auto agent_host = content::DevToolsAgentHost::GetOrCreateFor(web_contents);
+  ASSERT_TRUE(DevToolsWindow::FindDevToolsWindow(agent_host.get()));
+
+  // Navigating to extension page should close DevTools.
+  ui_test_utils::NavigateToURL(
+      browser(), GURL("chrome-extension://" + extension_id + "/options.html"));
+  ASSERT_FALSE(DevToolsWindow::FindDevToolsWindow(agent_host.get()));
+}
+
 class DevToolsAllowedByCommandLineSwitch : public DevToolsSanityExtensionTest {
  public:
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -1957,7 +1988,7 @@
                            kDisallowedForForceInstalledExtensions));
 
   content::WebContents* web_contents = nullptr;
-  ASSERT_NO_FATAL_FAILURE(ForceInstallExtension(&web_contents));
+  ASSERT_NO_FATAL_FAILURE(ForceInstallExtensionAndOpen(&web_contents));
 
   DevToolsWindow::OpenDevToolsWindow(web_contents);
   auto agent_host = content::DevToolsAgentHost::GetOrCreateFor(web_contents);
diff --git a/chrome/browser/extensions/api/autotest_private/autotest_private_api.cc b/chrome/browser/extensions/api/autotest_private/autotest_private_api.cc
index c862c0f..1ceefb33 100644
--- a/chrome/browser/extensions/api/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/extensions/api/autotest_private/autotest_private_api.cc
@@ -754,8 +754,8 @@
   Profile* profile = Profile::FromBrowserContext(browser_context());
   CrostiniInstallerView::Show(profile);
   CrostiniInstallerView::GetActiveViewForTesting()->Accept();
-  crostini::CrostiniManager::GetInstance()->RestartCrostini(
-      profile, kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
+  crostini::CrostiniManager::GetForProfile(profile)->RestartCrostini(
+      kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
       base::BindOnce(
           &AutotestPrivateRunCrostiniInstallerFunction::CrostiniRestarted,
           this));
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 2f6a37b6..eb561be8 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1535,13 +1535,6 @@
     "Remove a navigation entry when the corresponding history entry has been "
     "deleted.";
 
-const char kRemoveUsageOfDeprecatedGaiaSigninEndpointName[] =
-    "Remove usage of the deprecated GAIA sign-in endpoint";
-const char kRemoveUsageOfDeprecatedGaiaSigninEndpointDescription[] =
-    "The Gaia sign-in endpoint used for full-tab sign-in page is deprecated. "
-    "This flags controls wheter it should no longer be used during a sign-in "
-    " flow.";
-
 const char kRendererSideResourceSchedulerName[] =
     "Renderer side ResourceScheduler";
 const char kRendererSideResourceSchedulerDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index ca1bf03..e10c5cef 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -940,9 +940,6 @@
 extern const char kRemoveNavigationHistoryName[];
 extern const char kRemoveNavigationHistoryDescription[];
 
-extern const char kRemoveUsageOfDeprecatedGaiaSigninEndpointName[];
-extern const char kRemoveUsageOfDeprecatedGaiaSigninEndpointDescription[];
-
 extern const char kRendererSideResourceSchedulerName[];
 extern const char kRendererSideResourceSchedulerDescription[];
 
diff --git a/chrome/browser/importer/in_process_importer_bridge.cc b/chrome/browser/importer/in_process_importer_bridge.cc
index 5832fa6..b6e2661 100644
--- a/chrome/browser/importer/in_process_importer_bridge.cc
+++ b/chrome/browser/importer/in_process_importer_bridge.cc
@@ -174,13 +174,8 @@
 
 #if defined(OS_WIN)
 void InProcessImporterBridge::AddIE7PasswordInfo(
-    const importer::ImporterIE7PasswordInfo& password_info) {
-  IE7PasswordInfo ie7_password_info;
-  ie7_password_info.url_hash = password_info.url_hash;
-  ie7_password_info.encrypted_data = password_info.encrypted_data;
-  ie7_password_info.date_created = password_info.date_created;
-
-  writer_->AddIE7PasswordInfo(ie7_password_info);
+    const importer::ImporterIE7PasswordInfo&) {
+  // TODO(crbug.com/456119): delete AddIE7PasswordInfo
 }
 #endif  // OS_WIN
 
diff --git a/chrome/browser/importer/profile_writer.cc b/chrome/browser/importer/profile_writer.cc
index 2c47e3a..dab027aa 100644
--- a/chrome/browser/importer/profile_writer.cc
+++ b/chrome/browser/importer/profile_writer.cc
@@ -14,7 +14,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread.h"
-#include "build/build_config.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/favicon/favicon_service_factory.h"
@@ -36,10 +35,6 @@
 #include "components/search_engines/template_url.h"
 #include "components/search_engines/template_url_service.h"
 
-#if defined(OS_WIN)
-#include "components/password_manager/core/browser/webdata/password_web_data_service_win.h"
-#endif
-
 using bookmarks::BookmarkModel;
 using bookmarks::BookmarkNode;
 
@@ -96,13 +91,6 @@
       profile_, ServiceAccessType::EXPLICIT_ACCESS)->AddLogin(form);
 }
 
-#if defined(OS_WIN)
-void ProfileWriter::AddIE7PasswordInfo(const IE7PasswordInfo& info) {
-  WebDataServiceFactory::GetPasswordWebDataForProfile(
-      profile_, ServiceAccessType::EXPLICIT_ACCESS)->AddIE7Login(info);
-}
-#endif
-
 void ProfileWriter::AddHistoryPage(const history::URLRows& page,
                                    history::VisitSource visit_source) {
   if (!page.empty())
diff --git a/chrome/browser/importer/profile_writer.h b/chrome/browser/importer/profile_writer.h
index 3913e09..f4d0fe0 100644
--- a/chrome/browser/importer/profile_writer.h
+++ b/chrome/browser/importer/profile_writer.h
@@ -25,10 +25,6 @@
 class AutofillEntry;
 }
 
-#if defined(OS_WIN)
-struct IE7PasswordInfo;
-#endif
-
 // ProfileWriter encapsulates profile for writing entries into it.
 // This object must be invoked on UI thread.
 class ProfileWriter : public base::RefCountedThreadSafe<ProfileWriter> {
@@ -44,10 +40,6 @@
   // Helper methods for adding data to local stores.
   virtual void AddPasswordForm(const autofill::PasswordForm& form);
 
-#if defined(OS_WIN)
-  virtual void AddIE7PasswordInfo(const IE7PasswordInfo& info);
-#endif
-
   virtual void AddHistoryPage(const history::URLRows& page,
                               history::VisitSource visit_source);
 
diff --git a/chrome/browser/media/autoplay_metrics_browsertest.cc b/chrome/browser/media/autoplay_metrics_browsertest.cc
index 028912f..5d909f34 100644
--- a/chrome/browser/media/autoplay_metrics_browsertest.cc
+++ b/chrome/browser/media/autoplay_metrics_browsertest.cc
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/task/post_task.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/ukm/test_ukm_recorder.h"
@@ -19,25 +18,15 @@
 
 class AutoplayMetricsBrowserTest : public InProcessBrowserTest {
  public:
-  using Entry = ukm::builders::Media_Autoplay_Attempt;
-  using CreatedEntry = ukm::builders::DocumentCreated;
-
   void SetUpOnMainThread() override {
     host_resolver()->AddRule("*", "127.0.0.1");
     content::SetupCrossSiteRedirector(embedded_test_server());
     ASSERT_TRUE(embedded_test_server()->Start());
   }
 
-  void TryAutoplay(ukm::TestUkmRecorder& ukm_recorder,
-                   const content::ToRenderFrameHost& adapter) {
-    base::RunLoop run_loop;
-    base::PostDelayedTask(FROM_HERE, run_loop.QuitClosure(),
-                          base::TimeDelta::FromSeconds(10));
-    ukm_recorder.SetOnAddEntryCallback(Entry::kEntryName,
-                                       run_loop.QuitClosure());
+  void TryAutoplay(const content::ToRenderFrameHost& adapter) {
     EXPECT_TRUE(ExecuteScriptWithoutUserGesture(adapter.render_frame_host(),
                                                 "tryPlayback();"));
-    run_loop.Run();
   }
 
   void NavigateFrameAndWait(content::RenderFrameHost* rfh, const GURL& url) {
@@ -81,6 +70,9 @@
 
 IN_PROC_BROWSER_TEST_F(AutoplayMetricsBrowserTest, RecordAutoplayAttemptUkm) {
   ukm::TestAutoSetUkmRecorder test_ukm_recorder;
+  using Entry = ukm::builders::Media_Autoplay_Attempt;
+  using CreatedEntry = ukm::builders::DocumentCreated;
+
   GURL main_url(embedded_test_server()->GetURL("example.com",
                                                "/media/autoplay_iframe.html"));
   GURL foo_url(
@@ -90,25 +82,25 @@
 
   // Navigate main frame, try play.
   NavigateFrameAndWait(web_contents()->GetMainFrame(), main_url);
-  TryAutoplay(test_ukm_recorder, web_contents());
+  TryAutoplay(web_contents());
 
   // Check that we recorded a UKM event using the main frame URL.
   {
     auto ukm_entries = test_ukm_recorder.GetEntriesByName(Entry::kEntryName);
 
-    ASSERT_EQ(1u, ukm_entries.size());
+    EXPECT_EQ(1u, ukm_entries.size());
     test_ukm_recorder.ExpectEntrySourceHasUrl(ukm_entries[0], main_url);
   }
 
   // Navigate sub frame, try play.
   NavigateFrameAndWait(first_child(), foo_url);
-  TryAutoplay(test_ukm_recorder, first_child());
+  TryAutoplay(first_child());
 
   // Check that we recorded a UKM event that is not keyed to any URL.
   {
     auto ukm_entries = test_ukm_recorder.GetEntriesByName(Entry::kEntryName);
 
-    ASSERT_EQ(2u, ukm_entries.size());
+    EXPECT_EQ(2u, ukm_entries.size());
     EXPECT_FALSE(
         test_ukm_recorder.GetSourceForSourceId(ukm_entries[1]->source_id));
 
@@ -130,13 +122,13 @@
 
   // Navigate sub sub frame, try play.
   NavigateFrameAndWait(second_child(), bar_url);
-  TryAutoplay(test_ukm_recorder, second_child());
+  TryAutoplay(second_child());
 
   // Check that we recorded a UKM event that is not keyed to any url.
   {
     auto ukm_entries = test_ukm_recorder.GetEntriesByName(Entry::kEntryName);
 
-    ASSERT_EQ(3u, ukm_entries.size());
+    EXPECT_EQ(3u, ukm_entries.size());
     EXPECT_FALSE(
         test_ukm_recorder.GetSourceForSourceId(ukm_entries[2]->source_id));
 
@@ -158,13 +150,13 @@
 
   // Navigate top frame, try play.
   NavigateFrameAndWait(web_contents()->GetMainFrame(), foo_url);
-  TryAutoplay(test_ukm_recorder, web_contents());
+  TryAutoplay(web_contents());
 
   // Check that we recorded a UKM event using the main frame URL.
   {
     auto ukm_entries = test_ukm_recorder.GetEntriesByName(Entry::kEntryName);
 
-    ASSERT_EQ(4u, ukm_entries.size());
+    EXPECT_EQ(4u, ukm_entries.size());
     test_ukm_recorder.ExpectEntrySourceHasUrl(ukm_entries[3], foo_url);
   }
 }
diff --git a/chrome/browser/offline_pages/offline_page_utils.cc b/chrome/browser/offline_pages/offline_page_utils.cc
index f351c53c..a23e41f 100644
--- a/chrome/browser/offline_pages/offline_page_utils.cc
+++ b/chrome/browser/offline_pages/offline_page_utils.cc
@@ -59,15 +59,17 @@
 void OnGetPagesByURLDone(
     const GURL& url,
     int tab_id,
-    const std::vector<std::string>& namespaces_to_show_in_original_tab,
+    const std::vector<std::string>& namespaces_restricted_to_tab_from_client_id,
     base::OnceCallback<void(const std::vector<OfflinePageItem>&)> callback,
     const MultipleOfflinePageItemResult& pages) {
   std::vector<OfflinePageItem> selected_pages;
   std::string tab_id_str = base::IntToString(tab_id);
 
   // Exclude pages whose tab id does not match.
+  // Note: For this restriction to work offline pages saved to tab-bound
+  // namespaces must have the assigned tab id set to their ClientId::id field.
   for (const auto& page : pages) {
-    if (base::ContainsValue(namespaces_to_show_in_original_tab,
+    if (base::ContainsValue(namespaces_restricted_to_tab_from_client_id,
                             page.client_id.name_space) &&
         page.client_id.id != tab_id_str) {
       continue;
@@ -215,7 +217,7 @@
   offline_page_model->GetPagesByURL(
       url, base::BindOnce(&OnGetPagesByURLDone, url, tab_id,
                           offline_page_model->GetPolicyController()
-                              ->GetNamespacesRestrictedToOriginalTab(),
+                              ->GetNamespacesRestrictedToTabFromClientId(),
                           std::move(callback)));
 }
 
diff --git a/chrome/browser/password_manager/password_store_factory.cc b/chrome/browser/password_manager/password_store_factory.cc
index 0669f55..0452fd49 100644
--- a/chrome/browser/password_manager/password_store_factory.cc
+++ b/chrome/browser/password_manager/password_store_factory.cc
@@ -42,7 +42,6 @@
 
 #if defined(OS_WIN)
 #include "chrome/browser/password_manager/password_manager_util_win.h"
-#include "components/password_manager/core/browser/webdata/password_web_data_service_win.h"
 #elif defined(OS_MACOSX)
 #include "chrome/browser/password_manager/password_store_mac.h"
 #elif defined(OS_CHROMEOS) || defined(OS_ANDROID)
diff --git a/chrome/browser/profiles/off_the_record_profile_impl.cc b/chrome/browser/profiles/off_the_record_profile_impl.cc
index 9426898..c99922a7 100644
--- a/chrome/browser/profiles/off_the_record_profile_impl.cc
+++ b/chrome/browser/profiles/off_the_record_profile_impl.cc
@@ -479,10 +479,16 @@
   // occurs later upon first VideoDecodePerfHistory API request that requires DB
   // access. DB operations will not block the UI thread.
   if (!decode_history) {
-    // TODO(https://crbug/865321): For non guest sessions, this should instead
-    // be a pointer to GetOriginalProfile()->GetVideoDecodePerfHistory().
-    // Passing nullptr while we sort out lifetime issues.
-    media::VideoDecodeStatsDBProvider* seed_db_provider = nullptr;
+    // Use the original profile's DB to seed the OTR VideoDeocdePerfHisotry. The
+    // original DB is treated as read-only, while OTR playbacks will write stats
+    // to the InMemory version (cleared on profile destruction). Guest profiles
+    // don't have a root profile like incognito, meaning they don't have a seed
+    // DB to call on and we can just pass null.
+    media::VideoDecodeStatsDBProvider* seed_db_provider =
+        IsGuestSession() ? nullptr
+                         // Safely passing raw pointer to VideoDecodePerfHistory
+                         // because original profile will outlive this profile.
+                         : GetOriginalProfile()->GetVideoDecodePerfHistory();
 
     auto db_factory =
         std::make_unique<media::InMemoryVideoDecodeStatsDBFactory>(
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index 04a5df8..0214dc50 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -1533,7 +1533,8 @@
       chromeos::multidevice_setup::AuthTokenValidatorFactory::GetForProfile(
           this),
       std::make_unique<
-          chromeos::multidevice_setup::AndroidSmsAppHelperDelegateImpl>(this));
+          chromeos::multidevice_setup::AndroidSmsAppHelperDelegateImpl>(this),
+      chromeos::GcmDeviceInfoProviderImpl::GetInstance());
 }
 
 #endif  // defined(OS_CHROMEOS)
diff --git a/chrome/browser/resources/accessibility/accessibility.html b/chrome/browser/resources/accessibility/accessibility.html
index 57e6405a..2371586 100644
--- a/chrome/browser/resources/accessibility/accessibility.html
+++ b/chrome/browser/resources/accessibility/accessibility.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html>
+<html lang="en">
 <!--
 Copyright (c) 2013 The Chromium Authors. All rights reserved.
 Use of this source code is governed by a BSD-style license that can be
@@ -147,6 +147,5 @@
 
   <h2>Pages:</h2>
   <div id="pages" class="list"></div>
-  <script src="chrome://resources/js/i18n_template.js"></script>
 </body>
 </html>
diff --git a/chrome/browser/resources/md_bookmarks/app.html b/chrome/browser/resources/md_bookmarks/app.html
index 45d7dab..348cd0a 100644
--- a/chrome/browser/resources/md_bookmarks/app.html
+++ b/chrome/browser/resources/md_bookmarks/app.html
@@ -78,7 +78,7 @@
     </div>
     <bookmarks-router></bookmarks-router>
     <bookmarks-command-manager></bookmarks-command-manager>
-    <bookmarks-toast-manager duration="5000"></bookmarks-toast-manager>
+    <bookmarks-toast-manager duration="10000"></bookmarks-toast-manager>
   </template>
   <script src="chrome://bookmarks/app.js"></script>
 </dom-module>
diff --git a/chrome/browser/resources/settings/people_page/sync_page.html b/chrome/browser/resources/settings/people_page/sync_page.html
index 6007ce8f..d1e99ea 100644
--- a/chrome/browser/resources/settings/people_page/sync_page.html
+++ b/chrome/browser/resources/settings/people_page/sync_page.html
@@ -410,8 +410,7 @@
               $i18n{personalizeGoogleServicesTitle}
             </div>
             <paper-icon-button-light actionable class="icon-external">
-              <button aria-label="$i18n{personalizeGoogleServicesTitle}">
-              </button>
+              <button></button>
             </paper-icon-button-light>
           </a>
 
@@ -422,7 +421,7 @@
               $i18n{manageSyncedDataTitle}
             </div>
             <paper-icon-button-light actionable class="icon-external">
-              <button aria-label="$i18n{manageSyncedDataTitle}"></button>
+              <button></button>
             </paper-icon-button-light>
           </a>
 
@@ -452,20 +451,29 @@
                 on-paper-radio-group-changed=
                     "onEncryptionRadioSelectionChanged_">
               <cr-radio-button name="encrypt-with-google"
-                  class="list-item" disabled="[[syncPrefs.encryptAllData]]">
+                  class="list-item" disabled="[[syncPrefs.encryptAllData]]"
+                  aria-label="$i18n{encryptWithGoogleCredentialsLabel}">
                 $i18n{encryptWithGoogleCredentialsLabel}
               </cr-radio-button>
-              <cr-radio-button name="encrypt-with-passphrase"
-                  class="list-item" disabled="[[syncPrefs.encryptAllData]]">
-                <template is="dom-if" if="[[syncPrefs.fullEncryptionBody]]">
-                  <span>[[syncPrefs.fullEncryptionBody]]</span>
-                </template>
-                <template is="dom-if" if="[[!syncPrefs.fullEncryptionBody]]">
-                  <span on-click="onLearnMoreTap_">
+              <template is="dom-if" if="[[syncPrefs.fullEncryptionBody]]">
+                <cr-radio-button name="encrypt-with-passphrase"
+                    class="list-item" disabled="[[syncPrefs.encryptAllData]]"
+                    aria-labelledby="fullEncryptionBody">
+                  <span id="fullEncryptionBody">
+                    [[syncPrefs.fullEncryptionBody]]
+                  </span>
+                </cr-radio-button>
+              </template>
+              <template is="dom-if" if="[[!syncPrefs.fullEncryptionBody]]">
+                <cr-radio-button name="encrypt-with-passphrase"
+                    class="list-item" disabled="[[syncPrefs.encryptAllData]]"
+                    aria-labelledby="encryptWithSyncPassphraseLabel">
+                  <span id="encryptWithSyncPassphraseLabel"
+                      on-click="onLearnMoreTap_">
                     $i18nRaw{encryptWithSyncPassphraseLabel}
                   </span>
-                </template>
-              </cr-radio-button>
+                </cr-radio-button>
+              </template>
             </paper-radio-group>
           </div>
 
diff --git a/chrome/browser/signin/dice_browsertest.cc b/chrome/browser/signin/dice_browsertest.cc
index 1240265..06d0fab 100644
--- a/chrome/browser/signin/dice_browsertest.cc
+++ b/chrome/browser/signin/dice_browsertest.cc
@@ -277,10 +277,11 @@
 // Handler for ServiceLogin on the embedded test server.
 // Calls the callback with the dice request header, or kNoDiceRequestHeader if
 // there is no Dice header.
-std::unique_ptr<HttpResponse> HandleServiceLoginURL(
+std::unique_ptr<HttpResponse> HandleChromeSigninEmbeddedURL(
     const base::RepeatingCallback<void(const std::string&)>& callback,
     const HttpRequest& request) {
-  if (!net::test_server::ShouldHandle(request, "/ServiceLogin"))
+  if (!net::test_server::ShouldHandle(request,
+                                      "/embedded/setup/chrome/usermenu"))
     return nullptr;
 
   std::string dice_request_header(kNoDiceRequestHeader);
@@ -336,8 +337,8 @@
         base::BindRepeating(&DiceBrowserTestBase::OnTokenRevocationRequest,
                             base::Unretained(this))));
     https_server_.RegisterDefaultHandler(base::BindRepeating(
-        &FakeGaia::HandleServiceLoginURL,
-        base::BindRepeating(&DiceBrowserTestBase::OnServiceLoginRequest,
+        &FakeGaia::HandleChromeSigninEmbeddedURL,
+        base::BindRepeating(&DiceBrowserTestBase::OnChromeSigninEmbeddedRequest,
                             base::Unretained(this))));
     signin::SetDiceAccountReconcilorBlockDelayForTesting(
         kAccountReconcilorDelayMs);
@@ -496,9 +497,9 @@
     dice_request_header_ = dice_request_header;
   }
 
-  void OnServiceLoginRequest(const std::string& dice_request_header) {
+  void OnChromeSigninEmbeddedRequest(const std::string& dice_request_header) {
     dice_request_header_ = dice_request_header;
-    RunClosureIfValid(std::move(service_login_quit_closure_));
+    RunClosureIfValid(std::move(chrome_signin_embedded_quit_closure_));
   }
 
   void OnEnableSyncRequest(base::OnceClosure unblock_response_closure) {
@@ -617,7 +618,7 @@
   base::OnceClosure token_requested_quit_closure_;
   base::OnceClosure token_revoked_quit_closure_;
   base::OnceClosure refresh_token_available_quit_closure_;
-  base::OnceClosure service_login_quit_closure_;
+  base::OnceClosure chrome_signin_embedded_quit_closure_;
   base::OnceClosure unblock_count_quit_closure_;
   base::OnceClosure tokens_loaded_quit_closure_;
   base::OnceClosure google_signin_succeeded_quit_closure_;
@@ -782,13 +783,12 @@
 // Checks that Dice request header is not set from request from WebUI.
 // See https://crbug.com/428396
 IN_PROC_BROWSER_TEST_F(DiceBrowserTest, NoDiceFromWebUI) {
-
   // Navigate to Gaia and from the native tab, which uses an extension.
   ui_test_utils::NavigateToURL(browser(), GURL("chrome:chrome-signin"));
 
   // Check that the request had no Dice request header.
   if (dice_request_header_.empty())
-    WaitForClosure(&service_login_quit_closure_);
+    WaitForClosure(&chrome_signin_embedded_quit_closure_);
   EXPECT_EQ(kNoDiceRequestHeader, dice_request_header_);
   EXPECT_EQ(0, reconcilor_blocked_count_);
   WaitForReconcilorUnblockedCount(0);
diff --git a/chrome/browser/signin/signin_promo.cc b/chrome/browser/signin/signin_promo.cc
index 1117b72..fdd2349 100644
--- a/chrome/browser/signin/signin_promo.cc
+++ b/chrome/browser/signin/signin_promo.cc
@@ -81,12 +81,9 @@
 // |access_point| indicates where the sign in is being initiated.
 // |reason| indicates the purpose of using this URL.
 // |auto_close| whether to close the sign in promo automatically when done.
-// |is_constrained| whether to load the URL in a constrained window, false
-// by default.
 GURL GetPromoURL(signin_metrics::AccessPoint access_point,
                  signin_metrics::Reason reason,
-                 bool auto_close,
-                 bool is_constrained) {
+                 bool auto_close) {
   CHECK_LT(static_cast<int>(access_point),
            static_cast<int>(signin_metrics::AccessPoint::ACCESS_POINT_MAX));
   CHECK_NE(static_cast<int>(access_point),
@@ -106,20 +103,14 @@
     url = net::AppendQueryParameter(url, signin::kSignInPromoQueryKeyAutoClose,
                                     "1");
   }
-  if (is_constrained) {
-    url = net::AppendQueryParameter(
-        url, signin::kSignInPromoQueryKeyConstrained, "1");
-  }
-
   return url;
 }
 
 GURL GetReauthURL(signin_metrics::AccessPoint access_point,
                   signin_metrics::Reason reason,
                   const std::string& email,
-                  bool auto_close,
-                  bool is_constrained) {
-  GURL url = GetPromoURL(access_point, reason, auto_close, is_constrained);
+                  bool auto_close) {
+  GURL url = GetPromoURL(access_point, reason, auto_close);
   url = net::AppendQueryParameter(url, "email", email);
   url = net::AppendQueryParameter(url, "validateEmail", "1");
   return net::AppendQueryParameter(url, "readOnlyEmail", "1");
@@ -135,7 +126,6 @@
 const char kSignInPromoQueryKeyForceKeepData[] = "force_keep_data";
 const char kSignInPromoQueryKeyReason[] = "reason";
 const char kSignInPromoQueryKeySource[] = "source";
-const char kSignInPromoQueryKeyConstrained[] = "constrained";
 const char kSigninPromoLandingURLSuccessPage[] = "success.html";
 
 bool ShouldShowPromoAtStartup(Profile* profile, bool is_new_profile) {
@@ -228,23 +218,13 @@
 GURL GetPromoURLForTab(signin_metrics::AccessPoint access_point,
                        signin_metrics::Reason reason,
                        bool auto_close) {
-  if (base::FeatureList::IsEnabled(
-          features::kRemoveUsageOfDeprecatedGaiaSigninEndpoint)) {
-    // The full-tab sign-in endpoint is deprecated. Use the constrained page for
-    // the full-tab URL as well.
-    return GetPromoURL(access_point, reason, auto_close,
-                       true /* is_constrained */);
-  }
-
-  return GetPromoURL(access_point, reason, auto_close,
-                     false /* is_constrained */);
+  return GetPromoURL(access_point, reason, auto_close);
 }
 
 GURL GetPromoURLForDialog(signin_metrics::AccessPoint access_point,
                           signin_metrics::Reason reason,
                           bool auto_close) {
-  return GetPromoURL(access_point, reason, auto_close,
-                     true /* is_constrained */);
+  return GetPromoURL(access_point, reason, auto_close);
 }
 
 GURL GetReauthURLForDialog(signin_metrics::AccessPoint access_point,
@@ -253,8 +233,7 @@
                            const std::string& account_id) {
   AccountInfo info = AccountTrackerServiceFactory::GetForProfile(profile)
                          ->GetAccountInfo(account_id);
-  return GetReauthURL(access_point, reason, info.email, true /* auto_close */,
-                      true /* is_constrained */);
+  return GetReauthURL(access_point, reason, info.email, true /* auto_close */);
 }
 
 GURL GetReauthURLForTab(signin_metrics::AccessPoint access_point,
@@ -264,24 +243,13 @@
   AccountInfo info =
       AccountTrackerServiceFactory::GetForProfile(profile)->GetAccountInfo(
           account_id);
-
-  if (base::FeatureList::IsEnabled(
-          features::kRemoveUsageOfDeprecatedGaiaSigninEndpoint)) {
-    // The full-tab sign-in endpoint is deprecated. Use the constrained page for
-    // the full-tab URL as well.
-    return GetReauthURL(access_point, reason, info.email, true /* auto_close */,
-                        true /* is_constrained */);
-  }
-
-  return GetReauthURL(access_point, reason, info.email, true /* auto_close */,
-                      false /* is_constrained */);
+  return GetReauthURL(access_point, reason, info.email, true /* auto_close */);
 }
 
 GURL GetReauthURLWithEmailForDialog(signin_metrics::AccessPoint access_point,
                                     signin_metrics::Reason reason,
                                     const std::string& email) {
-  return GetReauthURL(access_point, reason, email, true /* auto_close */,
-                      true /* is_constrained */);
+  return GetReauthURL(access_point, reason, email, true /* auto_close */);
 }
 
 GURL GetSigninURLForDice(Profile* profile, const std::string& email) {
diff --git a/chrome/browser/signin/signin_promo.h b/chrome/browser/signin/signin_promo.h
index 06a342a..f1f5a78 100644
--- a/chrome/browser/signin/signin_promo.h
+++ b/chrome/browser/signin/signin_promo.h
@@ -26,7 +26,6 @@
 extern const char kSignInPromoQueryKeyForceKeepData[];
 extern const char kSignInPromoQueryKeyReason[];
 extern const char kSignInPromoQueryKeySource[];
-extern const char kSignInPromoQueryKeyConstrained[];
 extern const char kSigninPromoLandingURLSuccessPage[];
 
 // Returns true if we should show the sign in promo at startup.
diff --git a/chrome/browser/signin/signin_promo_unittest.cc b/chrome/browser/signin/signin_promo_unittest.cc
index 91f387f..abb794f 100644
--- a/chrome/browser/signin/signin_promo_unittest.cc
+++ b/chrome/browser/signin/signin_promo_unittest.cc
@@ -13,14 +13,12 @@
 
 TEST_F(SigninPromoTest, TestPromoURL) {
   GURL expected_url_1(
-      "chrome://chrome-signin/"
-      "?access_point=0&reason=0&auto_close=1&constrained=1");
+      "chrome://chrome-signin/?access_point=0&reason=0&auto_close=1");
   EXPECT_EQ(expected_url_1,
             GetPromoURLForDialog(
                 signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE,
                 signin_metrics::Reason::REASON_SIGNIN_PRIMARY_ACCOUNT, true));
-  GURL expected_url_2(
-      "chrome://chrome-signin/?access_point=15&reason=3&constrained=1");
+  GURL expected_url_2("chrome://chrome-signin/?access_point=15&reason=3");
   EXPECT_EQ(expected_url_2,
             GetPromoURLForDialog(
                 signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO,
@@ -30,8 +28,8 @@
 TEST_F(SigninPromoTest, TestReauthURL) {
   GURL expected_url_1(
       "chrome://chrome-signin/"
-      "?access_point=0&reason=0&auto_close=1&constrained=1&email=example%"
-      "40domain.com&validateEmail=1&readOnlyEmail=1");
+      "?access_point=0&reason=0&auto_close=1&email=example%40domain.com"
+      "&validateEmail=1&readOnlyEmail=1");
   EXPECT_EQ(expected_url_1,
             GetReauthURLWithEmailForDialog(
                 signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE,
diff --git a/chrome/browser/ui/app_list/arc/arc_default_app_list.cc b/chrome/browser/ui/app_list/arc/arc_default_app_list.cc
index 2f630c8..891a0aa5 100644
--- a/chrome/browser/ui/app_list/arc/arc_default_app_list.cc
+++ b/chrome/browser/ui/app_list/arc/arc_default_app_list.cc
@@ -1,10 +1,11 @@
-// 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/app_list/arc/arc_default_app_list.h"
 
+#include "base/barrier_closure.h"
 #include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
 #include "base/json/json_file_value_serializer.h"
 #include "base/path_service.h"
 #include "base/task/post_task.h"
@@ -34,87 +35,78 @@
 const base::FilePath::CharType kArcDirectory[] = FILE_PATH_LITERAL("arc");
 const base::FilePath::CharType kArcTestDirectory[] =
     FILE_PATH_LITERAL("arc_default_apps");
+const base::FilePath::CharType kArcTestBoardDirectory[] =
+    FILE_PATH_LITERAL("arc_board_default_apps");
+const base::FilePath::CharType kBoardDirectory[] =
+    FILE_PATH_LITERAL("/var/cache/arc_default_apps");
 
 bool use_test_apps_directory = false;
 
-std::unique_ptr<ArcDefaultAppList::AppInfoMap>
-ReadAppsFromFileThread() {
+std::unique_ptr<ArcDefaultAppList::AppInfoMap> ReadAppsFromFileThread(
+    const base::FilePath& base_path) {
+  base::FilePath root_dir;
+  // FileEnumerator does not work with a symbolic link dir. So map link
+  // to real folder in case |base_path| specifies a symbolic link.
+  if (!base::ReadSymbolicLink(base_path, &root_dir))
+    root_dir = base_path;
+
   std::unique_ptr<ArcDefaultAppList::AppInfoMap> apps =
       std::make_unique<ArcDefaultAppList::AppInfoMap>();
 
-  base::FilePath base_path;
-  if (!use_test_apps_directory) {
-    if (!base::PathService::Get(chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS,
-                                &base_path))
-      return apps;
-    base_path = base_path.Append(kArcDirectory);
-  } else {
-    if (!base::PathService::Get(chrome::DIR_TEST_DATA, &base_path))
-      return apps;
-    base_path = base_path.AppendASCII(kArcTestDirectory);
-  }
-
   base::FilePath::StringType extension(".json");
-  base::FileEnumerator json_files(
-      base_path,
-      false,  // Recursive.
-      base::FileEnumerator::FILES);
+  base::FileEnumerator json_files(root_dir,
+                                  false,  // Recursive.
+                                  base::FileEnumerator::FILES);
 
   for (base::FilePath file = json_files.Next(); !file.empty();
       file = json_files.Next()) {
-
-    if (file.MatchesExtension(extension)) {
-      JSONFileValueDeserializer deserializer(file);
-      std::string error_msg;
-      std::unique_ptr<base::Value> app_info =
-          deserializer.Deserialize(nullptr, &error_msg);
-      if (!app_info) {
-        VLOG(2) << "Unable to deserialize json data: " << error_msg
-                << " in file " << file.value() << ".";
-        continue;
-      }
-
-      std::unique_ptr<base::DictionaryValue> app_info_dictionary =
-          base::DictionaryValue::From(std::move(app_info));
-      CHECK(app_info_dictionary);
-
-      std::string name;
-      std::string package_name;
-      std::string activity;
-      std::string app_path;
-      bool oem = false;
-
-      app_info_dictionary->GetString(kName, &name);
-      app_info_dictionary->GetString(kPackageName, &package_name);
-      app_info_dictionary->GetString(kActivity, &activity);
-      app_info_dictionary->GetString(kAppPath, &app_path);
-      app_info_dictionary->GetBoolean(kOem, &oem);
-
-      if (name.empty() ||
-          package_name.empty() ||
-          activity.empty() ||
-          app_path.empty()) {
-        VLOG(2) << "ARC app declaration is incomplete in file " << file.value()
-                << ".";
-        continue;
-      }
-
-      const std::string app_id = ArcAppListPrefs::GetAppId(
-          package_name, activity);
-      std::unique_ptr<ArcDefaultAppList::AppInfo> app =
-          std::make_unique<ArcDefaultAppList::AppInfo>(name,
-                                         package_name,
-                                         activity,
-                                         oem,
-                                         base_path.Append(app_path));
-      apps.get()->insert(
-          std::pair<std::string,
-                    std::unique_ptr<ArcDefaultAppList::AppInfo>>(
-                        app_id, std::move(app)));
-    } else {
+    if (!file.MatchesExtension(extension)) {
       DVLOG(1) << "Not considering: " << file.LossyDisplayName()
                << " (does not have a .json extension)";
+      continue;
     }
+
+    JSONFileValueDeserializer deserializer(file);
+    std::string error_msg;
+    std::unique_ptr<base::Value> app_info =
+        deserializer.Deserialize(nullptr, &error_msg);
+    if (!app_info) {
+      VLOG(2) << "Unable to deserialize json data: " << error_msg << " in file "
+              << file.value() << ".";
+      continue;
+    }
+
+    std::unique_ptr<base::DictionaryValue> app_info_dictionary =
+        base::DictionaryValue::From(std::move(app_info));
+    CHECK(app_info_dictionary);
+
+    std::string name;
+    std::string package_name;
+    std::string activity;
+    std::string app_path;
+    bool oem = false;
+
+    app_info_dictionary->GetString(kName, &name);
+    app_info_dictionary->GetString(kPackageName, &package_name);
+    app_info_dictionary->GetString(kActivity, &activity);
+    app_info_dictionary->GetString(kAppPath, &app_path);
+    app_info_dictionary->GetBoolean(kOem, &oem);
+
+    if (name.empty() || package_name.empty() || activity.empty() ||
+        app_path.empty()) {
+      VLOG(2) << "ARC app declaration is incomplete in file " << file.value()
+              << ".";
+      continue;
+    }
+
+    const std::string app_id =
+        ArcAppListPrefs::GetAppId(package_name, activity);
+    std::unique_ptr<ArcDefaultAppList::AppInfo> app =
+        std::make_unique<ArcDefaultAppList::AppInfo>(
+            name, package_name, activity, oem, root_dir.Append(app_path));
+    apps.get()->insert(
+        std::pair<std::string, std::unique_ptr<ArcDefaultAppList::AppInfo>>(
+            app_id, std::move(app)));
   }
 
   return apps;
@@ -150,17 +142,48 @@
       weak_ptr_factory_(this) {
   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
 
+  // Load default apps from two sources.
+  // /usr/share/google-chrome/extensions/arc - contains default apps for all
+  //     boards that share the same image.
+  // /var/cache/arc_default_apps that is link to
+  //     /usr/share/google-chrome/extensions/arc/BOARD_NAME - contains default
+  //     apps for particular current board.
+  //
+  std::vector<base::FilePath> sources;
+
+  base::FilePath base_path;
+  if (!use_test_apps_directory) {
+    const bool valid_path = base::PathService::Get(
+        chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS, &base_path);
+    DCHECK(valid_path);
+    sources.push_back(base_path.Append(kArcDirectory));
+    sources.push_back(base::FilePath(kBoardDirectory));
+  } else {
+    const bool valid_path =
+        base::PathService::Get(chrome::DIR_TEST_DATA, &base_path);
+    DCHECK(valid_path);
+    sources.push_back(base_path.Append(kArcTestDirectory));
+    sources.push_back(base_path.Append(kArcTestBoardDirectory));
+  }
+
+  // Using base::Unretained(this) here is safe since we own barrier_closure_.
+  barrier_closure_ = base::BarrierClosure(
+      sources.size(),
+      base::BindOnce(&ArcDefaultAppList::OnAppsReady, base::Unretained(this)));
+
   // Once ready OnAppsReady is called.
-  base::PostTaskWithTraitsAndReplyWithResult(
-      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
-      base::Bind(&ReadAppsFromFileThread),
-      base::Bind(&ArcDefaultAppList::OnAppsReady,
-                 weak_ptr_factory_.GetWeakPtr()));
+  for (const auto& source : sources) {
+    base::PostTaskWithTraitsAndReplyWithResult(
+        FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+        base::BindOnce(&ReadAppsFromFileThread, source),
+        base::BindOnce(&ArcDefaultAppList::OnAppsRead,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
 }
 
 ArcDefaultAppList::~ArcDefaultAppList() = default;
 
-void ArcDefaultAppList::OnAppsReady(std::unique_ptr<AppInfoMap> apps) {
+void ArcDefaultAppList::OnAppsRead(std::unique_ptr<AppInfoMap> apps) {
   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
 
   const PrefService* const prefs = profile_->GetPrefs();
@@ -170,6 +193,11 @@
     app_map[entry.first] = std::move(entry.second);
   }
 
+  barrier_closure_.Run();
+}
+
+void ArcDefaultAppList::OnAppsReady() {
+  const PrefService* const prefs = profile_->GetPrefs();
   // Register Play Store as default app. Some services and ArcSupportHost may
   // not be available in tests.
   extensions::ExtensionService* service =
diff --git a/chrome/browser/ui/app_list/arc/arc_default_app_list.h b/chrome/browser/ui/app_list/arc/arc_default_app_list.h
index c1d8929..4b70c53 100644
--- a/chrome/browser/ui/app_list/arc/arc_default_app_list.h
+++ b/chrome/browser/ui/app_list/arc/arc_default_app_list.h
@@ -86,8 +86,10 @@
   }
 
  private:
-  // Called when default apps are ready.
-  void OnAppsReady(std::unique_ptr<AppInfoMap> apps);
+  // Called when default apps are read from the provided source.
+  void OnAppsRead(std::unique_ptr<AppInfoMap> apps);
+  // Called when default apps from all sources are read.
+  void OnAppsReady();
 
   // Unowned pointer.
   Profile* profile_;
@@ -100,6 +102,8 @@
   AppInfoMap visible_apps_;
   // Keeps hidden apps.
   AppInfoMap hidden_apps_;
+  // To wait until all sources with apps are loaded.
+  base::RepeatingClosure barrier_closure_;
 
   base::WeakPtrFactory<ArcDefaultAppList> weak_ptr_factory_;
 
diff --git a/chrome/browser/ui/app_list/crostini/crostini_app_context_menu.cc b/chrome/browser/ui/app_list/crostini/crostini_app_context_menu.cc
index 4e0a278..359e479 100644
--- a/chrome/browser/ui/app_list/crostini/crostini_app_context_menu.cc
+++ b/chrome/browser/ui/app_list/crostini/crostini_app_context_menu.cc
@@ -58,8 +58,8 @@
 
     case ash::MENU_CLOSE:
       if (app_id() == kCrostiniTerminalId) {
-        crostini::CrostiniManager::GetInstance()->StopVm(
-            profile(), kCrostiniDefaultVmName, base::DoNothing());
+        crostini::CrostiniManager::GetForProfile(profile())->StopVm(
+            kCrostiniDefaultVmName, base::DoNothing());
         return;
       }
       break;
diff --git a/chrome/browser/ui/cocoa/browser_dialogs_views_mac.cc b/chrome/browser/ui/cocoa/browser_dialogs_views_mac.cc
index c18ea3f9..5685b99d 100644
--- a/chrome/browser/ui/cocoa/browser_dialogs_views_mac.cc
+++ b/chrome/browser/ui/cocoa/browser_dialogs_views_mac.cc
@@ -57,17 +57,19 @@
     return;
   }
 
-  views::View* anchor_view =
-      bubble_anchor_util::GetPageInfoAnchorView(browser, anchor);
+  bubble_anchor_util::AnchorConfiguration configuration =
+      bubble_anchor_util::GetPageInfoAnchorConfiguration(browser, anchor);
   gfx::Rect anchor_rect =
-      anchor_view ? gfx::Rect()
-                  : bubble_anchor_util::GetPageInfoAnchorRect(browser);
+      configuration.anchor_view
+          ? gfx::Rect()
+          : bubble_anchor_util::GetPageInfoAnchorRect(browser);
   gfx::NativeWindow parent_window = browser->window()->GetNativeWindow();
   views::BubbleDialogDelegateView* bubble =
       PageInfoBubbleView::CreatePageInfoBubble(
-          anchor_view, anchor_rect, parent_window, browser->profile(),
-          web_contents, virtual_url, security_info);
+          configuration.anchor_view, anchor_rect, parent_window,
+          browser->profile(), web_contents, virtual_url, security_info);
   bubble->GetWidget()->Show();
+  bubble->set_arrow(configuration.bubble_arrow);
   KeepBubbleAnchored(bubble, GetPageInfoDecoration(parent_window));
 }
 
diff --git a/chrome/browser/ui/extensions/OWNERS b/chrome/browser/ui/extensions/OWNERS
index 4116bbb..b8cca04d 100644
--- a/chrome/browser/ui/extensions/OWNERS
+++ b/chrome/browser/ui/extensions/OWNERS
@@ -1,5 +1,6 @@
 # App-y stuff
 benwells@chromium.org
+per-file hosted_app_*=alancutter@chromium.org
 per-file hosted_app_*=mgiuca@chromium.org
 per-file hosted_app_*=ortuno@chromium.org
 
diff --git a/chrome/browser/ui/views/bubble_anchor_util_views.cc b/chrome/browser/ui/views/bubble_anchor_util_views.cc
index 63c87f88..14398ef 100644
--- a/chrome/browser/ui/views/bubble_anchor_util_views.cc
+++ b/chrome/browser/ui/views/bubble_anchor_util_views.cc
@@ -16,22 +16,24 @@
 
 namespace bubble_anchor_util {
 
-views::View* GetPageInfoAnchorView(Browser* browser, Anchor anchor) {
+AnchorConfiguration GetPageInfoAnchorConfiguration(Browser* browser,
+                                                   Anchor anchor) {
 #if defined(OS_MACOSX)
   if (views_mode_controller::IsViewsBrowserCocoa())
-    return nullptr;
+    return {};
 #endif
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
 
   if (anchor == kLocationBar && browser_view->GetLocationBarView()->IsDrawn())
-    return browser_view->GetLocationBarView()->GetSecurityBubbleAnchorView();
+    return {browser_view->GetLocationBarView()->GetSecurityBubbleAnchorView(),
+            views::BubbleBorder::TOP_LEFT};
   // Fall back to menu button if no location bar present.
 
   views::View* app_menu_button =
       browser_view->toolbar_button_provider()->GetAppMenuButton();
   if (app_menu_button && app_menu_button->IsDrawn())
-    return app_menu_button;
-  return nullptr;
+    return {app_menu_button, views::BubbleBorder::TOP_RIGHT};
+  return {};
 }
 
 gfx::Rect GetPageInfoAnchorRect(Browser* browser) {
@@ -39,8 +41,9 @@
   if (views_mode_controller::IsViewsBrowserCocoa())
     return GetPageInfoAnchorRectCocoa(browser);
 #endif
-  // GetPageInfoAnchorView() should be preferred if available.
-  DCHECK_EQ(GetPageInfoAnchorView(browser), nullptr);
+  // GetPageInfoAnchorConfiguration()'s anchor_view should be preferred if
+  // available.
+  DCHECK_EQ(GetPageInfoAnchorConfiguration(browser).anchor_view, nullptr);
 
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
   // Get position in view (taking RTL UI into account).
diff --git a/chrome/browser/ui/views/bubble_anchor_util_views.h b/chrome/browser/ui/views/bubble_anchor_util_views.h
index 80a9828..f8671630 100644
--- a/chrome/browser/ui/views/bubble_anchor_util_views.h
+++ b/chrome/browser/ui/views/bubble_anchor_util_views.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_VIEWS_BUBBLE_ANCHOR_UTIL_VIEWS_H_
 
 #include "chrome/browser/ui/bubble_anchor_util.h"
+#include "ui/views/bubble/bubble_border.h"
 
 namespace views {
 class View;
@@ -15,9 +16,17 @@
 
 namespace bubble_anchor_util {
 
-// Returns the PageInfo |anchor| View for |browser|, or null if it should not be
-// used.
-views::View* GetPageInfoAnchorView(Browser* browser, Anchor = kLocationBar);
+struct AnchorConfiguration {
+  views::View* anchor_view = nullptr;
+  views::BubbleBorder::Arrow bubble_arrow = views::BubbleBorder::TOP_LEFT;
+};
+
+// Returns:
+// - The PageInfo |anchor| View for |browser|, or null if it should not be
+//   used.
+// - The arrow position for the PageInfo bubble.
+AnchorConfiguration GetPageInfoAnchorConfiguration(Browser* browser,
+                                                   Anchor = kLocationBar);
 
 }  // namespace bubble_anchor_util
 
diff --git a/chrome/browser/ui/views/crostini/crostini_installer_view.cc b/chrome/browser/ui/views/crostini/crostini_installer_view.cc
index 50d4304..b582c41a 100644
--- a/chrome/browser/ui/views/crostini/crostini_installer_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_installer_view.cc
@@ -9,6 +9,7 @@
 
 #include "ash/public/cpp/ash_typography.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/numerics/ranges.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
@@ -137,7 +138,7 @@
   // Retry.
   DCHECK(state_ == State::PROMPT || state_ == State::ERROR);
 
-  state_ = State::INSTALL_START;
+  UpdateState(State::INSTALL_START);
   profile_->GetPrefs()->SetBoolean(crostini::prefs::kCrostiniEnabled, true);
 
   progress_bar_->SetVisible(true);
@@ -159,11 +160,13 @@
   }
 
   // Kick off the Crostini Restart sequence. We will be added as an observer.
-  restart_id_ = crostini::CrostiniManager::GetInstance()->RestartCrostini(
-      profile_, kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
-      base::BindOnce(&CrostiniInstallerView::MountContainerFinished,
-                     weak_ptr_factory_.GetWeakPtr()),
-      this);
+  restart_id_ =
+      crostini::CrostiniManager::GetForProfile(profile_)->RestartCrostini(
+          kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
+          base::BindOnce(&CrostiniInstallerView::MountContainerFinished,
+                         weak_ptr_factory_.GetWeakPtr()),
+          this);
+  UpdateState(State::INSTALL_IMAGE_LOADER);
   return false;
 }
 
@@ -172,8 +175,8 @@
       restart_id_ != crostini::CrostiniManager::kUninitializedRestartId) {
     // Abort the long-running flow, and prevent our RestartObserver methods
     // being called after "this" has been destroyed.
-    crostini::CrostiniManager::GetInstance()->AbortRestartCrostini(profile_,
-                                                                   restart_id_);
+    crostini::CrostiniManager::GetForProfile(profile_)->AbortRestartCrostini(
+        restart_id_);
     RecordSetupResultHistogram(SetupResult::kUserCancelled);
   } else {
     RecordSetupResultHistogram(SetupResult::kNotStarted);
@@ -195,8 +198,8 @@
 }
 
 void CrostiniInstallerView::OnComponentLoaded(ConciergeClientResult result) {
-  DCHECK_EQ(state_, State::INSTALL_START);
-  state_ = State::INSTALL_IMAGE_LOADER;
+  DCHECK_EQ(state_, State::INSTALL_IMAGE_LOADER);
+
   if (result != ConciergeClientResult::SUCCESS) {
     LOG(ERROR) << "Failed to install the cros-termina component";
     HandleError(
@@ -205,12 +208,12 @@
     return;
   }
   VLOG(1) << "cros-termina install success";
+  UpdateState(State::START_CONCIERGE);
   StepProgress();
 }
 
 void CrostiniInstallerView::OnConciergeStarted(ConciergeClientResult result) {
-  DCHECK_EQ(state_, State::INSTALL_IMAGE_LOADER);
-  state_ = State::START_CONCIERGE;
+  DCHECK_EQ(state_, State::START_CONCIERGE);
   if (result != ConciergeClientResult::SUCCESS) {
     LOG(ERROR) << "Failed to install start Concierge with error code: "
                << static_cast<int>(result);
@@ -220,12 +223,12 @@
     return;
   }
   VLOG(1) << "Concierge service started";
+  UpdateState(State::CREATE_DISK_IMAGE);
   StepProgress();
 }
 
 void CrostiniInstallerView::OnDiskImageCreated(ConciergeClientResult result) {
-  DCHECK_EQ(state_, State::START_CONCIERGE);
-  state_ = State::CREATE_DISK_IMAGE;
+  DCHECK_EQ(state_, State::CREATE_DISK_IMAGE);
   if (result != ConciergeClientResult::SUCCESS) {
     LOG(ERROR) << "Failed to create disk imagewith error code: "
                << static_cast<int>(result);
@@ -235,12 +238,12 @@
     return;
   }
   VLOG(1) << "Created crostini disk image";
+  UpdateState(State::START_TERMINA_VM);
   StepProgress();
 }
 
 void CrostiniInstallerView::OnVmStarted(ConciergeClientResult result) {
-  DCHECK_EQ(state_, State::CREATE_DISK_IMAGE);
-  state_ = State::START_TERMINA_VM;
+  DCHECK_EQ(state_, State::START_TERMINA_VM);
   if (result != ConciergeClientResult::SUCCESS) {
     LOG(ERROR) << "Failed to start Termina VM with error code: "
                << static_cast<int>(result);
@@ -250,12 +253,28 @@
     return;
   }
   VLOG(1) << "Started Termina VM successfully";
+  UpdateState(State::CREATE_CONTAINER);
+  StepProgress();
+}
+
+void CrostiniInstallerView::OnContainerDownloading(int32_t download_percent) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_EQ(state_, State::CREATE_CONTAINER);
+  container_download_percent_ = base::ClampToRange(download_percent, 0, 100);
+  StepProgress();
+}
+
+void CrostiniInstallerView::OnContainerCreated(ConciergeClientResult result) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_EQ(state_, State::CREATE_CONTAINER);
+  UpdateState(State::START_CONTAINER);
   StepProgress();
 }
 
 void CrostiniInstallerView::OnContainerStarted(ConciergeClientResult result) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  state_ = State::START_CONTAINER;
+  DCHECK_EQ(state_, State::START_CONTAINER);
+
   if (result != ConciergeClientResult::SUCCESS) {
     LOG(ERROR) << "Failed to start container with error code: "
                << static_cast<int>(result);
@@ -265,12 +284,14 @@
     return;
   }
   VLOG(1) << "Started container successfully";
+  UpdateState(State::FETCH_SSH_KEYS);
   StepProgress();
 }
 
 void CrostiniInstallerView::OnSshKeysFetched(ConciergeClientResult result) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  state_ = State::FETCH_SSH_KEYS;
+  DCHECK_EQ(state_, State::FETCH_SSH_KEYS);
+
   if (result != ConciergeClientResult::SUCCESS) {
     LOG(ERROR) << "Failed to fetch ssh keys with error code: "
                << static_cast<int>(result);
@@ -280,6 +301,7 @@
     return;
   }
   VLOG(1) << "Fetched ssh keys successfully";
+  UpdateState(State::MOUNT_CONTAINER);
   StepProgress();
 }
 
@@ -390,7 +412,7 @@
 
   RecordSetupResultHistogram(result);
   restart_id_ = crostini::CrostiniManager::kUninitializedRestartId;
-  state_ = State::ERROR;
+  UpdateState(State::ERROR);
   message_label_->SetVisible(true);
   message_label_->SetText(error_message);
   SetBigMessageLabel();
@@ -408,7 +430,6 @@
 void CrostiniInstallerView::MountContainerFinished(
     ConciergeClientResult result) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  state_ = State::MOUNT_CONTAINER;
   if (result != ConciergeClientResult::SUCCESS) {
     LOG(ERROR) << "Failed to mount container with error code: "
                << static_cast<int>(result);
@@ -423,10 +444,10 @@
 
 void CrostiniInstallerView::ShowLoginShell() {
   DCHECK_EQ(state_, State::MOUNT_CONTAINER);
-  state_ = State::SHOW_LOGIN_SHELL;
+  UpdateState(State::SHOW_LOGIN_SHELL);
 
-  crostini::CrostiniManager::GetInstance()->LaunchContainerTerminal(
-      profile_, kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
+  crostini::CrostiniManager::GetForProfile(profile_)->LaunchContainerTerminal(
+      kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
       std::vector<std::string>());
 
   StepProgress();
@@ -436,11 +457,73 @@
 }
 
 void CrostiniInstallerView::StepProgress() {
+  base::TimeDelta time_in_state = base::Time::Now() - state_start_time_;
+
+  VLOG(1) << "state_ = " << static_cast<int>(state_);
+
+  double state_start_mark = 0;
+  double state_end_mark = 0;
+  int state_max_seconds = 1;
+
+  switch (state_) {
+    case State::INSTALL_START:
+      state_start_mark = 0;
+      state_end_mark = 0;
+      break;
+    case State::INSTALL_IMAGE_LOADER:
+      state_start_mark = 0.0;
+      state_end_mark = 0.25;
+      state_max_seconds = 30;
+      break;
+    case State::START_CONCIERGE:
+      state_start_mark = 0.25;
+      state_end_mark = 0.26;
+      break;
+    case State::CREATE_DISK_IMAGE:
+      state_start_mark = 0.26;
+      state_end_mark = 0.27;
+      break;
+    case State::START_TERMINA_VM:
+      state_start_mark = 0.27;
+      state_end_mark = 0.35;
+      state_max_seconds = 8;
+      break;
+    case State::CREATE_CONTAINER:
+      state_start_mark = 0.35;
+      state_end_mark = 0.95;
+      state_max_seconds = 180;
+      break;
+    case State::START_CONTAINER:
+      state_start_mark = 0.95;
+      state_end_mark = 0.99;
+      state_max_seconds = 8;
+      break;
+    case State::FETCH_SSH_KEYS:
+      state_start_mark = 0.99;
+      state_end_mark = 1;
+      break;
+
+    default:
+      break;
+  }
+
   if (State::INSTALL_START <= state_ && state_ < State::INSTALL_END) {
-    // Setting value to -1 makes the progress bar play the
-    // "indeterminate animation".
-    progress_bar_->SetValue(-1);
+    double state_fraction = time_in_state.InSecondsF() / state_max_seconds;
+
+    if (state_ == State::CREATE_CONTAINER) {
+      // In CREATE_CONTAINER, consume half the progress bar with downloading,
+      // the rest with time.
+      state_fraction =
+          0.5 * (state_fraction + 0.01 * container_download_percent_);
+    }
+    VLOG(1) << "start = " << state_start_mark << ", end = " << state_end_mark
+            << ", fraction = " << state_fraction;
+    progress_bar_->SetValue(state_start_mark +
+                            base::ClampToRange(state_fraction, 0.0, 1.0) *
+                                (state_end_mark - state_start_mark));
     progress_bar_->SetVisible(true);
+  } else {
+    progress_bar_->SetVisible(false);
   }
   SetMessageLabel();
   SetBigMessageLabel();
@@ -448,31 +531,63 @@
   GetWidget()->GetRootView()->Layout();
 }
 
+void CrostiniInstallerView::UpdateState(State new_state) {
+  state_start_time_ = base::Time::Now();
+  state_ = new_state;
+  if (state_ == State::INSTALL_START) {
+    state_progress_timer_ = std::make_unique<base::RepeatingTimer>();
+    state_progress_timer_->Start(
+        FROM_HERE, base::TimeDelta::FromMilliseconds(500),
+        base::BindRepeating(&CrostiniInstallerView::StepProgress,
+                            weak_ptr_factory_.GetWeakPtr()));
+  } else if (state_ < State::INSTALL_START || state_ >= State::INSTALL_END) {
+    if (state_progress_timer_) {
+      VLOG(1) << "Killing timer, state_ = " << static_cast<int>(state_);
+      state_progress_timer_->AbandonAndStop();
+    }
+  }
+}
+
 void CrostiniInstallerView::SetMessageLabel() {
   int message_id = 0;
   // The States below refer to stages that have completed.
   // The messages selected refer to the next stage, now underway.
-  if (state_ == State::INSTALL_START) {
-    message_id = IDS_CROSTINI_INSTALLER_LOAD_TERMINA_MESSAGE;
-  } else if (state_ == State::INSTALL_IMAGE_LOADER) {
-    message_id = IDS_CROSTINI_INSTALLER_START_CONCIERGE_MESSAGE;
-  } else if (state_ == State::START_CONCIERGE) {
-    message_id = IDS_CROSTINI_INSTALLER_CREATE_DISK_IMAGE_MESSAGE;
-  } else if (state_ == State::CREATE_DISK_IMAGE) {
-    message_id = IDS_CROSTINI_INSTALLER_START_TERMINA_VM_MESSAGE;
-  } else if (state_ == State::START_TERMINA_VM) {
-    message_id = IDS_CROSTINI_INSTALLER_START_CONTAINER_MESSAGE;
-  } else if (state_ == State::START_CONTAINER) {
-    message_id = IDS_CROSTINI_INSTALLER_FETCH_SSH_KEYS_MESSAGE;
-  } else if (state_ == State::FETCH_SSH_KEYS) {
-    message_id = IDS_CROSTINI_INSTALLER_MOUNT_CONTAINER_MESSAGE;
+  switch (state_) {
+    case State::INSTALL_IMAGE_LOADER:
+      message_id = IDS_CROSTINI_INSTALLER_LOAD_TERMINA_MESSAGE;
+      break;
+    case State::START_CONCIERGE:
+      message_id = IDS_CROSTINI_INSTALLER_START_CONCIERGE_MESSAGE;
+      break;
+    case State::CREATE_DISK_IMAGE:
+      message_id = IDS_CROSTINI_INSTALLER_CREATE_DISK_IMAGE_MESSAGE;
+      break;
+    case State::START_TERMINA_VM:
+      message_id = IDS_CROSTINI_INSTALLER_START_TERMINA_VM_MESSAGE;
+      break;
+    case State::CREATE_CONTAINER:
+      message_id = IDS_CROSTINI_INSTALLER_START_CONTAINER_MESSAGE;
+      break;
+    case State::START_CONTAINER:
+      message_id = IDS_CROSTINI_INSTALLER_START_CONTAINER_MESSAGE;
+      break;
+    case State::FETCH_SSH_KEYS:
+      message_id = IDS_CROSTINI_INSTALLER_FETCH_SSH_KEYS_MESSAGE;
+      break;
+    case State::MOUNT_CONTAINER:
+      message_id = IDS_CROSTINI_INSTALLER_MOUNT_CONTAINER_MESSAGE;
+      break;
+    default:
+      break;
   }
-  if (message_id != 0) {
-    message_label_->SetText(l10n_util::GetStringUTF16(message_id));
-    message_label_->SetVisible(true);
-  } else {
+
+  if (message_id == 0) {
     message_label_->SetVisible(false);
+    return;
   }
+
+  message_label_->SetText(l10n_util::GetStringUTF16(message_id));
+  message_label_->SetVisible(true);
 }
 
 void CrostiniInstallerView::SetBigMessageLabel() {
diff --git a/chrome/browser/ui/views/crostini/crostini_installer_view.h b/chrome/browser/ui/views/crostini/crostini_installer_view.h
index c674c4a..a50a53df 100644
--- a/chrome/browser/ui/views/crostini/crostini_installer_view.h
+++ b/chrome/browser/ui/views/crostini/crostini_installer_view.h
@@ -7,6 +7,8 @@
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
 #include "chrome/browser/component_updater/cros_component_installer_chromeos.h"
 #include "ui/views/controls/link_listener.h"
@@ -68,6 +70,8 @@
   void OnConciergeStarted(crostini::ConciergeClientResult result) override;
   void OnDiskImageCreated(crostini::ConciergeClientResult result) override;
   void OnVmStarted(crostini::ConciergeClientResult result) override;
+  void OnContainerDownloading(int32_t download_percent) override;
+  void OnContainerCreated(crostini::ConciergeClientResult result) override;
   void OnContainerStarted(crostini::ConciergeClientResult result) override;
   void OnSshKeysFetched(crostini::ConciergeClientResult result) override;
 
@@ -83,6 +87,7 @@
     START_CONCIERGE,       // Starting the Concierge D-Bus client.
     CREATE_DISK_IMAGE,     // Creating the image for the Termina VM.
     START_TERMINA_VM,      // Starting the Termina VM.
+    CREATE_CONTAINER,      // Creating the container inside the Termina VM.
     START_CONTAINER,       // Starting the container inside the Termina VM.
     FETCH_SSH_KEYS,        // Fetch ssh keys from concierge.
     MOUNT_CONTAINER,       // Do sshfs mount of container.
@@ -97,6 +102,7 @@
   void MountContainerFinished(crostini::ConciergeClientResult result);
   void ShowLoginShell();
   void StepProgress();
+  void UpdateState(State new_state);
   void SetMessageLabel();
   void SetBigMessageLabel();
 
@@ -112,6 +118,9 @@
   Profile* profile_;
   crostini::CrostiniManager::RestartId restart_id_ =
       crostini::CrostiniManager::kUninitializedRestartId;
+  int32_t container_download_percent_ = 0;
+  base::Time state_start_time_;
+  std::unique_ptr<base::RepeatingTimer> state_progress_timer_;
 
   // Whether the result has been logged or not is stored to prevent multiple
   // results being logged for a given setup flow. This can happen due to
diff --git a/chrome/browser/ui/views/crostini/crostini_installer_view_browsertest.cc b/chrome/browser/ui/views/crostini/crostini_installer_view_browsertest.cc
index ce03308..ec51430 100644
--- a/chrome/browser/ui/views/crostini/crostini_installer_view_browsertest.cc
+++ b/chrome/browser/ui/views/crostini/crostini_installer_view_browsertest.cc
@@ -7,7 +7,6 @@
 #include "base/metrics/histogram_base.h"
 #include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "chrome/browser/chromeos/crostini/crostini_manager.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
@@ -19,7 +18,6 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chromeos/dbus/cros_disks_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
-#include "chromeos/dbus/fake_cicerone_client.h"
 #include "chromeos/dbus/fake_concierge_client.h"
 #include "chromeos/dbus/fake_cros_disks_client.h"
 #include "chromeos/disks/disk_mount_manager.h"
@@ -38,16 +36,8 @@
         const vm_tools::concierge::StartVmRequest& request,
         chromeos::DBusMethodCallback<vm_tools::concierge::StartVmResponse>
             callback) override {
-      signal_.set_owner_id(request.owner_id());
-      signal_.set_vm_name(request.name());
-
       chromeos::FakeConciergeClient::StartTerminaVm(request,
                                                     std::move(callback));
-
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE,
-          base::BindOnce(&WaitingFakeConciergeClient::OnTremplinStarted,
-                         base::Unretained(this)));
       if (closure_) {
         base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                       std::move(closure_));
@@ -62,87 +52,9 @@
     }
 
    private:
-    void OnTremplinStarted() {
-      crostini::CrostiniManager::GetInstance()->OnTremplinStarted(signal_);
-    }
-
-    vm_tools::cicerone::TremplinStartedSignal signal_;
     base::OnceClosure closure_;
   };
 
-  // TODO(timloh): Consider moving this logic into the fake itself.
-  class SignalingFakeCiceroneClient : public chromeos::FakeCiceroneClient {
-   public:
-    void CreateLxdContainer(
-        const vm_tools::cicerone::CreateLxdContainerRequest& request,
-        chromeos::DBusMethodCallback<
-            vm_tools::cicerone::CreateLxdContainerResponse> callback) override {
-      lxd_container_created_signal_.set_owner_id(request.owner_id());
-      lxd_container_created_signal_.set_vm_name(request.vm_name());
-      lxd_container_created_signal_.set_container_name(
-          request.container_name());
-
-      chromeos::FakeCiceroneClient::CreateLxdContainer(
-          request,
-          base::BindOnce(&SignalingFakeCiceroneClient::OnLxdContainerCreated,
-                         base::Unretained(this), std::move(callback)));
-    }
-
-    void SetUpLxdContainerUser(
-        const vm_tools::cicerone::SetUpLxdContainerUserRequest& request,
-        chromeos::DBusMethodCallback<
-            vm_tools::cicerone::SetUpLxdContainerUserResponse> callback)
-        override {
-      container_started_signal_.set_owner_id(request.owner_id());
-      container_started_signal_.set_vm_name(request.vm_name());
-      container_started_signal_.set_container_name(request.container_name());
-
-      chromeos::FakeCiceroneClient::SetUpLxdContainerUser(
-          request,
-          base::BindOnce(&SignalingFakeCiceroneClient::OnSetUpLxdContainerUser,
-                         base::Unretained(this), std::move(callback)));
-    }
-
-   private:
-    void OnLxdContainerCreated(
-        chromeos::DBusMethodCallback<
-            vm_tools::cicerone::CreateLxdContainerResponse> callback,
-        base::Optional<vm_tools::cicerone::CreateLxdContainerResponse> reply) {
-      std::move(callback).Run(reply);
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE,
-          base::BindOnce(
-              &SignalingFakeCiceroneClient::SendLxdContainerCreatedSignal,
-              base::Unretained(this)));
-    }
-
-    void OnSetUpLxdContainerUser(
-        chromeos::DBusMethodCallback<
-            vm_tools::cicerone::SetUpLxdContainerUserResponse> callback,
-        base::Optional<vm_tools::cicerone::SetUpLxdContainerUserResponse>
-            reply) {
-      std::move(callback).Run(reply);
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE,
-          base::BindOnce(
-              &SignalingFakeCiceroneClient::SendContainerStartedSignal,
-              base::Unretained(this)));
-    }
-
-    void SendLxdContainerCreatedSignal() {
-      crostini::CrostiniManager::GetInstance()->OnLxdContainerCreated(
-          lxd_container_created_signal_);
-    }
-
-    void SendContainerStartedSignal() {
-      crostini::CrostiniManager::GetInstance()->OnContainerStarted(
-          container_started_signal_);
-    }
-
-    vm_tools::cicerone::LxdContainerCreatedSignal lxd_container_created_signal_;
-    vm_tools::cicerone::ContainerStartedSignal container_started_signal_;
-  };
-
   class WaitingDiskMountManagerObserver
       : public chromeos::disks::DiskMountManager::Observer {
    public:
@@ -165,13 +77,10 @@
 
   CrostiniInstallerViewBrowserTest()
       : waiting_fake_concierge_client_(new WaitingFakeConciergeClient()),
-        signaling_fake_cicerone_client_(new SignalingFakeCiceroneClient()),
         waiting_disk_mount_manager_observer_(
             new WaitingDiskMountManagerObserver) {
     chromeos::DBusThreadManager::GetSetterForTesting()->SetConciergeClient(
         base::WrapUnique(waiting_fake_concierge_client_));
-    chromeos::DBusThreadManager::GetSetterForTesting()->SetCiceroneClient(
-        base::WrapUnique(signaling_fake_cicerone_client_));
     static_cast<chromeos::FakeCrosDisksClient*>(
         chromeos::DBusThreadManager::Get()->GetCrosDisksClient())
         ->AddCustomMountPointCallback(base::BindRepeating(
@@ -179,12 +88,16 @@
             base::Unretained(this)));
   }
 
-  // DialogBrowserTest:
+  // CrostiniDialogBrowserTest:
   void ShowUi(const std::string& name) override {
     ShowCrostiniInstallerView(browser()->profile(),
                               CrostiniUISurface::kSettings);
   }
 
+  void SetUpOnMainThread() override {
+    CrostiniDialogBrowserTest::SetUpOnMainThread();
+  }
+
   CrostiniInstallerView* ActiveView() {
     return CrostiniInstallerView::GetActiveViewForTesting();
   }
@@ -200,7 +113,6 @@
  protected:
   // Owned by chromeos::DBusThreadManager
   WaitingFakeConciergeClient* waiting_fake_concierge_client_ = nullptr;
-  SignalingFakeCiceroneClient* signaling_fake_cicerone_client_ = nullptr;
   WaitingDiskMountManagerObserver* waiting_disk_mount_manager_observer_ =
       nullptr;
 
diff --git a/chrome/browser/ui/views/crostini/crostini_uninstaller_view.cc b/chrome/browser/ui/views/crostini/crostini_uninstaller_view.cc
index 2caa4ef..031dc5c8 100644
--- a/chrome/browser/ui/views/crostini/crostini_uninstaller_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_uninstaller_view.cc
@@ -84,8 +84,8 @@
       l10n_util::GetStringUTF16(IDS_CROSTINI_UNINSTALLER_UNINSTALLING_MESSAGE));
 
   // Kick off the Crostini Remove sequence.
-  crostini::CrostiniManager::GetInstance()->RemoveCrostini(
-      profile_, kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
+  crostini::CrostiniManager::GetForProfile(profile_)->RemoveCrostini(
+      kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
       base::BindOnce(&CrostiniUninstallerView::UninstallCrostiniFinished,
                      weak_ptr_factory_.GetWeakPtr()));
 
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
index cb9e7258..8a0c10e52 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
@@ -57,6 +57,7 @@
 #include "chrome/browser/ui/views/location_bar/content_setting_image_view.h"
 #include "chrome/browser/ui/views/location_bar/zoom_bubble_view.h"
 #include "chrome/browser/ui/views/page_action/page_action_icon_container_view.h"
+#include "chrome/browser/ui/views/page_info/page_info_bubble_view_base.h"
 #include "chrome/browser/ui/views/profiles/profile_indicator_icon.h"
 #include "chrome/browser/ui/views/tabs/tab.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
@@ -835,6 +836,32 @@
 
 }  // namespace
 
+// Tests that the page info dialog doesn't anchor in a way that puts it outside
+// of hosted app windows. This is important as some platforms don't support
+// bubble anchor adjustment (see |BubbleDialogDelegateView::CreateBubble()|).
+IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest,
+                       PageInfoBubblePosition) {
+  // Resize app window to only take up the left half of the screen.
+  views::Widget* widget = browser_view_->GetWidget();
+  gfx::Size screen_size =
+      display::Screen::GetScreen()
+          ->GetDisplayNearestWindow(widget->GetNativeWindow())
+          .work_area_size();
+  widget->SetBounds(
+      gfx::Rect(0, 0, screen_size.width() / 2, screen_size.height()));
+
+  // Show page info dialog (currently PWAs use page info in place of an actual
+  // app info dialog).
+  chrome::ExecuteCommand(app_browser_, IDC_HOSTED_APP_MENU_APP_INFO);
+
+  // Check the bubble anchors inside the main app window even if there's space
+  // available outside the main app window.
+  gfx::Rect page_info_bounds = PageInfoBubbleViewBase::GetPageInfoBubble()
+                                   ->GetWidget()
+                                   ->GetWindowBoundsInScreen();
+  EXPECT_TRUE(widget->GetWindowBoundsInScreen().Contains(page_info_bounds));
+}
+
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest, FocusableViews) {
   EXPECT_TRUE(browser_view_->contents_web_view()->HasFocus());
   browser_view_->GetFocusManager()->AdvanceFocus(false);
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
index 78b6397..d109053 100644
--- a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
+++ b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
@@ -77,8 +77,9 @@
 #include "chrome/browser/safe_browsing/chrome_password_protection_service.h"
 #endif
 
+using bubble_anchor_util::AnchorConfiguration;
 using bubble_anchor_util::GetPageInfoAnchorRect;
-using bubble_anchor_util::GetPageInfoAnchorView;
+using bubble_anchor_util::GetPageInfoAnchorConfiguration;
 
 namespace {
 
@@ -922,14 +923,16 @@
                                            security_info, anchor);
   }
 #endif
-  views::View* anchor_view = GetPageInfoAnchorView(browser, anchor);
+  AnchorConfiguration configuration =
+      GetPageInfoAnchorConfiguration(browser, anchor);
   gfx::Rect anchor_rect =
-      anchor_view ? gfx::Rect() : GetPageInfoAnchorRect(browser);
+      configuration.anchor_view ? gfx::Rect() : GetPageInfoAnchorRect(browser);
   gfx::NativeWindow parent_window = browser->window()->GetNativeWindow();
   views::BubbleDialogDelegateView* bubble =
       PageInfoBubbleView::CreatePageInfoBubble(
-          anchor_view, anchor_rect, parent_window, browser->profile(),
-          web_contents, virtual_url, security_info);
+          configuration.anchor_view, anchor_rect, parent_window,
+          browser->profile(), web_contents, virtual_url, security_info);
+  bubble->set_arrow(configuration.bubble_arrow);
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
   auto* location_bar = browser_view->GetLocationBarView();
   if (location_bar)
diff --git a/chrome/browser/ui/views/permission_bubble/chooser_bubble_ui.cc b/chrome/browser/ui/views/permission_bubble/chooser_bubble_ui.cc
index a30a079..b9bea6a5 100644
--- a/chrome/browser/ui/views/permission_bubble/chooser_bubble_ui.cc
+++ b/chrome/browser/ui/views/permission_bubble/chooser_bubble_ui.cc
@@ -17,13 +17,12 @@
 #include "ui/views/controls/table/table_view_observer.h"
 #include "ui/views/window/dialog_client_view.h"
 
+using bubble_anchor_util::AnchorConfiguration;
+
 namespace {
 
-constexpr views::BubbleBorder::Arrow kChooserAnchorArrow =
-    views::BubbleBorder::TOP_LEFT;
-
-views::View* GetChooserAnchorView(Browser* browser) {
-  return bubble_anchor_util::GetPageInfoAnchorView(browser);
+AnchorConfiguration GetChooserAnchorConfiguration(Browser* browser) {
+  return bubble_anchor_util::GetPageInfoAnchorConfiguration(browser);
 }
 
 gfx::Rect GetChooserAnchorRect(Browser* browser) {
@@ -79,9 +78,7 @@
 ChooserBubbleUiViewDelegate::ChooserBubbleUiViewDelegate(
     Browser* browser,
     std::unique_ptr<ChooserController> chooser_controller)
-    : views::BubbleDialogDelegateView(GetChooserAnchorView(browser),
-                                      kChooserAnchorArrow),
-      device_chooser_content_view_(nullptr) {
+    : device_chooser_content_view_(nullptr) {
   // ------------------------------------
   // | Chooser bubble title             |
   // | -------------------------------- |
@@ -99,8 +96,7 @@
 
   device_chooser_content_view_ =
       new DeviceChooserContentView(this, std::move(chooser_controller));
-  if (!GetAnchorView())
-    SetAnchorRect(GetChooserAnchorRect(browser));
+  UpdateAnchor(browser);
   chrome::RecordDialogCreation(chrome::DialogIdentifier::CHOOSER_UI);
 }
 
@@ -166,10 +162,11 @@
 }
 
 void ChooserBubbleUiViewDelegate::UpdateAnchor(Browser* browser) {
-  views::View* anchor_view = GetChooserAnchorView(browser);
-  SetAnchorView(anchor_view);
-  if (!anchor_view)
+  AnchorConfiguration configuration = GetChooserAnchorConfiguration(browser);
+  SetAnchorView(configuration.anchor_view);
+  if (!configuration.anchor_view)
     SetAnchorRect(GetChooserAnchorRect(browser));
+  set_arrow(configuration.bubble_arrow);
 }
 
 void ChooserBubbleUiViewDelegate::set_bubble_reference(
diff --git a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
index 55a49cb..d8b0c84 100644
--- a/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
+++ b/chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc
@@ -37,18 +37,17 @@
 #include "chrome/common/chrome_features.h"
 #endif
 
+using bubble_anchor_util::AnchorConfiguration;
+
 namespace {
 
 // (Square) pixel size of icon.
 constexpr int kPermissionIconSize = 18;
 
-// The type of arrow to display on the permission bubble.
-constexpr views::BubbleBorder::Arrow kPermissionAnchorArrow =
-    views::BubbleBorder::TOP_LEFT;
-
-// Returns the view to anchor the permission bubble to. May be null.
-views::View* GetPermissionAnchorView(Browser* browser) {
-  return bubble_anchor_util::GetPageInfoAnchorView(browser);
+// Returns the view to anchor the permission bubble to (may be null) and the
+// arrow position of the bubble.
+AnchorConfiguration GetPermissionAnchorConfiguration(Browser* browser) {
+  return bubble_anchor_util::GetPageInfoAnchorConfiguration(browser);
 }
 
 // Returns the anchor rect to anchor the permission bubble to, as a fallback.
@@ -104,7 +103,6 @@
   DCHECK(!requests.empty());
 
   set_close_on_deactivate(false);
-  set_arrow(kPermissionAnchorArrow);
 
 #if defined(OS_MACOSX)
   // On Mac, the browser UI flips depending on a runtime feature. TODO(tapted):
@@ -237,10 +235,12 @@
 }
 
 void PermissionsBubbleDialogDelegateView::UpdateAnchor() {
-  views::View* anchor_view = GetPermissionAnchorView(owner_->browser());
-  SetAnchorView(anchor_view);
-  if (!anchor_view)
+  AnchorConfiguration configuration =
+      GetPermissionAnchorConfiguration(owner_->browser());
+  SetAnchorView(configuration.anchor_view);
+  if (!configuration.anchor_view)
     SetAnchorRect(GetPermissionAnchorRect(owner_->browser()));
+  set_arrow(configuration.bubble_arrow);
 }
 
 //////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc b/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc
index cf29c54..9900d621 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc
@@ -336,8 +336,7 @@
     return;
   updating_crostini_size_ = true;
 
-  crostini::CrostiniManager::GetInstance()->ListVmDisks(
-      CryptohomeIdForProfile(profile_),
+  crostini::CrostiniManager::GetForProfile(profile_)->ListVmDisks(
       base::BindOnce(&StorageHandler::OnGetCrostiniSize,
                      weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/chrome/browser/ui/webui/signin/inline_login_handler.cc b/chrome/browser/ui/webui/signin/inline_login_handler.cc
index 0738564..0af8570 100644
--- a/chrome/browser/ui/webui/signin/inline_login_handler.cc
+++ b/chrome/browser/ui/webui/signin/inline_login_handler.cc
@@ -137,11 +137,10 @@
   if (!default_email.empty())
     params.SetString("email", default_email);
 
-  std::string is_constrained;
-  net::GetValueForKeyInQuery(
-      current_url, signin::kSignInPromoQueryKeyConstrained, &is_constrained);
-  if (!is_constrained.empty())
-    params.SetString(signin::kSignInPromoQueryKeyConstrained, is_constrained);
+  // The legacy full-tab Chrome sign-in page is no longer used as it was relying
+  // on exchanging cookies for refresh tokens and that endpoint is no longer
+  // supported.
+  params.SetString("constrained", "1");
 
   // TODO(rogerta): this needs to be passed on to gaia somehow.
   std::string read_only_email;
@@ -180,14 +179,6 @@
       main_frame_url, kSignInPromoQueryKeyShowAccountManagement, "1");
   main_frame_url = net::AppendOrReplaceQueryParameter(
       main_frame_url, signin::kSignInPromoQueryKeyForceKeepData, "1");
-  if (base::FeatureList::IsEnabled(
-          features::kRemoveUsageOfDeprecatedGaiaSigninEndpoint)) {
-    main_frame_url = net::AppendOrReplaceQueryParameter(
-        main_frame_url, signin::kSignInPromoQueryKeyConstrained, "1");
-  } else {
-    main_frame_url = net::AppendOrReplaceQueryParameter(
-        main_frame_url, signin::kSignInPromoQueryKeyConstrained, "0");
-  }
 
   NavigateParams params(profile, main_frame_url,
                         ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
diff --git a/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc b/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc
index e332323..a741418 100644
--- a/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc
+++ b/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc
@@ -166,7 +166,6 @@
     const std::string& email,
     const std::string& gaia_id,
     const std::string& password,
-    const std::string& session_index,
     const std::string& auth_code,
     const std::string& signin_scoped_device_id,
     bool choose_what_to_sync,
@@ -182,22 +181,16 @@
       email_(email),
       gaia_id_(gaia_id),
       password_(password),
-      session_index_(session_index),
       auth_code_(auth_code),
       choose_what_to_sync_(choose_what_to_sync),
       confirm_untrusted_signin_(confirm_untrusted_signin),
       is_force_sign_in_with_usermanager_(is_force_sign_in_with_usermanager) {
   DCHECK(profile_);
   DCHECK(!email_.empty());
-  if (!auth_code_.empty()) {
-    gaia_auth_fetcher_.StartAuthCodeForOAuth2TokenExchangeWithDeviceId(
-        auth_code, signin_scoped_device_id);
-  } else {
-    DCHECK(!session_index_.empty());
-    gaia_auth_fetcher_
-        .DeprecatedStartCookieForOAuthLoginTokenExchangeWithDeviceId(
-            session_index_, signin_scoped_device_id);
-  }
+  DCHECK(!auth_code_.empty());
+
+  gaia_auth_fetcher_.StartAuthCodeForOAuth2TokenExchangeWithDeviceId(
+      auth_code_, signin_scoped_device_id);
 }
 
 InlineSigninHelper::~InlineSigninHelper() {}
@@ -463,11 +456,6 @@
   signin_metrics::Reason reason =
       signin::GetSigninReasonForPromoURL(current_url);
 
-  std::string is_constrained;
-  net::GetValueForKeyInQuery(current_url, "constrained", &is_constrained);
-
-  // Use new embedded flow if in constrained window.
-  if (is_constrained == "1") {
     const GURL& url = GaiaUrls::GetInstance()->embedded_signin_url();
     params.SetBoolean("isNewGaiaFlow", true);
     params.SetString("clientId",
@@ -491,12 +479,9 @@
         break;
     }
     params.SetString("flow", flow);
-  }
 
   content::WebContentsObserver::Observe(contents);
   LogHistogramValue(signin_metrics::HISTOGRAM_SHOWN);
-  UMA_HISTOGRAM_BOOLEAN("Signin.UseDeprecatedGaiaSigninEndpoint",
-                        is_constrained == "1");
 }
 
 void InlineLoginHandlerImpl::CompleteLogin(const base::ListValue* args) {
@@ -533,19 +518,10 @@
   DCHECK(!gaia_id_string16.empty());
   std::string gaia_id = base::UTF16ToASCII(gaia_id_string16);
 
-  std::string is_constrained;
-  net::GetValueForKeyInQuery(current_url, "constrained", &is_constrained);
-  const bool is_password_separated_signin_flow = is_constrained == "1";
-
-  base::string16 session_index_string16;
-  dict->GetString("sessionIndex", &session_index_string16);
-  std::string session_index = base::UTF16ToASCII(session_index_string16);
-  DCHECK(is_password_separated_signin_flow || !session_index.empty());
-
   base::string16 auth_code_string16;
   dict->GetString("authCode", &auth_code_string16);
   std::string auth_code = base::UTF16ToASCII(auth_code_string16);
-  DCHECK(!is_password_separated_signin_flow || !auth_code.empty());
+  DCHECK(!auth_code.empty());
 
   bool choose_what_to_sync = false;
   dict->GetBoolean("chooseWhatToSync", &choose_what_to_sync);
@@ -573,8 +549,8 @@
       if (reason == signin_metrics::Reason::REASON_REAUTHENTICATION) {
         FinishCompleteLoginParams params(
             this, partition, current_url, base::FilePath(),
-            confirm_untrusted_signin_, email, gaia_id, password, session_index,
-            auth_code, choose_what_to_sync, false);
+            confirm_untrusted_signin_, email, gaia_id, password, auth_code,
+            choose_what_to_sync, false);
         ProfileManager::CreateCallback callback =
             base::Bind(&InlineLoginHandlerImpl::FinishCompleteLogin, params);
         profiles::LoadProfileAsync(path, callback);
@@ -588,8 +564,8 @@
           handler = this;
         FinishCompleteLoginParams params(
             handler, partition, current_url, path, confirm_untrusted_signin_,
-            email, gaia_id, password, session_index, auth_code,
-            choose_what_to_sync, is_force_signin_enabled);
+            email, gaia_id, password, auth_code, choose_what_to_sync,
+            is_force_signin_enabled);
         ProfileManager::CreateCallback callback =
             base::Bind(&InlineLoginHandlerImpl::FinishCompleteLogin, params);
         if (is_force_signin_enabled) {
@@ -602,12 +578,11 @@
       }
     }
   } else {
-    FinishCompleteLogin(
-        FinishCompleteLoginParams(this, partition, current_url,
-                                  base::FilePath(), confirm_untrusted_signin_,
-                                  email, gaia_id, password, session_index,
-                                  auth_code, choose_what_to_sync, false),
-        profile, Profile::CREATE_STATUS_CREATED);
+    FinishCompleteLogin(FinishCompleteLoginParams(
+                            this, partition, current_url, base::FilePath(),
+                            confirm_untrusted_signin_, email, gaia_id, password,
+                            auth_code, choose_what_to_sync, false),
+                        profile, Profile::CREATE_STATUS_CREATED);
   }
 }
 
@@ -620,7 +595,6 @@
     const std::string& email,
     const std::string& gaia_id,
     const std::string& password,
-    const std::string& session_index,
     const std::string& auth_code,
     bool choose_what_to_sync,
     bool is_force_sign_in_with_usermanager)
@@ -632,7 +606,6 @@
       email(email),
       gaia_id(gaia_id),
       password(password),
-      session_index(session_index),
       auth_code(auth_code),
       choose_what_to_sync(choose_what_to_sync),
       is_force_sign_in_with_usermanager(is_force_sign_in_with_usermanager) {}
@@ -727,8 +700,8 @@
       handler_weak_ptr,
       params.partition->GetURLLoaderFactoryForBrowserProcess(), profile, status,
       params.url, params.email, params.gaia_id, params.password,
-      params.session_index, params.auth_code, signin_scoped_device_id,
-      params.choose_what_to_sync, params.confirm_untrusted_signin,
+      params.auth_code, signin_scoped_device_id, params.choose_what_to_sync,
+      params.confirm_untrusted_signin,
       params.is_force_sign_in_with_usermanager);
 
   // If opened from user manager to unlock a profile, make sure the user manager
diff --git a/chrome/browser/ui/webui/signin/inline_login_handler_impl.h b/chrome/browser/ui/webui/signin/inline_login_handler_impl.h
index fb2d86f..dd143eb 100644
--- a/chrome/browser/ui/webui/signin/inline_login_handler_impl.h
+++ b/chrome/browser/ui/webui/signin/inline_login_handler_impl.h
@@ -64,7 +64,6 @@
                               const std::string& email,
                               const std::string& gaia_id,
                               const std::string& password,
-                              const std::string& session_index,
                               const std::string& auth_code,
                               bool choose_what_to_sync,
                               bool is_force_sign_in_with_usermanager);
@@ -89,9 +88,6 @@
     std::string gaia_id;
     // Password of the account used to sign in.
     std::string password;
-    // Index within gaia cookie of the account used to sign in.  Used only
-    // with password combined signin flow.
-    std::string session_index;
     // Authentication code used to exchange for a login scoped refresh token
     // for the account used to sign in.  Used only with password separated
     // signin flow.
@@ -136,7 +132,6 @@
       const std::string& email,
       const std::string& gaia_id,
       const std::string& password,
-      const std::string& session_index,
       const std::string& auth_code,
       const std::string& signin_scoped_device_id,
       bool choose_what_to_sync,
@@ -192,7 +187,6 @@
   std::string email_;
   std::string gaia_id_;
   std::string password_;
-  std::string session_index_;
   std::string auth_code_;
   bool choose_what_to_sync_;
   bool confirm_untrusted_signin_;
diff --git a/chrome/browser/ui/webui/signin/inline_login_ui_browsertest.cc b/chrome/browser/ui/webui/signin/inline_login_ui_browsertest.cc
index 0fd4b89..2be52a36 100644
--- a/chrome/browser/ui/webui/signin/inline_login_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/signin/inline_login_ui_browsertest.cc
@@ -161,7 +161,6 @@
       const std::string& email,
       const std::string& gaia_id,
       const std::string& password,
-      const std::string& session_index,
       const std::string& auth_code,
       const std::string& signin_scoped_device_id,
       bool choose_what_to_sync,
@@ -191,7 +190,6 @@
     const std::string& email,
     const std::string& gaia_id,
     const std::string& password,
-    const std::string& session_index,
     const std::string& auth_code,
     const std::string& signin_scoped_device_id,
     bool choose_what_to_sync,
@@ -204,7 +202,6 @@
                          email,
                          gaia_id,
                          password,
-                         session_index,
                          auth_code,
                          signin_scoped_device_id,
                          choose_what_to_sync,
@@ -223,7 +220,6 @@
       const std::string& email,
       const std::string& gaia_id,
       const std::string& password,
-      const std::string& session_index,
       const std::string& auth_code,
       const std::string& signin_scoped_device_id,
       bool choose_what_to_sync,
@@ -250,7 +246,6 @@
     const std::string& email,
     const std::string& gaia_id,
     const std::string& password,
-    const std::string& session_index,
     const std::string& auth_code,
     const std::string& signin_scoped_device_id,
     bool choose_what_to_sync,
@@ -264,7 +259,6 @@
                          email,
                          gaia_id,
                          password,
-                         session_index,
                          auth_code,
                          signin_scoped_device_id,
                          choose_what_to_sync,
@@ -500,13 +494,6 @@
 
     host_resolver()->AddRule("*", "127.0.0.1");
 
-    deprecated_client_login_to_oauth2_response_ =
-        std::make_unique<net::test_server::ControllableHttpResponse>(
-            embedded_test_server(),
-            GaiaUrls::GetInstance()
-                ->deprecated_client_login_to_oauth2_url()
-                .path(),
-            /*relative_url_is_prefix=*/true);
     oauth2_token_exchange_success_ =
         std::make_unique<net::test_server::ControllableHttpResponse>(
             embedded_test_server(),
@@ -526,14 +513,6 @@
     ASSERT_TRUE(token_service_);
   }
 
-  void SimulateStartCookieForOAuthLoginTokenExchangeSuccess(
-      const std::string& cookie_string) {
-    deprecated_client_login_to_oauth2_response_->WaitForRequest();
-    deprecated_client_login_to_oauth2_response_->Send(
-        net::HTTP_OK, "text/html; charset=utf-8", "", {cookie_string});
-    deprecated_client_login_to_oauth2_response_->Done();
-  }
-
   void SimulateStartAuthCodeForOAuth2TokenExchangeSuccess(
       const std::string& json_response) {
     oauth2_token_exchange_success_->WaitForRequest();
@@ -559,8 +538,6 @@
 
  protected:
   std::unique_ptr<net::test_server::ControllableHttpResponse>
-      deprecated_client_login_to_oauth2_response_;
-  std::unique_ptr<net::test_server::ControllableHttpResponse>
       oauth2_token_exchange_success_;
 
  private:
@@ -573,34 +550,6 @@
   DISALLOW_COPY_AND_ASSIGN(InlineLoginHelperBrowserTest);
 };
 
-// Test signin helper calls correct fetcher methods when called with a session
-// index.
-IN_PROC_BROWSER_TEST_F(InlineLoginHelperBrowserTest, WithSessionIndex) {
-  base::WeakPtr<InlineLoginHandlerImpl> handler;
-  MockInlineSigninHelper helper(handler, test_shared_loader_factory(),
-                                browser()->profile(), GURL(), "foo@gmail.com",
-                                "gaiaid-12345", "password",
-                                "0",            // session index from above
-                                std::string(),  // auth code
-                                std::string(),
-                                false,   // choose what to sync
-                                false);  // confirm untrusted signin
-  base::RunLoop run_loop;
-  EXPECT_CALL(helper, OnClientOAuthSuccess(_))
-      .WillOnce(testing::InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-
-  SimulateStartCookieForOAuthLoginTokenExchangeSuccess(
-      "oauth_code=code; secure; httponly");
-
-  SimulateStartAuthCodeForOAuth2TokenExchangeSuccess(
-      R"({
-           "access_token": "access_token",
-           "expires_in": 1234567890,
-           "refresh_token": "refresh_token"
-         })");
-  run_loop.Run();
-}
-
 // Test signin helper calls correct fetcher methods when called with an
 // auth code.
 IN_PROC_BROWSER_TEST_F(InlineLoginHelperBrowserTest, WithAuthCode) {
@@ -608,7 +557,6 @@
   MockInlineSigninHelper helper(handler, test_shared_loader_factory(),
                                 browser()->profile(), GURL(), "foo@gmail.com",
                                 "gaiaid-12345", "password",
-                                "",           // session index
                                 "auth_code",  // auth code
                                 std::string(),
                                 false,   // choose what to sync
@@ -645,7 +593,6 @@
               ->GetURLLoaderFactoryForBrowserProcess(),
           browser()->profile(), url, "foo@gmail.com", "gaiaid-12345",
           "password",
-          "",           // session index
           "auth_code",  // auth code
           std::string(),
           false,  // choose what to sync
@@ -689,7 +636,6 @@
       new MockSyncStarterInlineSigninHelper(
           handler, test_shared_loader_factory(), browser()->profile(), url,
           "foo@gmail.com", "gaiaid-12345", "password",
-          "",           // session index
           "auth_code",  // auth code
           std::string(),
           true,   // choose what to sync
@@ -720,7 +666,6 @@
       new MockSyncStarterInlineSigninHelper(
           handler, test_shared_loader_factory(), browser()->profile(), url,
           "foo@gmail.com", "gaiaid-12345", "password",
-          "",           // session index
           "auth_code",  // auth code
           std::string(),
           false,  // choose what to sync
@@ -752,7 +697,6 @@
       new MockSyncStarterInlineSigninHelper(
           handler, test_shared_loader_factory(), browser()->profile(), url,
           "foo@gmail.com", "gaiaid-12345", "password",
-          "",           // session index
           "auth_code",  // auth code
           std::string(),
           false,  // choose what to sync
@@ -784,7 +728,6 @@
                             browser()->profile(),
                             Profile::CreateStatus::CREATE_STATUS_INITIALIZED,
                             url, "foo@gmail.com", "gaiaid-12345", "password",
-                            "",           // session index
                             "auth_code",  // auth code
                             std::string(),
                             false,  // choose what to sync
@@ -809,7 +752,6 @@
                             browser()->profile(),
                             Profile::CreateStatus::CREATE_STATUS_INITIALIZED,
                             url, "foo@gmail.com", "gaiaid-12345", "password",
-                            "",           // session index
                             "auth_code",  // auth code
                             std::string(),
                             false,  // choose what to sync
@@ -830,7 +772,7 @@
   MockSyncStarterInlineSigninHelper* helper =
       new MockSyncStarterInlineSigninHelper(
           handler, test_shared_loader_factory(), browser()->profile(), url,
-          "foo@gmail.com", "gaiaid-12345", "password", "", "auth_code",
+          "foo@gmail.com", "gaiaid-12345", "password", "auth_code",
           std::string(), false, false, true);
   EXPECT_CALL(
       *helper,
diff --git a/chrome/browser/web_applications/bookmark_apps/policy/web_app_policy_manager_unittest.cc b/chrome/browser/web_applications/bookmark_apps/policy/web_app_policy_manager_unittest.cc
index 0f6c2a75..0b4befc 100644
--- a/chrome/browser/web_applications/bookmark_apps/policy/web_app_policy_manager_unittest.cc
+++ b/chrome/browser/web_applications/bookmark_apps/policy/web_app_policy_manager_unittest.cc
@@ -92,29 +92,20 @@
     web_app::WebAppProvider::Get(profile())->Reset();
   }
 
-  // TODO(nigeltao): replace the N SimulateEtc methods with 1 method, that
-  // derives the extensions::Manifest::Location from app_info.install_source.
-  void SimulatePreviouslyInstalledApp(PendingAppManager::AppInfo app_info,
-                                      extensions::Manifest::Location location =
-                                          extensions::Manifest::INTERNAL) {
+  void SimulatePreviouslyInstalledApp(GURL url,
+                                      extensions::Manifest::Location location) {
     scoped_refptr<extensions::Extension> extension =
         extensions::ExtensionBuilder("Dummy Name")
             .SetLocation(location)
-            .SetID(crx_file::id_util::GenerateId("fake_app_id_for:" +
-                                                 app_info.url.spec()))
+            .SetID(
+                crx_file::id_util::GenerateId("fake_app_id_for:" + url.spec()))
             .Build();
     extensions::ExtensionRegistry* registry =
         extensions::ExtensionRegistry::Get(profile());
     registry->AddEnabled(extension);
 
     ExtensionIdsMap extension_ids_map(profile()->GetPrefs());
-    extension_ids_map.Insert(app_info.url, extension->id());
-  }
-
-  void SimulatePreviouslyInstalledPolicyApp(
-      PendingAppManager::AppInfo app_info) {
-    SimulatePreviouslyInstalledApp(
-        std::move(app_info), extensions::Manifest::EXTERNAL_POLICY_DOWNLOAD);
+    extension_ids_map.Insert(url, extension->id());
   }
 
  private:
@@ -216,9 +207,12 @@
 TEST_F(WebAppPolicyManagerTest, UninstallAppInstalledInPreviousSession) {
   // Simulate two policy apps and a regular app that were installed in the
   // previous session.
-  SimulatePreviouslyInstalledPolicyApp(GetWindowedAppInfo());
-  SimulatePreviouslyInstalledPolicyApp(GetTabbedAppInfo());
-  SimulatePreviouslyInstalledApp(GetDefaultContainerAppInfo());
+  SimulatePreviouslyInstalledApp(
+      GURL(kWindowedUrl), extensions::Manifest::EXTERNAL_POLICY_DOWNLOAD);
+  SimulatePreviouslyInstalledApp(
+      GURL(kTabbedUrl), extensions::Manifest::EXTERNAL_POLICY_DOWNLOAD);
+  SimulatePreviouslyInstalledApp(GURL(kDefaultContainerUrl),
+                                 extensions::Manifest::INTERNAL);
 
   // Push a policy with only one of the apps.
   base::Value first_list(base::Value::Type::LIST);
diff --git a/chrome/browser/web_data_service_factory.cc b/chrome/browser/web_data_service_factory.cc
index 7681a62..282cf9d 100644
--- a/chrome/browser/web_data_service_factory.cc
+++ b/chrome/browser/web_data_service_factory.cc
@@ -22,10 +22,6 @@
 #include "components/webdata_services/web_data_service_wrapper.h"
 #include "content/public/browser/browser_thread.h"
 
-#if defined(OS_WIN)
-#include "components/password_manager/core/browser/webdata/password_web_data_service_win.h"
-#endif
-
 using content::BrowserThread;
 
 namespace {
@@ -151,20 +147,6 @@
                  : scoped_refptr<TokenWebData>(nullptr);
 }
 
-#if defined(OS_WIN)
-// static
-scoped_refptr<PasswordWebDataService>
-WebDataServiceFactory::GetPasswordWebDataForProfile(
-    Profile* profile,
-    ServiceAccessType access_type) {
-  WebDataServiceWrapper* wrapper =
-      WebDataServiceFactory::GetForProfile(profile, access_type);
-  // |wrapper| can be null in Incognito mode.
-  return wrapper ? wrapper->GetPasswordWebData()
-                 : scoped_refptr<PasswordWebDataService>(nullptr);
-}
-#endif
-
 // static
 scoped_refptr<payments::PaymentManifestWebDataService>
 WebDataServiceFactory::GetPaymentManifestWebDataForProfile(
diff --git a/chrome/browser/web_data_service_factory.h b/chrome/browser/web_data_service_factory.h
index 013bc9b..c13c076fc 100644
--- a/chrome/browser/web_data_service_factory.h
+++ b/chrome/browser/web_data_service_factory.h
@@ -21,10 +21,6 @@
 class TokenWebData;
 class WebDataServiceWrapper;
 
-#if defined(OS_WIN)
-class PasswordWebDataService;
-#endif
-
 namespace payments {
 class PaymentManifestWebDataService;
 }
@@ -64,13 +60,6 @@
       Profile* profile,
       ServiceAccessType access_type);
 
-#if defined(OS_WIN)
-  // Returns the PasswordWebDataService associated with the |profile|.
-  static scoped_refptr<PasswordWebDataService> GetPasswordWebDataForProfile(
-      Profile* profile,
-      ServiceAccessType access_type);
-#endif
-
   static scoped_refptr<payments::PaymentManifestWebDataService>
   GetPaymentManifestWebDataForProfile(Profile* profile,
                                       ServiceAccessType access_type);
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 12073395..eeeef47 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -495,12 +495,6 @@
 const base::Feature kPushMessagingBackgroundMode{
     "PushMessagingBackgroundMode", base::FEATURE_DISABLED_BY_DEFAULT};
 
-#if !defined(OS_ANDROID)
-const base::Feature kRemoveUsageOfDeprecatedGaiaSigninEndpoint{
-    "RemoveUsageOfDeprecatedGaiaSigninEndpoint",
-    base::FEATURE_ENABLED_BY_DEFAULT};
-#endif
-
 const base::Feature kSafeSearchUrlReporting{"SafeSearchUrlReporting",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 3cae097..84505520 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -324,11 +324,6 @@
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kPushMessagingBackgroundMode;
 
-#if !defined(OS_ANDROID)
-COMPONENT_EXPORT(CHROME_FEATURES)
-extern const base::Feature kRemoveUsageOfDeprecatedGaiaSigninEndpoint;
-#endif
-
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kSafeSearchUrlReporting;
 
diff --git a/chrome/test/chromedriver/chrome/chrome_finder.cc b/chrome/test/chromedriver/chrome/chrome_finder.cc
index 9115579..4194f510e 100644
--- a/chrome/test/chromedriver/chrome/chrome_finder.cc
+++ b/chrome/test/chromedriver/chrome/chrome_finder.cc
@@ -12,10 +12,13 @@
 #include "base/base_paths.h"
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/environment.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/macros.h"
 #include "base/path_service.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 
 #if defined(OS_WIN)
@@ -64,6 +67,40 @@
 }
 #endif
 
+void GetPathsFromEnvironment(std::vector<base::FilePath>* paths) {
+  base::FilePath::StringType delimiter;
+  base::FilePath::StringType commonPath;
+  std::string path;
+  std::unique_ptr<base::Environment> env(base::Environment::Create());
+
+  if (!env->GetVar("PATH", &path)) {
+    return;
+  }
+
+#if defined(OS_WIN)
+  commonPath = base::UTF8ToWide(path);
+  delimiter = L";";
+#else
+  commonPath = path;
+  delimiter = ":";
+#endif
+
+  std::vector<base::FilePath::StringType> path_entries = base::SplitString(
+      commonPath, delimiter, base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+
+  for (auto& path_entry : path_entries) {
+#if defined(OS_WIN)
+    size_t size = path_entry.size();
+    if (size >= 2 && path_entry[0] == '"' && path_entry[size - 1] == '"') {
+      path_entry.erase(0, 1);
+      path_entry.erase(size - 2, 1);
+    }
+#endif
+    if (path_entry.size() > 0)
+      paths->emplace_back(path_entry);
+  }
+}
+
 }  // namespace
 
 namespace internal {
@@ -124,6 +161,7 @@
 
   std::vector<base::FilePath> locations;
   GetApplicationDirs(&locations);
+  GetPathsFromEnvironment(&locations);
   return internal::FindExe(
       base::Bind(&base::PathExists),
       browser_exes,
diff --git a/chrome/test/data/arc_default_apps/test_app2.json b/chrome/test/data/arc_board_default_apps/test_app2.json
similarity index 100%
rename from chrome/test/data/arc_default_apps/test_app2.json
rename to chrome/test/data/arc_board_default_apps/test_app2.json
diff --git a/chrome/test/data/arc_default_apps/test_app2/icon_100p_48.png b/chrome/test/data/arc_board_default_apps/test_app2/icon_100p_48.png
similarity index 100%
rename from chrome/test/data/arc_default_apps/test_app2/icon_100p_48.png
rename to chrome/test/data/arc_board_default_apps/test_app2/icon_100p_48.png
Binary files differ
diff --git a/chrome/test/data/arc_default_apps/test_app2/icon_100p_64.png b/chrome/test/data/arc_board_default_apps/test_app2/icon_100p_64.png
similarity index 100%
rename from chrome/test/data/arc_default_apps/test_app2/icon_100p_64.png
rename to chrome/test/data/arc_board_default_apps/test_app2/icon_100p_64.png
Binary files differ
diff --git a/chrome/test/data/arc_default_apps/test_app2/icon_200p_48.png b/chrome/test/data/arc_board_default_apps/test_app2/icon_200p_48.png
similarity index 100%
rename from chrome/test/data/arc_default_apps/test_app2/icon_200p_48.png
rename to chrome/test/data/arc_board_default_apps/test_app2/icon_200p_48.png
Binary files differ
diff --git a/chrome/test/data/arc_default_apps/test_app2/icon_200p_64.png b/chrome/test/data/arc_board_default_apps/test_app2/icon_200p_64.png
similarity index 100%
rename from chrome/test/data/arc_default_apps/test_app2/icon_200p_64.png
rename to chrome/test/data/arc_board_default_apps/test_app2/icon_200p_64.png
Binary files differ
diff --git a/chromecast/media/cma/backend/media_pipeline_backend_for_mixer.cc b/chromecast/media/cma/backend/media_pipeline_backend_for_mixer.cc
index dbafa0a..d852198 100644
--- a/chromecast/media/cma/backend/media_pipeline_backend_for_mixer.cc
+++ b/chromecast/media/cma/backend/media_pipeline_backend_for_mixer.cc
@@ -79,6 +79,7 @@
   audio_ready_to_play_ = !audio_decoder_;
   first_resume_processed_ = false;
 
+  start_playback_timestamp_us_ = INT64_MIN;
   start_playback_pts_us_ = start_pts;
 
   int64_t effective_start_pts =
@@ -92,7 +93,11 @@
   if (video_decoder_ && !video_decoder_->Start(start_playback_pts_us_, true))
     return false;
 
-  state_ = kStatePlaying;
+  if (av_sync_) {
+    state_ = kStatePaused;
+  } else {
+    state_ = kStatePlaying;
+  }
   return true;
 }
 
@@ -111,14 +116,19 @@
 }
 
 bool MediaPipelineBackendForMixer::Pause() {
-  DCHECK_EQ(kStatePlaying, state_);
-  if (!first_resume_processed_) {
+  if (av_sync_ && !first_resume_processed_) {
+    DCHECK_EQ(kStatePaused, state_);
     return true;
   }
-  if (audio_decoder_ && !audio_decoder_->Pause())
+
+  DCHECK_EQ(kStatePlaying, state_);
+
+  if (audio_decoder_ && !audio_decoder_->Pause()) {
     return false;
-  if (video_decoder_ && !video_decoder_->Pause())
+  }
+  if (video_decoder_ && !video_decoder_->Pause()) {
     return false;
+  }
   if (av_sync_) {
     av_sync_->NotifyPause();
   }
@@ -128,8 +138,8 @@
 }
 
 bool MediaPipelineBackendForMixer::Resume() {
-  if (!first_resume_processed_) {
-    DCHECK_EQ(kStatePlaying, state_);
+  DCHECK_EQ(kStatePaused, state_);
+  if (av_sync_ && !first_resume_processed_) {
 
     LOG(INFO) << "First resume received.";
     first_resume_processed_ = true;
@@ -137,7 +147,6 @@
     return true;
   }
 
-  DCHECK_EQ(kStatePaused, state_);
   if (av_sync_) {
     av_sync_->NotifyResume();
   }
@@ -238,54 +247,52 @@
 void MediaPipelineBackendForMixer::OnVideoReadyToPlay() {
   DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
   DCHECK(!video_ready_to_play_);
+  DCHECK(video_decoder_);
 
   LOG(INFO) << "Video ready to play";
-
   video_ready_to_play_ = true;
 
-  TryStartPlayback();
-}
+  if (av_sync_) {
+    TryStartPlayback();
+  } else if (!IsIgnorePtsMode()) {
+    start_playback_timestamp_us_ = MonotonicClockNow();
+    LOG(INFO) << "Starting playback at=" << start_playback_timestamp_us_;
 
-void MediaPipelineBackendForMixer::OnAudioReadyForPlayback() {
-  LOG(INFO) << "The audio is ready for playback.";
-  DCHECK(!audio_ready_to_play_);
-
-  audio_ready_to_play_ = true;
-
-  TryStartPlayback();
-}
-
-void MediaPipelineBackendForMixer::TryStartPlayback() {
-  DCHECK(state_ == kStatePlaying || state_ == kStatePaused);
-
-  if (av_sync_ && !(audio_ready_to_play_ && first_resume_processed_ &&
-                    video_ready_to_play_)) {
-    return;
-  }
-
-  if (IsIgnorePtsMode()) {
-    start_playback_timestamp_us_ = INT64_MIN;
-  } else {
-    start_playback_timestamp_us_ =
-        MonotonicClockNow() + kSyncedPlaybackStartDelayUs;
-  }
-
-  LOG(INFO) << "Starting playback at=" << start_playback_timestamp_us_;
-
-  // Note that SetPts needs to be called even if the playback is not AV sync'd
-  // (for example we only have a video stream).
-  if (video_decoder_ && !IsIgnorePtsMode()) {
     video_decoder_->SetPts(start_playback_timestamp_us_,
                            start_playback_pts_us_);
   }
+}
 
-  if (audio_decoder_ && av_sync_) {
-    audio_decoder_->StartPlaybackAt(start_playback_timestamp_us_);
-  }
+void MediaPipelineBackendForMixer::OnAudioReadyForPlayback() {
+  DCHECK(!audio_ready_to_play_);
+
+  LOG(INFO) << "Audio ready to play";
+  audio_ready_to_play_ = true;
 
   if (av_sync_) {
-    av_sync_->NotifyStart(start_playback_timestamp_us_, start_playback_pts_us_);
+    TryStartPlayback();
   }
+}
+
+void MediaPipelineBackendForMixer::TryStartPlayback() {
+  DCHECK(av_sync_);
+  DCHECK(state_ == kStatePaused);
+  DCHECK(!IsIgnorePtsMode());
+  DCHECK(video_decoder_);
+  DCHECK(audio_decoder_);
+
+  if (!audio_ready_to_play_ || !video_ready_to_play_ ||
+      !first_resume_processed_) {
+    return;
+  }
+
+  start_playback_timestamp_us_ =
+      MonotonicClockNow() + kSyncedPlaybackStartDelayUs;
+  LOG(INFO) << "Starting playback at=" << start_playback_timestamp_us_;
+
+  video_decoder_->SetPts(start_playback_timestamp_us_, start_playback_pts_us_);
+  audio_decoder_->StartPlaybackAt(start_playback_timestamp_us_);
+  av_sync_->NotifyStart(start_playback_timestamp_us_, start_playback_pts_us_);
   state_ = kStatePlaying;
 }
 
diff --git a/chromeos/dbus/smb_provider_client.cc b/chromeos/dbus/smb_provider_client.cc
index f087056..0e34f72 100644
--- a/chromeos/dbus/smb_provider_client.cc
+++ b/chromeos/dbus/smb_provider_client.cc
@@ -76,7 +76,7 @@
                                  smbprovider::kMountMethod);
     dbus::MessageWriter writer(&method_call);
     writer.AppendProtoAsArrayOfBytes(options);
-    writer.AppendFileDescriptor(password_fd.release());
+    writer.AppendFileDescriptor(password_fd.get());
     CallMethod(&method_call, &SmbProviderClientImpl::HandleMountCallback,
                &callback);
   }
@@ -97,7 +97,7 @@
                                  smbprovider::kRemountMethod);
     dbus::MessageWriter writer(&method_call);
     writer.AppendProtoAsArrayOfBytes(options);
-    writer.AppendFileDescriptor(password_fd.release());
+    writer.AppendFileDescriptor(password_fd.get());
 
     CallDefaultMethod(&method_call, &callback);
   }
@@ -214,7 +214,7 @@
                                  smbprovider::kWriteFileMethod);
     dbus::MessageWriter writer(&method_call);
     writer.AppendProtoAsArrayOfBytes(options);
-    writer.AppendFileDescriptor(temp_fd.release());
+    writer.AppendFileDescriptor(temp_fd.get());
     CallDefaultMethod(&method_call, &callback);
   }
 
diff --git a/chromeos/services/device_sync/public/cpp/fake_device_sync_client.cc b/chromeos/services/device_sync/public/cpp/fake_device_sync_client.cc
index 33bbe4ad..df114d9 100644
--- a/chromeos/services/device_sync/public/cpp/fake_device_sync_client.cc
+++ b/chromeos/services/device_sync/public/cpp/fake_device_sync_client.cc
@@ -17,12 +17,12 @@
 
 void FakeDeviceSyncClient::ForceEnrollmentNow(
     mojom::DeviceSync::ForceEnrollmentNowCallback callback) {
-  std::move(callback).Run(force_enrollment_now_success_);
+  force_enrollment_now_callback_queue_.push(std::move(callback));
 }
 
 void FakeDeviceSyncClient::ForceSyncNow(
     mojom::DeviceSync::ForceSyncNowCallback callback) {
-  std::move(callback).Run(force_sync_now_success_);
+  force_sync_now_callback_queue_.push(std::move(callback));
 }
 
 cryptauth::RemoteDeviceRefList FakeDeviceSyncClient::GetSyncedDevices() {
@@ -54,6 +54,27 @@
   get_debug_info_callback_queue_.push(std::move(callback));
 }
 
+int FakeDeviceSyncClient::GetForceEnrollmentNowCallbackQueueSize() {
+  return force_enrollment_now_callback_queue_.size();
+}
+
+int FakeDeviceSyncClient::GetForceSyncNowCallbackQueueSize() {
+  return force_sync_now_callback_queue_.size();
+}
+
+void FakeDeviceSyncClient::InvokePendingForceEnrollmentNowCallback(
+    bool success) {
+  DCHECK(force_enrollment_now_callback_queue_.size() > 0);
+  std::move(force_enrollment_now_callback_queue_.front()).Run(success);
+  force_enrollment_now_callback_queue_.pop();
+}
+
+void FakeDeviceSyncClient::InvokePendingForceSyncNowCallback(bool success) {
+  DCHECK(force_sync_now_callback_queue_.size() > 0);
+  std::move(force_sync_now_callback_queue_.front()).Run(success);
+  force_sync_now_callback_queue_.pop();
+}
+
 void FakeDeviceSyncClient::InvokePendingSetSoftwareFeatureStateCallback(
     mojom::NetworkRequestResult result_code) {
   std::move(set_software_feature_state_callback_queue_.front())
diff --git a/chromeos/services/device_sync/public/cpp/fake_device_sync_client.h b/chromeos/services/device_sync/public/cpp/fake_device_sync_client.h
index 1bd60e84..7257604 100644
--- a/chromeos/services/device_sync/public/cpp/fake_device_sync_client.h
+++ b/chromeos/services/device_sync/public/cpp/fake_device_sync_client.h
@@ -27,6 +27,10 @@
   FakeDeviceSyncClient();
   ~FakeDeviceSyncClient() override;
 
+  int GetForceEnrollmentNowCallbackQueueSize();
+  int GetForceSyncNowCallbackQueueSize();
+  void InvokePendingForceEnrollmentNowCallback(bool success);
+  void InvokePendingForceSyncNowCallback(bool success);
   void InvokePendingSetSoftwareFeatureStateCallback(
       mojom::NetworkRequestResult result_code);
   void InvokePendingFindEligibleDevicesCallback(
@@ -35,14 +39,6 @@
       cryptauth::RemoteDeviceRefList ineligible_devices);
   void InvokePendingGetDebugInfoCallback(mojom::DebugInfoPtr debug_info_ptr);
 
-  void set_force_enrollment_now_success(bool force_enrollment_now_success) {
-    force_enrollment_now_success_ = force_enrollment_now_success;
-  }
-
-  void set_force_sync_now_success(bool force_sync_now_success) {
-    force_sync_now_success_ = force_sync_now_success;
-  }
-
   void set_synced_devices(cryptauth::RemoteDeviceRefList synced_devices) {
     synced_devices_ = synced_devices;
   }
@@ -52,9 +48,9 @@
     local_device_metadata_ = local_device_metadata;
   }
 
-  using DeviceSyncClient::NotifyReady;
   using DeviceSyncClient::NotifyEnrollmentFinished;
   using DeviceSyncClient::NotifyNewDevicesSynced;
+  using DeviceSyncClient::NotifyReady;
 
  private:
   // DeviceSyncClient:
@@ -73,11 +69,13 @@
                            FindEligibleDevicesCallback callback) override;
   void GetDebugInfo(mojom::DeviceSync::GetDebugInfoCallback callback) override;
 
-  bool force_enrollment_now_success_;
-  bool force_sync_now_success_;
   cryptauth::RemoteDeviceRefList synced_devices_;
   base::Optional<cryptauth::RemoteDeviceRef> local_device_metadata_;
 
+  std::queue<mojom::DeviceSync::ForceEnrollmentNowCallback>
+      force_enrollment_now_callback_queue_;
+  std::queue<mojom::DeviceSync::ForceSyncNowCallback>
+      force_sync_now_callback_queue_;
   std::queue<mojom::DeviceSync::SetSoftwareFeatureStateCallback>
       set_software_feature_state_callback_queue_;
   std::queue<FindEligibleDevicesCallback> find_eligible_devices_callback_queue_;
diff --git a/chromeos/services/multidevice_setup/BUILD.gn b/chromeos/services/multidevice_setup/BUILD.gn
index b4c229a1..acf403c2 100644
--- a/chromeos/services/multidevice_setup/BUILD.gn
+++ b/chromeos/services/multidevice_setup/BUILD.gn
@@ -14,6 +14,8 @@
     "account_status_change_delegate_notifier.h",
     "account_status_change_delegate_notifier_impl.cc",
     "account_status_change_delegate_notifier_impl.h",
+    "device_reenroller.cc",
+    "device_reenroller.h",
     "eligible_host_devices_provider.h",
     "eligible_host_devices_provider_impl.cc",
     "eligible_host_devices_provider_impl.h",
@@ -110,6 +112,7 @@
 
   sources = [
     "account_status_change_delegate_notifier_impl_unittest.cc",
+    "device_reenroller_unittest.cc",
     "eligible_host_devices_provider_impl_unittest.cc",
     "feature_state_manager_impl_unittest.cc",
     "host_backend_delegate_impl_unittest.cc",
diff --git a/chromeos/services/multidevice_setup/device_reenroller.cc b/chromeos/services/multidevice_setup/device_reenroller.cc
new file mode 100644
index 0000000..30fe9c2
--- /dev/null
+++ b/chromeos/services/multidevice_setup/device_reenroller.cc
@@ -0,0 +1,166 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/multidevice_setup/device_reenroller.h"
+
+#include "base/containers/flat_set.h"
+#include "base/no_destructor.h"
+#include "chromeos/components/proximity_auth/logging/logging.h"
+#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
+#include "components/cryptauth/gcm_device_info_provider.h"
+
+namespace chromeos {
+
+namespace multidevice_setup {
+
+namespace {
+
+// The number of minutes to wait before retrying a failed re-enrollment or
+// device sync attempt.
+const int kNumMinutesBetweenRetries = 5;
+
+std::vector<cryptauth::SoftwareFeature>
+ComputeSupportedSoftwareFeaturesSortedDedupedListFromGcmDeviceInfo(
+    const cryptauth::GcmDeviceInfo& gcm_device_info) {
+  base::flat_set<cryptauth::SoftwareFeature> sorted_and_deduped_set;
+  for (int i = 0; i < gcm_device_info.supported_software_features_size(); ++i) {
+    sorted_and_deduped_set.insert(
+        gcm_device_info.supported_software_features(i));
+  }
+  return std::vector<cryptauth::SoftwareFeature>(sorted_and_deduped_set.begin(),
+                                                 sorted_and_deduped_set.end());
+}
+
+std::vector<cryptauth::SoftwareFeature>
+ComputeSupportedSoftwareFeaturesSortedDedupedListFromLocalDeviceMetadata(
+    const cryptauth::RemoteDeviceRef& local_device_metadata) {
+  base::flat_set<cryptauth::SoftwareFeature> sorted_and_deduped_set;
+  for (int i = cryptauth::SoftwareFeature_MIN;
+       i <= cryptauth::SoftwareFeature_MAX; ++i) {
+    cryptauth::SoftwareFeature feature =
+        static_cast<cryptauth::SoftwareFeature>(i);
+    if (local_device_metadata.GetSoftwareFeatureState(feature) !=
+        cryptauth::SoftwareFeatureState::kNotSupported) {
+      sorted_and_deduped_set.insert(feature);
+    }
+  }
+  return std::vector<cryptauth::SoftwareFeature>(sorted_and_deduped_set.begin(),
+                                                 sorted_and_deduped_set.end());
+}
+
+}  // namespace
+
+// static
+DeviceReenroller::Factory* DeviceReenroller::Factory::test_factory_ = nullptr;
+
+// static
+DeviceReenroller::Factory* DeviceReenroller::Factory::Get() {
+  if (test_factory_)
+    return test_factory_;
+
+  static base::NoDestructor<Factory> factory;
+  return factory.get();
+}
+
+// static
+void DeviceReenroller::Factory::SetFactoryForTesting(Factory* test_factory) {
+  test_factory_ = test_factory;
+}
+
+DeviceReenroller::Factory::~Factory() = default;
+
+std::unique_ptr<DeviceReenroller> DeviceReenroller::Factory::BuildInstance(
+    device_sync::DeviceSyncClient* device_sync_client,
+    const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider,
+    std::unique_ptr<base::OneShotTimer> timer) {
+  return base::WrapUnique(new DeviceReenroller(
+      device_sync_client, gcm_device_info_provider, std::move(timer)));
+}
+
+DeviceReenroller::~DeviceReenroller() = default;
+
+DeviceReenroller::DeviceReenroller(
+    device_sync::DeviceSyncClient* device_sync_client,
+    const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider,
+    std::unique_ptr<base::OneShotTimer> timer)
+    : device_sync_client_(device_sync_client),
+      gcm_supported_software_features_(
+          ComputeSupportedSoftwareFeaturesSortedDedupedListFromGcmDeviceInfo(
+              gcm_device_info_provider->GetGcmDeviceInfo())),
+      timer_(std::move(timer)) {
+  // If the current set of supported software features from GcmDeviceInfo
+  // differs from that of the local device metadata on the CryptAuth server,
+  // attempt re-enrollment. Note: Both lists here are sorted and duplicate-free.
+  if (gcm_supported_software_features_ !=
+      ComputeSupportedSoftwareFeaturesSortedDedupedListFromLocalDeviceMetadata(
+          *device_sync_client_->GetLocalDeviceMetadata())) {
+    AttemptReenrollment();
+  }
+}
+
+void DeviceReenroller::AttemptReenrollment() {
+  DCHECK(!timer_->IsRunning());
+  device_sync_client_->ForceEnrollmentNow(base::BindOnce(
+      &DeviceReenroller::OnForceEnrollmentNowComplete, base::Unretained(this)));
+}
+
+void DeviceReenroller::AttemptDeviceSync() {
+  DCHECK(!timer_->IsRunning());
+  device_sync_client_->ForceSyncNow(base::BindOnce(
+      &DeviceReenroller::OnForceSyncNowComplete, base::Unretained(this)));
+}
+
+void DeviceReenroller::OnForceEnrollmentNowComplete(bool success) {
+  if (success) {
+    PA_LOG(INFO) << "DeviceReenroller::OnForceEnrollmentNowComplete(): "
+                 << "Forced enrollment attempt was successful. "
+                 << "Syncing devices now.";
+    AttemptDeviceSync();
+    return;
+  }
+  PA_LOG(WARNING) << "DeviceReenroller::OnForceEnrollmentNowComplete(): "
+                  << "Forced enrollment attempt was unsuccessful. Retrying in "
+                  << kNumMinutesBetweenRetries << " minutes.";
+  timer_->Start(FROM_HERE,
+                base::TimeDelta::FromMinutes(kNumMinutesBetweenRetries),
+                base::BindOnce(&DeviceReenroller::AttemptReenrollment,
+                               base::Unretained(this)));
+}
+
+void DeviceReenroller::OnForceSyncNowComplete(bool success) {
+  // This is used to track if the device sync properly updated the local device
+  // metadata to reflect the supported software features from GcmDeviceInfo.
+  bool local_device_metadata_agrees =
+      device_sync_client_->GetLocalDeviceMetadata() &&
+      ComputeSupportedSoftwareFeaturesSortedDedupedListFromLocalDeviceMetadata(
+          *device_sync_client_->GetLocalDeviceMetadata()) ==
+          gcm_supported_software_features_;
+
+  if (success && local_device_metadata_agrees) {
+    PA_LOG(INFO) << "DeviceReenroller::OnForceSyncNowComplete(): "
+                 << "Forced device sync attempt was successful.";
+    return;
+  }
+  if (!success) {
+    PA_LOG(WARNING) << "DeviceReenroller::OnForceSyncNowComplete(): "
+                    << "Forced device sync attempt was unsuccessful. "
+                    << "Retrying in " << kNumMinutesBetweenRetries
+                    << " minutes.";
+  } else {
+    DCHECK(!local_device_metadata_agrees);
+    PA_LOG(WARNING) << "DeviceReenroller::OnForceSyncNowComplete(): "
+                    << "The local device metadata's supported software "
+                    << "features do not agree with the set extracted from GCM "
+                    << "device info. Retrying in " << kNumMinutesBetweenRetries
+                    << " minutes.";
+  }
+  timer_->Start(FROM_HERE,
+                base::TimeDelta::FromMinutes(kNumMinutesBetweenRetries),
+                base::BindOnce(&DeviceReenroller::AttemptDeviceSync,
+                               base::Unretained(this)));
+}
+
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
diff --git a/chromeos/services/multidevice_setup/device_reenroller.h b/chromeos/services/multidevice_setup/device_reenroller.h
new file mode 100644
index 0000000..96bc9e88
--- /dev/null
+++ b/chromeos/services/multidevice_setup/device_reenroller.h
@@ -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.
+
+#ifndef CHROMEOS_SERVICES_MULTIDEVICE_SETUP_DEVICE_REENROLLER_H_
+#define CHROMEOS_SERVICES_MULTIDEVICE_SETUP_DEVICE_REENROLLER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "components/cryptauth/proto/cryptauth_api.pb.h"
+
+namespace base {
+class OneShotTimer;
+}  // namespace base
+
+namespace cryptauth {
+class GcmDeviceInfoProvider;
+}  // namespace cryptauth
+
+namespace chromeos {
+
+namespace device_sync {
+class DeviceSyncClient;
+}  // namespace device_sync
+
+namespace multidevice_setup {
+
+// The DeviceReenroller constructor re-enrolls and syncs the device if the set
+// of supported SoftwareFeatures in the current GCM device info differs from
+// that of the local device metadata on the CryptAuth server.
+class DeviceReenroller {
+ public:
+  class Factory {
+   public:
+    static Factory* Get();
+    static void SetFactoryForTesting(Factory* test_factory);
+    virtual ~Factory();
+    virtual std::unique_ptr<DeviceReenroller> BuildInstance(
+        device_sync::DeviceSyncClient* device_sync_client,
+        const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider,
+        std::unique_ptr<base::OneShotTimer> timer =
+            std::make_unique<base::OneShotTimer>());
+
+   private:
+    static Factory* test_factory_;
+  };
+
+  virtual ~DeviceReenroller();
+
+ private:
+  DeviceReenroller(
+      device_sync::DeviceSyncClient* device_sync_client,
+      const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider,
+      std::unique_ptr<base::OneShotTimer> timer);
+
+  void AttemptReenrollment();
+  void AttemptDeviceSync();
+
+  // If the re-enrollment was successful, force a device sync; otherwise, retry
+  // re-enrollment every 5 minutes or until success.
+  void OnForceEnrollmentNowComplete(bool success);
+  // If the device sync was successful and the list of supported software
+  // features on the CryptAuth server now agrees with the list of supported
+  // software features in GcmDeviceInfo, log the success; otherwise, retry
+  // device sync every 5 minutes or until success.
+  void OnForceSyncNowComplete(bool success);
+
+  device_sync::DeviceSyncClient* device_sync_client_;
+  // The sorted and deduped list of supported software features extracted from
+  // GcmDeviceInfo.
+  std::vector<cryptauth::SoftwareFeature> gcm_supported_software_features_;
+  std::unique_ptr<base::OneShotTimer> timer_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeviceReenroller);
+};
+
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_MULTIDEVICE_SETUP_DEVICE_REENROLLER_H_
diff --git a/chromeos/services/multidevice_setup/device_reenroller_unittest.cc b/chromeos/services/multidevice_setup/device_reenroller_unittest.cc
new file mode 100644
index 0000000..70f6e0a
--- /dev/null
+++ b/chromeos/services/multidevice_setup/device_reenroller_unittest.cc
@@ -0,0 +1,377 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/multidevice_setup/device_reenroller.h"
+
+#include "base/macros.h"
+#include "base/timer/mock_timer.h"
+#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
+#include "components/cryptauth/fake_gcm_device_info_provider.h"
+#include "components/cryptauth/remote_device_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+
+namespace multidevice_setup {
+
+class MultiDeviceSetupDeviceReenrollerTest : public testing::Test {
+ protected:
+  MultiDeviceSetupDeviceReenrollerTest()
+      : test_local_device_(cryptauth::CreateRemoteDeviceRefForTest()) {}
+  ~MultiDeviceSetupDeviceReenrollerTest() override = default;
+
+  // testing::Test:
+  void SetUp() override {
+    fake_device_sync_client_ =
+        std::make_unique<device_sync::FakeDeviceSyncClient>();
+
+    fake_gcm_device_info_provider_ =
+        std::make_unique<cryptauth::FakeGcmDeviceInfoProvider>(
+            cryptauth::GcmDeviceInfo());
+  }
+
+  void SetLocalDeviceMetadataSoftwareFeaturesMap(
+      const std::map<cryptauth::SoftwareFeature,
+                     cryptauth::SoftwareFeatureState>& map) {
+    cryptauth::GetMutableRemoteDevice(test_local_device_)->software_features =
+        map;
+    fake_device_sync_client_->set_local_device_metadata(test_local_device_);
+  }
+
+  void SetFakeGcmDeviceInfoProviderWithSupportedSoftwareFeatures(
+      const std::vector<cryptauth::SoftwareFeature>&
+          supported_software_features) {
+    cryptauth::GcmDeviceInfo gcm_device_info;
+    gcm_device_info.clear_supported_software_features();
+    for (cryptauth::SoftwareFeature feature : supported_software_features) {
+      gcm_device_info.add_supported_software_features(feature);
+    }
+    fake_gcm_device_info_provider_ =
+        std::make_unique<cryptauth::FakeGcmDeviceInfoProvider>(gcm_device_info);
+  }
+
+  device_sync::FakeDeviceSyncClient* fake_device_sync_client() {
+    return fake_device_sync_client_.get();
+  }
+
+  base::MockOneShotTimer* timer() { return mock_timer_; }
+
+  void CreateDeviceReenroller() {
+    auto mock_timer = std::make_unique<base::MockOneShotTimer>();
+    mock_timer_ = mock_timer.get();
+
+    device_reenroller_ = DeviceReenroller::Factory::Get()->BuildInstance(
+        fake_device_sync_client_.get(), fake_gcm_device_info_provider_.get(),
+        std::move(mock_timer));
+  }
+
+ private:
+  std::unique_ptr<DeviceReenroller> device_reenroller_;
+
+  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
+  std::unique_ptr<cryptauth::FakeGcmDeviceInfoProvider>
+      fake_gcm_device_info_provider_;
+  base::MockOneShotTimer* mock_timer_;
+
+  cryptauth::RemoteDeviceRef test_local_device_;
+
+  DISALLOW_COPY_AND_ASSIGN(MultiDeviceSetupDeviceReenrollerTest);
+};
+
+TEST_F(MultiDeviceSetupDeviceReenrollerTest,
+       IfGmcDeviceInfoAndLocalDeviceMetadataMatchThenNoReenrollment) {
+  // Set the current local device metadata to contain a sample of supported
+  // software features.
+  SetLocalDeviceMetadataSoftwareFeaturesMap(
+      std::map<cryptauth::SoftwareFeature, cryptauth::SoftwareFeatureState>{
+          {cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported}});
+  // Set the current GcmDeviceInfo supported software features to contain the
+  // same set.
+  SetFakeGcmDeviceInfoProviderWithSupportedSoftwareFeatures(
+      std::vector<cryptauth::SoftwareFeature>{
+          cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+          cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT});
+
+  CreateDeviceReenroller();
+
+  // No enrollment or device sync attempts should have taken place nor should
+  // any be scheduled.
+  EXPECT_EQ(
+      0, fake_device_sync_client()->GetForceEnrollmentNowCallbackQueueSize());
+  EXPECT_EQ(0, fake_device_sync_client()->GetForceSyncNowCallbackQueueSize());
+  EXPECT_FALSE(timer()->IsRunning());
+}
+
+TEST_F(MultiDeviceSetupDeviceReenrollerTest,
+       IfFeaturesBecomeUnsupportedThenUpdateAndReenroll) {
+  // Set the current local device metadata to contain a sample of supported
+  // software features.
+  SetLocalDeviceMetadataSoftwareFeaturesMap(
+      std::map<cryptauth::SoftwareFeature, cryptauth::SoftwareFeatureState>{
+          {cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported}});
+  // Remove one supported software feature in the GcmDeviceInfo.
+  SetFakeGcmDeviceInfoProviderWithSupportedSoftwareFeatures(
+      std::vector<cryptauth::SoftwareFeature>{
+          cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT});
+
+  CreateDeviceReenroller();
+
+  // Assume successful enrollment, sync, and local device metadata update.
+  EXPECT_EQ(
+      1, fake_device_sync_client()->GetForceEnrollmentNowCallbackQueueSize());
+  fake_device_sync_client()->InvokePendingForceEnrollmentNowCallback(
+      true /* success */);
+  EXPECT_EQ(1, fake_device_sync_client()->GetForceSyncNowCallbackQueueSize());
+  SetLocalDeviceMetadataSoftwareFeaturesMap(
+      std::map<cryptauth::SoftwareFeature, cryptauth::SoftwareFeatureState>{
+          {cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported}});
+  fake_device_sync_client()->InvokePendingForceSyncNowCallback(
+      true /* success */);
+  // No other attempts should be scheduled.
+  EXPECT_FALSE(timer()->IsRunning());
+}
+
+TEST_F(MultiDeviceSetupDeviceReenrollerTest,
+       IfFeaturesBecomeSupportedThenUpdateAndReenroll) {
+  // Set the current local device metadata to contain a sample of supported
+  // software features.
+  SetLocalDeviceMetadataSoftwareFeaturesMap(
+      std::map<cryptauth::SoftwareFeature, cryptauth::SoftwareFeatureState>{
+          {cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported}});
+  // Add one more supported software feature in the GcmDeviceInfo.
+  SetFakeGcmDeviceInfoProviderWithSupportedSoftwareFeatures(
+      std::vector<cryptauth::SoftwareFeature>{
+          cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+          cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+          cryptauth::SoftwareFeature::MAGIC_TETHER_CLIENT});
+
+  CreateDeviceReenroller();
+
+  // Assume successful enrollment, sync, and local device metadata update.
+  EXPECT_EQ(
+      1, fake_device_sync_client()->GetForceEnrollmentNowCallbackQueueSize());
+  fake_device_sync_client()->InvokePendingForceEnrollmentNowCallback(
+      true /* success */);
+  EXPECT_EQ(1, fake_device_sync_client()->GetForceSyncNowCallbackQueueSize());
+  SetLocalDeviceMetadataSoftwareFeaturesMap(
+      std::map<cryptauth::SoftwareFeature, cryptauth::SoftwareFeatureState>{
+          {cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::MAGIC_TETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported}});
+  fake_device_sync_client()->InvokePendingForceSyncNowCallback(
+      true /* success */);
+  // No other attempts should be scheduled.
+  EXPECT_FALSE(timer()->IsRunning());
+}
+
+TEST_F(MultiDeviceSetupDeviceReenrollerTest,
+       IfReenrollmentFailsThenScheduleAnotherAttempt) {
+  // Set the current local device metadata to contain a sample of supported
+  // software features.
+  SetLocalDeviceMetadataSoftwareFeaturesMap(
+      std::map<cryptauth::SoftwareFeature, cryptauth::SoftwareFeatureState>{
+          {cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported}});
+  // Add one more supported software feature in the GcmDeviceInfo to trigger a
+  // re-enrollment attempt.
+  SetFakeGcmDeviceInfoProviderWithSupportedSoftwareFeatures(
+      std::vector<cryptauth::SoftwareFeature>{
+          cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+          cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+          cryptauth::SoftwareFeature::MAGIC_TETHER_CLIENT});
+
+  CreateDeviceReenroller();
+
+  EXPECT_EQ(
+      1, fake_device_sync_client()->GetForceEnrollmentNowCallbackQueueSize());
+  // Assume unsuccessful re-enrollment attempt.
+  fake_device_sync_client()->InvokePendingForceEnrollmentNowCallback(
+      false /* success */);
+  // No device sync call should have been made.
+  EXPECT_EQ(0, fake_device_sync_client()->GetForceSyncNowCallbackQueueSize());
+  // Another re-enrollment attempt should be scheduled.
+  EXPECT_TRUE(timer()->IsRunning());
+  // This should trigger another enrollment attempt.
+  timer()->Fire();
+  EXPECT_EQ(
+      1, fake_device_sync_client()->GetForceEnrollmentNowCallbackQueueSize());
+  EXPECT_EQ(0, fake_device_sync_client()->GetForceSyncNowCallbackQueueSize());
+}
+
+TEST_F(MultiDeviceSetupDeviceReenrollerTest,
+       IfDeviceSyncFailsThenScheduleAnotherAttempt) {
+  // Set the current local device metadata to contain a sample of supported
+  // software features.
+  SetLocalDeviceMetadataSoftwareFeaturesMap(
+      std::map<cryptauth::SoftwareFeature, cryptauth::SoftwareFeatureState>{
+          {cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported}});
+  // Add one more supported software feature in the GcmDeviceInfo to trigger a
+  // re-enrollment attempt.
+  SetFakeGcmDeviceInfoProviderWithSupportedSoftwareFeatures(
+      std::vector<cryptauth::SoftwareFeature>{
+          cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+          cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+          cryptauth::SoftwareFeature::MAGIC_TETHER_CLIENT});
+
+  CreateDeviceReenroller();
+
+  // Assume successful re-enrollment attempt.
+  EXPECT_EQ(
+      1, fake_device_sync_client()->GetForceEnrollmentNowCallbackQueueSize());
+  fake_device_sync_client()->InvokePendingForceEnrollmentNowCallback(
+      true /* success */);
+  EXPECT_EQ(1, fake_device_sync_client()->GetForceSyncNowCallbackQueueSize());
+  // Assume unsuccessful device sync attempt.
+  fake_device_sync_client()->InvokePendingForceSyncNowCallback(
+      false /* success */);
+  // Another device sync attempt should be scheduled.
+  EXPECT_TRUE(timer()->IsRunning());
+  // This should trigger another device sync attempt.
+  timer()->Fire();
+  EXPECT_EQ(
+      0, fake_device_sync_client()->GetForceEnrollmentNowCallbackQueueSize());
+  EXPECT_EQ(1, fake_device_sync_client()->GetForceSyncNowCallbackQueueSize());
+}
+
+TEST_F(MultiDeviceSetupDeviceReenrollerTest,
+       IfLocalDeviceMetadataNotUpdatedCorrectlyThenScheduleAnotherSyncAttempt) {
+  // Set the current local device metadata to contain a sample of supported
+  // software features.
+  SetLocalDeviceMetadataSoftwareFeaturesMap(
+      std::map<cryptauth::SoftwareFeature, cryptauth::SoftwareFeatureState>{
+          {cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported}});
+  // Add one more supported software feature in the GcmDeviceInfo to trigger a
+  // re-enrollment attempt.
+  SetFakeGcmDeviceInfoProviderWithSupportedSoftwareFeatures(
+      std::vector<cryptauth::SoftwareFeature>{
+          cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+          cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+          cryptauth::SoftwareFeature::MAGIC_TETHER_CLIENT});
+
+  CreateDeviceReenroller();
+  // Assume successful enrollment and device sync.
+  EXPECT_EQ(
+      1, fake_device_sync_client()->GetForceEnrollmentNowCallbackQueueSize());
+  fake_device_sync_client()->InvokePendingForceEnrollmentNowCallback(
+      true /* success */);
+  EXPECT_EQ(1, fake_device_sync_client()->GetForceSyncNowCallbackQueueSize());
+  // Assume local device metadata was not updated correctly.
+  SetLocalDeviceMetadataSoftwareFeaturesMap(
+      std::map<cryptauth::SoftwareFeature, cryptauth::SoftwareFeatureState>{
+          {cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported}});
+  fake_device_sync_client()->InvokePendingForceSyncNowCallback(
+      true /* success */);
+  // Another device sync attempt should be scheduled.
+  EXPECT_TRUE(timer()->IsRunning());
+  // This should trigger another device sync attempt.
+  timer()->Fire();
+  EXPECT_EQ(
+      0, fake_device_sync_client()->GetForceEnrollmentNowCallbackQueueSize());
+  EXPECT_EQ(1, fake_device_sync_client()->GetForceSyncNowCallbackQueueSize());
+}
+
+TEST_F(MultiDeviceSetupDeviceReenrollerTest,
+       GcmDeviceInfoFeatureListOrderingAndDuplicatesAreIrrelevantForReenroll) {
+  // Set the current local device metadata to contain a sample of supported
+  // software features.
+  SetLocalDeviceMetadataSoftwareFeaturesMap(
+      std::map<cryptauth::SoftwareFeature, cryptauth::SoftwareFeatureState>{
+          {cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::MAGIC_TETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported}});
+  // Add one more supported software feature in the GcmDeviceInfo.
+  SetFakeGcmDeviceInfoProviderWithSupportedSoftwareFeatures(
+      std::vector<cryptauth::SoftwareFeature>{
+          cryptauth::SoftwareFeature::SMS_CONNECT_CLIENT,
+          cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+          cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+          cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+          cryptauth::SoftwareFeature::SMS_CONNECT_CLIENT,
+          cryptauth::SoftwareFeature::MAGIC_TETHER_CLIENT});
+
+  CreateDeviceReenroller();
+
+  // Assume successful enrollment, sync, and local device metadata update.
+  EXPECT_EQ(
+      1, fake_device_sync_client()->GetForceEnrollmentNowCallbackQueueSize());
+  fake_device_sync_client()->InvokePendingForceEnrollmentNowCallback(
+      true /* success */);
+  EXPECT_EQ(1, fake_device_sync_client()->GetForceSyncNowCallbackQueueSize());
+  SetLocalDeviceMetadataSoftwareFeaturesMap(
+      std::map<cryptauth::SoftwareFeature, cryptauth::SoftwareFeatureState>{
+          {cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::MAGIC_TETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::SMS_CONNECT_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported}});
+  fake_device_sync_client()->InvokePendingForceSyncNowCallback(
+      true /* success */);
+  // No other attempts should be scheduled.
+  EXPECT_FALSE(timer()->IsRunning());
+}
+
+TEST_F(
+    MultiDeviceSetupDeviceReenrollerTest,
+    GcmDeviceInfoFeatureListOrderingAndDuplicatesAreIrrelevantForNoReenroll) {
+  // Set the current local device metadata to contain a sample of supported
+  // software features.
+  SetLocalDeviceMetadataSoftwareFeaturesMap(
+      std::map<cryptauth::SoftwareFeature, cryptauth::SoftwareFeatureState>{
+          {cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported},
+          {cryptauth::SoftwareFeature::MAGIC_TETHER_CLIENT,
+           cryptauth::SoftwareFeatureState::kSupported}});
+  // Add one more supported software feature in the GcmDeviceInfo.
+  SetFakeGcmDeviceInfoProviderWithSupportedSoftwareFeatures(
+      std::vector<cryptauth::SoftwareFeature>{
+          cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+          cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+          cryptauth::SoftwareFeature::EASY_UNLOCK_CLIENT,
+          cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT,
+          cryptauth::SoftwareFeature::MAGIC_TETHER_CLIENT});
+
+  CreateDeviceReenroller();
+
+  EXPECT_EQ(
+      0, fake_device_sync_client()->GetForceEnrollmentNowCallbackQueueSize());
+  EXPECT_EQ(0, fake_device_sync_client()->GetForceSyncNowCallbackQueueSize());
+  // No other attempts should be scheduled.
+  EXPECT_FALSE(timer()->IsRunning());
+}
+
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_impl.cc b/chromeos/services/multidevice_setup/multidevice_setup_impl.cc
index cf24736..cd51893 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_impl.cc
+++ b/chromeos/services/multidevice_setup/multidevice_setup_impl.cc
@@ -9,6 +9,7 @@
 #include "base/time/default_clock.h"
 #include "chromeos/components/proximity_auth/logging/logging.h"
 #include "chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.h"
+#include "chromeos/services/multidevice_setup/device_reenroller.h"
 #include "chromeos/services/multidevice_setup/eligible_host_devices_provider_impl.h"
 #include "chromeos/services/multidevice_setup/feature_state_manager_impl.h"
 #include "chromeos/services/multidevice_setup/host_backend_delegate_impl.h"
@@ -54,10 +55,12 @@
     secure_channel::SecureChannelClient* secure_channel_client,
     AuthTokenValidator* auth_token_validator,
     std::unique_ptr<AndroidSmsAppHelperDelegate>
-        android_sms_app_helper_delegate) {
+        android_sms_app_helper_delegate,
+    const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider) {
   return base::WrapUnique(new MultiDeviceSetupImpl(
       pref_service, device_sync_client, secure_channel_client,
-      auth_token_validator, std::move(android_sms_app_helper_delegate)));
+      auth_token_validator, std::move(android_sms_app_helper_delegate),
+      gcm_device_info_provider));
 }
 
 MultiDeviceSetupImpl::MultiDeviceSetupImpl(
@@ -66,7 +69,8 @@
     secure_channel::SecureChannelClient* secure_channel_client,
     AuthTokenValidator* auth_token_validator,
     std::unique_ptr<AndroidSmsAppHelperDelegate>
-        android_sms_app_helper_delegate)
+        android_sms_app_helper_delegate,
+    const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider)
     : android_sms_app_helper_delegate_(
           std::move(android_sms_app_helper_delegate)),
       eligible_host_devices_provider_(
@@ -102,6 +106,9 @@
                               pref_service,
                               setup_flow_completion_recorder_.get(),
                               base::DefaultClock::GetInstance())),
+      device_reenroller_(DeviceReenroller::Factory::Get()->BuildInstance(
+          device_sync_client,
+          gcm_device_info_provider)),
       auth_token_validator_(auth_token_validator) {
   host_status_provider_->AddObserver(this);
   feature_state_manager_->AddObserver(this);
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_impl.h b/chromeos/services/multidevice_setup/multidevice_setup_impl.h
index 46ec211..038c3381 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_impl.h
+++ b/chromeos/services/multidevice_setup/multidevice_setup_impl.h
@@ -17,6 +17,10 @@
 
 class PrefService;
 
+namespace cryptauth {
+class GcmDeviceInfoProvider;
+}  // namespace cryptauth
+
 namespace chromeos {
 
 namespace device_sync {
@@ -32,6 +36,7 @@
 class AccountStatusChangeDelegateNotifier;
 class AndroidSmsAppHelperDelegate;
 class AuthTokenValidator;
+class DeviceReenroller;
 class HostBackendDelegate;
 class HostStatusProvider;
 class HostVerifier;
@@ -54,7 +59,8 @@
         secure_channel::SecureChannelClient* secure_channel_client,
         AuthTokenValidator* auth_token_validator,
         std::unique_ptr<AndroidSmsAppHelperDelegate>
-            android_sms_app_helper_delegate);
+            android_sms_app_helper_delegate,
+        const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider);
 
    private:
     static Factory* test_factory_;
@@ -72,7 +78,8 @@
       secure_channel::SecureChannelClient* secure_channel_client,
       AuthTokenValidator* auth_token_validator,
       std::unique_ptr<AndroidSmsAppHelperDelegate>
-          android_sms_app_helper_delegate);
+          android_sms_app_helper_delegate,
+      const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider);
 
   // mojom::MultiDeviceSetup:
   void SetAccountStatusChangeDelegate(
@@ -114,6 +121,7 @@
   std::unique_ptr<FeatureStateManager> feature_state_manager_;
   std::unique_ptr<SetupFlowCompletionRecorder> setup_flow_completion_recorder_;
   std::unique_ptr<AccountStatusChangeDelegateNotifier> delegate_notifier_;
+  std::unique_ptr<DeviceReenroller> device_reenroller_;
   AuthTokenValidator* auth_token_validator_;
 
   mojo::InterfacePtrSet<mojom::HostStatusObserver> host_status_observers_;
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc b/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc
index 33ce0a5..8a18c141 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc
+++ b/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/test/scoped_task_environment.h"
 #include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
 #include "chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.h"
+#include "chromeos/services/multidevice_setup/device_reenroller.h"
 #include "chromeos/services/multidevice_setup/eligible_host_devices_provider_impl.h"
 #include "chromeos/services/multidevice_setup/fake_account_status_change_delegate.h"
 #include "chromeos/services/multidevice_setup/fake_account_status_change_delegate_notifier.h"
@@ -31,6 +32,7 @@
 #include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
 #include "chromeos/services/multidevice_setup/setup_flow_completion_recorder_impl.h"
 #include "chromeos/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
+#include "components/cryptauth/fake_gcm_device_info_provider.h"
 #include "components/cryptauth/remote_device_test_util.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -350,6 +352,36 @@
   DISALLOW_COPY_AND_ASSIGN(FakeAccountStatusChangeDelegateNotifierFactory);
 };
 
+class FakeDeviceReenrollerFactory : public DeviceReenroller::Factory {
+ public:
+  FakeDeviceReenrollerFactory(
+      device_sync::FakeDeviceSyncClient* expected_device_sync_client,
+      const cryptauth::FakeGcmDeviceInfoProvider*
+          expected_gcm_device_info_provider)
+      : expected_device_sync_client_(expected_device_sync_client),
+        expected_gcm_device_info_provider_(expected_gcm_device_info_provider) {}
+
+  ~FakeDeviceReenrollerFactory() override = default;
+
+ private:
+  // DeviceReenroller::Factory:
+  std::unique_ptr<DeviceReenroller> BuildInstance(
+      device_sync::DeviceSyncClient* device_sync_client,
+      const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider,
+      std::unique_ptr<base::OneShotTimer> timer) override {
+    EXPECT_EQ(expected_device_sync_client_, device_sync_client);
+    EXPECT_EQ(expected_gcm_device_info_provider_, gcm_device_info_provider);
+    // Only check inputs and return nullptr. We do not want to trigger the
+    // DeviceReenroller logic in these unit tests.
+    return nullptr;
+  }
+
+  device_sync::FakeDeviceSyncClient* expected_device_sync_client_;
+  const cryptauth::GcmDeviceInfoProvider* expected_gcm_device_info_provider_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeDeviceReenrollerFactory);
+};
+
 }  // namespace
 
 class MultiDeviceSetupImplTest : public testing::Test {
@@ -375,6 +407,10 @@
     fake_android_sms_app_helper_delegate_ =
         fake_android_sms_app_helper_delegate.get();
 
+    fake_gcm_device_info_provider_ =
+        std::make_unique<cryptauth::FakeGcmDeviceInfoProvider>(
+            cryptauth::GcmDeviceInfo());
+
     fake_eligible_host_devices_provider_factory_ =
         std::make_unique<FakeEligibleHostDevicesProviderFactory>(
             fake_device_sync_client_.get());
@@ -422,10 +458,18 @@
     AccountStatusChangeDelegateNotifierImpl::Factory::SetFactoryForTesting(
         fake_account_status_change_delegate_notifier_factory_.get());
 
+    fake_device_reenroller_factory_ =
+        std::make_unique<FakeDeviceReenrollerFactory>(
+            fake_device_sync_client_.get(),
+            fake_gcm_device_info_provider_.get());
+    DeviceReenroller::Factory::SetFactoryForTesting(
+        fake_device_reenroller_factory_.get());
+
     multidevice_setup_ = MultiDeviceSetupImpl::Factory::Get()->BuildInstance(
         test_pref_service_.get(), fake_device_sync_client_.get(),
         fake_secure_channel_client_.get(), fake_auth_token_validator_.get(),
-        std::move(fake_android_sms_app_helper_delegate));
+        std::move(fake_android_sms_app_helper_delegate),
+        fake_gcm_device_info_provider_.get());
   }
 
   void TearDown() override {
@@ -437,6 +481,7 @@
     SetupFlowCompletionRecorderImpl::Factory::SetFactoryForTesting(nullptr);
     AccountStatusChangeDelegateNotifierImpl::Factory::SetFactoryForTesting(
         nullptr);
+    DeviceReenroller::Factory::SetFactoryForTesting(nullptr);
   }
 
   void CallSetAccountStatusChangeDelegate() {
@@ -685,6 +730,8 @@
   std::unique_ptr<secure_channel::FakeSecureChannelClient>
       fake_secure_channel_client_;
   std::unique_ptr<FakeAuthTokenValidator> fake_auth_token_validator_;
+  std::unique_ptr<cryptauth::FakeGcmDeviceInfoProvider>
+      fake_gcm_device_info_provider_;
 
   std::unique_ptr<FakeEligibleHostDevicesProviderFactory>
       fake_eligible_host_devices_provider_factory_;
@@ -699,6 +746,7 @@
       fake_setup_flow_completion_recorder_factory_;
   std::unique_ptr<FakeAccountStatusChangeDelegateNotifierFactory>
       fake_account_status_change_delegate_notifier_factory_;
+  std::unique_ptr<FakeDeviceReenrollerFactory> fake_device_reenroller_factory_;
   FakeAndroidSmsAppHelperDelegate* fake_android_sms_app_helper_delegate_;
 
   std::unique_ptr<FakeAccountStatusChangeDelegate>
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_initializer.cc b/chromeos/services/multidevice_setup/multidevice_setup_initializer.cc
index f1ad70d..9457b14d 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_initializer.cc
+++ b/chromeos/services/multidevice_setup/multidevice_setup_initializer.cc
@@ -44,10 +44,12 @@
     secure_channel::SecureChannelClient* secure_channel_client,
     AuthTokenValidator* auth_token_validator,
     std::unique_ptr<AndroidSmsAppHelperDelegate>
-        android_sms_app_helper_delegate) {
+        android_sms_app_helper_delegate,
+    const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider) {
   return base::WrapUnique(new MultiDeviceSetupInitializer(
       pref_service, device_sync_client, secure_channel_client,
-      auth_token_validator, std::move(android_sms_app_helper_delegate)));
+      auth_token_validator, std::move(android_sms_app_helper_delegate),
+      gcm_device_info_provider));
 }
 
 MultiDeviceSetupInitializer::MultiDeviceSetupInitializer(
@@ -56,13 +58,15 @@
     secure_channel::SecureChannelClient* secure_channel_client,
     AuthTokenValidator* auth_token_validator,
     std::unique_ptr<AndroidSmsAppHelperDelegate>
-        android_sms_app_helper_delegate)
+        android_sms_app_helper_delegate,
+    const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider)
     : pref_service_(pref_service),
       device_sync_client_(device_sync_client),
       secure_channel_client_(secure_channel_client),
       auth_token_validator_(auth_token_validator),
       android_sms_app_helper_delegate_(
-          std::move(android_sms_app_helper_delegate)) {
+          std::move(android_sms_app_helper_delegate)),
+      gcm_device_info_provider_(gcm_device_info_provider) {
   if (device_sync_client_->is_ready()) {
     InitializeImplementation();
     return;
@@ -221,7 +225,8 @@
 
   multidevice_setup_impl_ = MultiDeviceSetupImpl::Factory::Get()->BuildInstance(
       pref_service_, device_sync_client_, secure_channel_client_,
-      auth_token_validator_, std::move(android_sms_app_helper_delegate_));
+      auth_token_validator_, std::move(android_sms_app_helper_delegate_),
+      gcm_device_info_provider_);
 
   if (pending_delegate_) {
     multidevice_setup_impl_->SetAccountStatusChangeDelegate(
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_initializer.h b/chromeos/services/multidevice_setup/multidevice_setup_initializer.h
index 0bc80f79..fabccff 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_initializer.h
+++ b/chromeos/services/multidevice_setup/multidevice_setup_initializer.h
@@ -16,6 +16,10 @@
 
 class PrefService;
 
+namespace cryptauth {
+class GcmDeviceInfoProvider;
+}  // namespace cryptauth
+
 namespace chromeos {
 
 namespace secure_channel {
@@ -45,7 +49,8 @@
         secure_channel::SecureChannelClient* secure_channel_client,
         AuthTokenValidator* auth_token_validator,
         std::unique_ptr<AndroidSmsAppHelperDelegate>
-            android_sms_app_helper_delegate);
+            android_sms_app_helper_delegate,
+        const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider);
 
    private:
     static Factory* test_factory_;
@@ -60,7 +65,8 @@
       secure_channel::SecureChannelClient* secure_channel_client,
       AuthTokenValidator* auth_token_validator,
       std::unique_ptr<AndroidSmsAppHelperDelegate>
-          android_sms_app_helper_delegate);
+          android_sms_app_helper_delegate,
+      const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider);
 
   // mojom::MultiDeviceSetup:
   void SetAccountStatusChangeDelegate(
@@ -94,6 +100,7 @@
   secure_channel::SecureChannelClient* secure_channel_client_;
   AuthTokenValidator* auth_token_validator_;
   std::unique_ptr<AndroidSmsAppHelperDelegate> android_sms_app_helper_delegate_;
+  const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider_;
 
   std::unique_ptr<mojom::MultiDeviceSetup> multidevice_setup_impl_;
 
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_service.cc b/chromeos/services/multidevice_setup/multidevice_setup_service.cc
index c77a8f8..6cfce7e 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_service.cc
+++ b/chromeos/services/multidevice_setup/multidevice_setup_service.cc
@@ -6,6 +6,7 @@
 
 #include "chromeos/components/proximity_auth/logging/logging.h"
 #include "chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.h"
+#include "chromeos/services/multidevice_setup/device_reenroller.h"
 #include "chromeos/services/multidevice_setup/host_backend_delegate_impl.h"
 #include "chromeos/services/multidevice_setup/host_verifier_impl.h"
 #include "chromeos/services/multidevice_setup/multidevice_setup_base.h"
@@ -32,14 +33,16 @@
     secure_channel::SecureChannelClient* secure_channel_client,
     AuthTokenValidator* auth_token_validator,
     std::unique_ptr<AndroidSmsAppHelperDelegate>
-        android_sms_app_helper_delegate)
+        android_sms_app_helper_delegate,
+    const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider)
     : multidevice_setup_(
           MultiDeviceSetupInitializer::Factory::Get()->BuildInstance(
               pref_service,
               device_sync_client,
               secure_channel_client,
               auth_token_validator,
-              std::move(android_sms_app_helper_delegate))) {}
+              std::move(android_sms_app_helper_delegate),
+              gcm_device_info_provider)) {}
 
 MultiDeviceSetupService::~MultiDeviceSetupService() = default;
 
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_service.h b/chromeos/services/multidevice_setup/multidevice_setup_service.h
index 63b0e839..50bdffb 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_service.h
+++ b/chromeos/services/multidevice_setup/multidevice_setup_service.h
@@ -14,6 +14,10 @@
 class PrefService;
 class PrefRegistrySimple;
 
+namespace cryptauth {
+class GcmDeviceInfoProvider;
+}  // namespace cryptauth
+
 namespace chromeos {
 
 namespace device_sync {
@@ -41,7 +45,8 @@
       secure_channel::SecureChannelClient* secure_channel_client,
       AuthTokenValidator* auth_token_validator,
       std::unique_ptr<AndroidSmsAppHelperDelegate>
-          android_sms_app_helper_delegate);
+          android_sms_app_helper_delegate,
+      const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider);
   ~MultiDeviceSetupService() override;
 
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_service_unittest.cc b/chromeos/services/multidevice_setup/multidevice_setup_service_unittest.cc
index 146cfd0aa..e480443 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_service_unittest.cc
+++ b/chromeos/services/multidevice_setup/multidevice_setup_service_unittest.cc
@@ -18,6 +18,7 @@
 #include "chromeos/services/multidevice_setup/public/mojom/constants.mojom.h"
 #include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
 #include "chromeos/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
+#include "components/cryptauth/fake_gcm_device_info_provider.h"
 #include "components/cryptauth/remote_device_test_util.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "services/service_manager/public/cpp/test/test_connector_factory.h"
@@ -39,13 +40,16 @@
       device_sync::FakeDeviceSyncClient* expected_device_sync_client,
       secure_channel::FakeSecureChannelClient* expected_secure_channel_client,
       FakeAuthTokenValidator* expected_auth_token_validator,
-      FakeAndroidSmsAppHelperDelegate* expected_android_sms_app_helper_delegate)
+      FakeAndroidSmsAppHelperDelegate* expected_android_sms_app_helper_delegate,
+      const cryptauth::FakeGcmDeviceInfoProvider*
+          expected_gcm_device_info_provider)
       : expected_testing_pref_service_(expected_testing_pref_service),
         expected_device_sync_client_(expected_device_sync_client),
         expected_secure_channel_client_(expected_secure_channel_client),
         expected_auth_token_validator_(expected_auth_token_validator),
         expected_android_sms_app_helper_delegate_(
-            expected_android_sms_app_helper_delegate) {}
+            expected_android_sms_app_helper_delegate),
+        expected_gcm_device_info_provider_(expected_gcm_device_info_provider) {}
 
   ~FakeMultiDeviceSetupFactory() override = default;
 
@@ -58,7 +62,9 @@
       secure_channel::SecureChannelClient* secure_channel_client,
       AuthTokenValidator* auth_token_validator,
       std::unique_ptr<AndroidSmsAppHelperDelegate>
-          android_sms_app_helper_delegate) override {
+          android_sms_app_helper_delegate,
+      const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider)
+      override {
     EXPECT_FALSE(instance_);
     EXPECT_EQ(expected_testing_pref_service_, pref_service);
     EXPECT_EQ(expected_device_sync_client_, device_sync_client);
@@ -66,6 +72,7 @@
     EXPECT_EQ(expected_auth_token_validator_, auth_token_validator);
     EXPECT_EQ(expected_android_sms_app_helper_delegate_,
               android_sms_app_helper_delegate.get());
+    EXPECT_EQ(expected_gcm_device_info_provider_, gcm_device_info_provider);
 
     auto instance = std::make_unique<FakeMultiDeviceSetup>();
     instance_ = instance.get();
@@ -77,6 +84,8 @@
   secure_channel::FakeSecureChannelClient* expected_secure_channel_client_;
   FakeAuthTokenValidator* expected_auth_token_validator_;
   FakeAndroidSmsAppHelperDelegate* expected_android_sms_app_helper_delegate_;
+  const cryptauth::FakeGcmDeviceInfoProvider*
+      expected_gcm_device_info_provider_;
 
   FakeMultiDeviceSetup* instance_ = nullptr;
 
@@ -105,12 +114,16 @@
         std::make_unique<FakeAndroidSmsAppHelperDelegate>();
     fake_android_sms_app_helper_delegate_ =
         fake_android_sms_app_helper_delegate.get();
+    fake_gcm_device_info_provider_ =
+        std::make_unique<cryptauth::FakeGcmDeviceInfoProvider>(
+            cryptauth::GcmDeviceInfo());
 
     fake_multidevice_setup_factory_ =
         std::make_unique<FakeMultiDeviceSetupFactory>(
             test_pref_service_.get(), fake_device_sync_client_.get(),
             fake_secure_channel_client_.get(), fake_auth_token_validator_.get(),
-            fake_android_sms_app_helper_delegate_);
+            fake_android_sms_app_helper_delegate_,
+            fake_gcm_device_info_provider_.get());
     MultiDeviceSetupImpl::Factory::SetFactoryForTesting(
         fake_multidevice_setup_factory_.get());
 
@@ -120,7 +133,8 @@
                 test_pref_service_.get(), fake_device_sync_client_.get(),
                 fake_secure_channel_client_.get(),
                 fake_auth_token_validator_.get(),
-                std::move(fake_android_sms_app_helper_delegate)));
+                std::move(fake_android_sms_app_helper_delegate),
+                fake_gcm_device_info_provider_.get()));
 
     auto connector = connector_factory_->CreateConnector();
     connector->BindInterface(mojom::kServiceName, &multidevice_setup_ptr_);
@@ -178,6 +192,8 @@
       fake_secure_channel_client_;
   std::unique_ptr<FakeAuthTokenValidator> fake_auth_token_validator_;
   FakeAndroidSmsAppHelperDelegate* fake_android_sms_app_helper_delegate_;
+  std::unique_ptr<cryptauth::FakeGcmDeviceInfoProvider>
+      fake_gcm_device_info_provider_;
 
   std::unique_ptr<FakeMultiDeviceSetupFactory> fake_multidevice_setup_factory_;
 
diff --git a/chromeos/services/multidevice_setup/public/cpp/multidevice_setup_client_impl_unittest.cc b/chromeos/services/multidevice_setup/public/cpp/multidevice_setup_client_impl_unittest.cc
index 053642b..9e0bae0 100644
--- a/chromeos/services/multidevice_setup/public/cpp/multidevice_setup_client_impl_unittest.cc
+++ b/chromeos/services/multidevice_setup/public/cpp/multidevice_setup_client_impl_unittest.cc
@@ -47,7 +47,9 @@
       secure_channel::SecureChannelClient* secure_channel_client,
       AuthTokenValidator* auth_token_validator,
       std::unique_ptr<AndroidSmsAppHelperDelegate>
-          android_sms_app_helper_delegate) override {
+          android_sms_app_helper_delegate,
+      const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider)
+      override {
     EXPECT_TRUE(fake_multidevice_setup_);
     return std::move(fake_multidevice_setup_);
   }
@@ -122,7 +124,8 @@
     auto multidevice_setup_service = std::make_unique<MultiDeviceSetupService>(
         nullptr /* pref_service */, nullptr /* device_sync_client */,
         nullptr /* secure_channel_client */, nullptr /* auth_token_validator */,
-        nullptr /* android_sms_app_helper_delegate */);
+        nullptr /* android_sms_app_helper_delegate */,
+        nullptr /* gcm_device_info_provider */);
 
     connector_factory_ =
         service_manager::TestConnectorFactory::CreateForUniqueService(
diff --git a/chromeos/services/secure_channel/BUILD.gn b/chromeos/services/secure_channel/BUILD.gn
index 1fea8ae..27039ef7 100644
--- a/chromeos/services/secure_channel/BUILD.gn
+++ b/chromeos/services/secure_channel/BUILD.gn
@@ -78,6 +78,7 @@
     "multiplexed_channel.h",
     "multiplexed_channel_impl.cc",
     "multiplexed_channel_impl.h",
+    "pending_ble_connection_request_base.h",
     "pending_ble_initiator_connection_request.cc",
     "pending_ble_initiator_connection_request.h",
     "pending_ble_listener_connection_request.cc",
@@ -210,6 +211,7 @@
     "connection_attempt_base_unittest.cc",
     "error_tolerant_ble_advertisement_impl_unittest.cc",
     "multiplexed_channel_impl_unittest.cc",
+    "pending_ble_connection_request_base_unittest.cc",
     "pending_ble_initiator_connection_request_unittest.cc",
     "pending_ble_listener_connection_request_unittest.cc",
     "pending_connection_manager_impl_unittest.cc",
diff --git a/chromeos/services/secure_channel/pending_ble_connection_request_base.h b/chromeos/services/secure_channel/pending_ble_connection_request_base.h
new file mode 100644
index 0000000..10ebbb3
--- /dev/null
+++ b/chromeos/services/secure_channel/pending_ble_connection_request_base.h
@@ -0,0 +1,78 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_SECURE_CHANNEL_PENDING_BLE_CONNECTION_REQUEST_BASE_H_
+#define CHROMEOS_SERVICES_SECURE_CHANNEL_PENDING_BLE_CONNECTION_REQUEST_BASE_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "chromeos/services/secure_channel/pending_connection_request_base.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+
+namespace chromeos {
+
+namespace secure_channel {
+
+template <typename BleFailureDetailType>
+class PendingBleConnectionRequestBase
+    : public PendingConnectionRequestBase<BleFailureDetailType>,
+      public device::BluetoothAdapter::Observer {
+ public:
+  ~PendingBleConnectionRequestBase() override {
+    bluetooth_adapter_->RemoveObserver(this);
+  }
+
+ protected:
+  PendingBleConnectionRequestBase(
+      std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
+      ConnectionPriority connection_priority,
+      const std::string& readable_request_type_for_logging,
+      PendingConnectionRequestDelegate* delegate,
+      scoped_refptr<device::BluetoothAdapter> bluetooth_adapter)
+      : PendingConnectionRequestBase<BleFailureDetailType>(
+            std::move(client_connection_parameters),
+            connection_priority,
+            readable_request_type_for_logging,
+            delegate),
+        bluetooth_adapter_(std::move(bluetooth_adapter)) {
+    bluetooth_adapter_->AddObserver(this);
+  }
+
+ private:
+  friend class SecureChannelPendingBleConnectionRequestBaseTest;
+
+  // device::BluetoothAdapter::Observer:
+  void AdapterPoweredChanged(device::BluetoothAdapter* adapter,
+                             bool powered) override {
+    DCHECK_EQ(bluetooth_adapter_, adapter);
+    if (powered)
+      return;
+
+    this->StopRequestDueToConnectionFailures(
+        mojom::ConnectionAttemptFailureReason::ADAPTER_DISABLED);
+  }
+
+  void AdapterPresentChanged(device::BluetoothAdapter* adapter,
+                             bool present) override {
+    DCHECK_EQ(bluetooth_adapter_, adapter);
+    if (present)
+      return;
+
+    this->StopRequestDueToConnectionFailures(
+        mojom::ConnectionAttemptFailureReason::ADAPTER_NOT_PRESENT);
+  }
+
+  scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_;
+
+  DISALLOW_COPY_AND_ASSIGN(PendingBleConnectionRequestBase);
+};
+
+}  // namespace secure_channel
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_SECURE_CHANNEL_PENDING_BLE_CONNECTION_REQUEST_BASE_H_
diff --git a/chromeos/services/secure_channel/pending_ble_connection_request_base_unittest.cc b/chromeos/services/secure_channel/pending_ble_connection_request_base_unittest.cc
new file mode 100644
index 0000000..230215d
--- /dev/null
+++ b/chromeos/services/secure_channel/pending_ble_connection_request_base_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/secure_channel/pending_ble_connection_request_base.h"
+
+#include <memory>
+
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "chromeos/services/secure_channel/fake_client_connection_parameters.h"
+#include "chromeos/services/secure_channel/fake_connection_delegate.h"
+#include "chromeos/services/secure_channel/fake_pending_connection_request_delegate.h"
+#include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+
+namespace secure_channel {
+
+namespace {
+
+const char kTestReadableRequestTypeForLogging[] = "Test Request Type";
+const char kTestFeature[] = "testFeature";
+enum class TestFailureDetail { kTestFailureReason };
+
+// Since PendingBleConnectionRequestBase is templatized, a concrete
+// implementation is needed for its test.
+class TestPendingBleConnectionRequestBase
+    : public PendingBleConnectionRequestBase<TestFailureDetail> {
+ public:
+  TestPendingBleConnectionRequestBase(
+      std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
+      ConnectionPriority connection_priority,
+      PendingConnectionRequestDelegate* delegate,
+      scoped_refptr<device::BluetoothAdapter> bluetooth_adapter)
+      : PendingBleConnectionRequestBase<TestFailureDetail>(
+            std::move(client_connection_parameters),
+            connection_priority,
+            kTestReadableRequestTypeForLogging,
+            delegate,
+            std::move(bluetooth_adapter)) {}
+  ~TestPendingBleConnectionRequestBase() override = default;
+
+  // PendingConnectionRequest<TestFailureDetailType>:
+  void HandleConnectionFailure(TestFailureDetail failure_detail) override {}
+};
+
+}  // namespace
+
+class SecureChannelPendingBleConnectionRequestBaseTest : public testing::Test {
+ protected:
+  SecureChannelPendingBleConnectionRequestBaseTest() = default;
+  ~SecureChannelPendingBleConnectionRequestBaseTest() override = default;
+
+  void SetUp() override {
+    auto fake_client_connection_parameters =
+        std::make_unique<FakeClientConnectionParameters>(kTestFeature);
+    fake_client_connection_parameters_ =
+        fake_client_connection_parameters.get();
+
+    mock_adapter_ =
+        base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
+
+    fake_pending_connection_request_delegate_ =
+        std::make_unique<FakePendingConnectionRequestDelegate>();
+
+    test_pending_ble_connection_request_ =
+        std::make_unique<TestPendingBleConnectionRequestBase>(
+            std::move(fake_client_connection_parameters),
+            ConnectionPriority::kLow,
+            fake_pending_connection_request_delegate_.get(), mock_adapter_);
+
+    EXPECT_TRUE(mock_adapter_->GetObservers().HasObserver(
+        test_pending_ble_connection_request_.get()));
+  }
+
+  const base::Optional<
+      PendingConnectionRequestDelegate::FailedConnectionReason>&
+  GetFailedConnectionReason() {
+    return fake_pending_connection_request_delegate_
+        ->GetFailedConnectionReasonForId(
+            test_pending_ble_connection_request_->GetRequestId());
+  }
+
+  const base::Optional<mojom::ConnectionAttemptFailureReason>&
+  GetConnectionAttemptFailureReason() const {
+    return fake_client_connection_parameters_->failure_reason();
+  }
+
+  void SimulateAdapterPoweredChanged(bool powered) {
+    test_pending_ble_connection_request_->AdapterPoweredChanged(
+        mock_adapter_.get(), powered);
+  }
+
+  void SimulateAdapterPresentChanged(bool present) {
+    test_pending_ble_connection_request_->AdapterPresentChanged(
+        mock_adapter_.get(), present);
+  }
+
+ private:
+  FakeClientConnectionParameters* fake_client_connection_parameters_;
+  std::unique_ptr<FakePendingConnectionRequestDelegate>
+      fake_pending_connection_request_delegate_;
+  scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> mock_adapter_;
+
+  std::unique_ptr<TestPendingBleConnectionRequestBase>
+      test_pending_ble_connection_request_;
+
+  DISALLOW_COPY_AND_ASSIGN(SecureChannelPendingBleConnectionRequestBaseTest);
+};
+
+TEST_F(SecureChannelPendingBleConnectionRequestBaseTest,
+       HandleAdapterPoweredChanged) {
+  // Turning the adapter on should do nothing.
+  SimulateAdapterPoweredChanged(true /* powered */);
+  EXPECT_FALSE(GetFailedConnectionReason());
+  EXPECT_FALSE(GetConnectionAttemptFailureReason());
+
+  // Turning the adapter off should trigger a failure.
+  SimulateAdapterPoweredChanged(false /* powered */);
+  EXPECT_EQ(
+      PendingConnectionRequestDelegate::FailedConnectionReason::kRequestFailed,
+      *GetFailedConnectionReason());
+  EXPECT_EQ(mojom::ConnectionAttemptFailureReason::ADAPTER_DISABLED,
+            *GetConnectionAttemptFailureReason());
+}
+
+TEST_F(SecureChannelPendingBleConnectionRequestBaseTest,
+       HandleAdapterPresentChanged) {
+  // The adapter appearing should do nothing.
+  SimulateAdapterPresentChanged(true /* present */);
+  EXPECT_FALSE(GetFailedConnectionReason());
+  EXPECT_FALSE(GetConnectionAttemptFailureReason());
+
+  // The adapter disappearing should trigger a failure.
+  SimulateAdapterPresentChanged(false /* present */);
+  EXPECT_EQ(
+      PendingConnectionRequestDelegate::FailedConnectionReason::kRequestFailed,
+      *GetFailedConnectionReason());
+  EXPECT_EQ(mojom::ConnectionAttemptFailureReason::ADAPTER_NOT_PRESENT,
+            *GetConnectionAttemptFailureReason());
+}
+
+}  // namespace secure_channel
+
+}  // namespace chromeos
diff --git a/chromeos/services/secure_channel/pending_ble_initiator_connection_request.cc b/chromeos/services/secure_channel/pending_ble_initiator_connection_request.cc
index a3a5e757..4b8ca286 100644
--- a/chromeos/services/secure_channel/pending_ble_initiator_connection_request.cc
+++ b/chromeos/services/secure_channel/pending_ble_initiator_connection_request.cc
@@ -4,6 +4,8 @@
 
 #include "chromeos/services/secure_channel/pending_ble_initiator_connection_request.h"
 
+#include <utility>
+
 #include "base/memory/ptr_util.h"
 #include "base/no_destructor.h"
 #include "chromeos/components/proximity_auth/logging/logging.h"
@@ -13,7 +15,9 @@
 namespace secure_channel {
 
 namespace {
+
 const char kBleInitiatorReadableRequestTypeForLogging[] = "BLE Initiator";
+
 }  // namespace
 
 // The number of times to attempt to connect to a device without receiving any
@@ -60,20 +64,24 @@
 PendingBleInitiatorConnectionRequest::Factory::BuildInstance(
     std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
     ConnectionPriority connection_priority,
-    PendingConnectionRequestDelegate* delegate) {
+    PendingConnectionRequestDelegate* delegate,
+    scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) {
   return base::WrapUnique(new PendingBleInitiatorConnectionRequest(
-      std::move(client_connection_parameters), connection_priority, delegate));
+      std::move(client_connection_parameters), connection_priority, delegate,
+      bluetooth_adapter));
 }
 
 PendingBleInitiatorConnectionRequest::PendingBleInitiatorConnectionRequest(
     std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
     ConnectionPriority connection_priority,
-    PendingConnectionRequestDelegate* delegate)
-    : PendingConnectionRequestBase<BleInitiatorFailureType>(
+    PendingConnectionRequestDelegate* delegate,
+    scoped_refptr<device::BluetoothAdapter> bluetooth_adapter)
+    : PendingBleConnectionRequestBase<BleInitiatorFailureType>(
           std::move(client_connection_parameters),
           connection_priority,
           kBleInitiatorReadableRequestTypeForLogging,
-          delegate) {}
+          delegate,
+          std::move(bluetooth_adapter)) {}
 
 PendingBleInitiatorConnectionRequest::~PendingBleInitiatorConnectionRequest() =
     default;
diff --git a/chromeos/services/secure_channel/pending_ble_initiator_connection_request.h b/chromeos/services/secure_channel/pending_ble_initiator_connection_request.h
index b2a2a0a0..05c7722 100644
--- a/chromeos/services/secure_channel/pending_ble_initiator_connection_request.h
+++ b/chromeos/services/secure_channel/pending_ble_initiator_connection_request.h
@@ -5,10 +5,12 @@
 #ifndef CHROMEOS_SERVICES_SECURE_CHANNEL_PENDING_BLE_INITIATOR_CONNECTION_REQUEST_H_
 #define CHROMEOS_SERVICES_SECURE_CHANNEL_PENDING_BLE_INITIATOR_CONNECTION_REQUEST_H_
 
+#include <memory>
+
 #include "base/macros.h"
 #include "chromeos/services/secure_channel/ble_initiator_failure_type.h"
 #include "chromeos/services/secure_channel/client_connection_parameters.h"
-#include "chromeos/services/secure_channel/pending_connection_request_base.h"
+#include "chromeos/services/secure_channel/pending_ble_connection_request_base.h"
 #include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
 
 namespace chromeos {
@@ -17,7 +19,7 @@
 
 // ConnectionRequest corresponding to BLE connections in the initiator role.
 class PendingBleInitiatorConnectionRequest
-    : public PendingConnectionRequestBase<BleInitiatorFailureType> {
+    : public PendingBleConnectionRequestBase<BleInitiatorFailureType> {
  public:
   class Factory {
    public:
@@ -28,7 +30,8 @@
     BuildInstance(std::unique_ptr<ClientConnectionParameters>
                       client_connection_parameters,
                   ConnectionPriority connection_priority,
-                  PendingConnectionRequestDelegate* delegate);
+                  PendingConnectionRequestDelegate* delegate,
+                  scoped_refptr<device::BluetoothAdapter> bluetooth_adapter);
 
    private:
     static Factory* test_factory_;
@@ -43,7 +46,8 @@
   PendingBleInitiatorConnectionRequest(
       std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
       ConnectionPriority connection_priority,
-      PendingConnectionRequestDelegate* delegate);
+      PendingConnectionRequestDelegate* delegate,
+      scoped_refptr<device::BluetoothAdapter> bluetooth_adapter);
 
   // PendingConnectionRequest<BleInitiatorFailureType>:
   void HandleConnectionFailure(BleInitiatorFailureType failure_detail) override;
diff --git a/chromeos/services/secure_channel/pending_ble_initiator_connection_request_unittest.cc b/chromeos/services/secure_channel/pending_ble_initiator_connection_request_unittest.cc
index 34285a2..d73f9c8 100644
--- a/chromeos/services/secure_channel/pending_ble_initiator_connection_request_unittest.cc
+++ b/chromeos/services/secure_channel/pending_ble_initiator_connection_request_unittest.cc
@@ -5,6 +5,7 @@
 #include "chromeos/services/secure_channel/pending_ble_initiator_connection_request.h"
 
 #include <memory>
+#include <utility>
 
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
@@ -12,6 +13,7 @@
 #include "chromeos/services/secure_channel/fake_client_connection_parameters.h"
 #include "chromeos/services/secure_channel/fake_pending_connection_request_delegate.h"
 #include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace chromeos {
@@ -36,12 +38,14 @@
         std::make_unique<FakeClientConnectionParameters>(kTestFeature);
     fake_client_connection_parameters_ =
         fake_client_connection_parameters.get();
+    mock_adapter_ =
+        base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
 
     pending_ble_initiator_request_ =
         PendingBleInitiatorConnectionRequest::Factory::Get()->BuildInstance(
             std::move(fake_client_connection_parameters),
             ConnectionPriority::kLow,
-            fake_pending_connection_request_delegate_.get());
+            fake_pending_connection_request_delegate_.get(), mock_adapter_);
   }
 
   const base::Optional<
@@ -67,6 +71,7 @@
   std::unique_ptr<FakePendingConnectionRequestDelegate>
       fake_pending_connection_request_delegate_;
   FakeClientConnectionParameters* fake_client_connection_parameters_;
+  scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> mock_adapter_;
 
   std::unique_ptr<PendingConnectionRequest<BleInitiatorFailureType>>
       pending_ble_initiator_request_;
diff --git a/chromeos/services/secure_channel/pending_ble_listener_connection_request.cc b/chromeos/services/secure_channel/pending_ble_listener_connection_request.cc
index 4de6e57..b3b4a3f 100644
--- a/chromeos/services/secure_channel/pending_ble_listener_connection_request.cc
+++ b/chromeos/services/secure_channel/pending_ble_listener_connection_request.cc
@@ -4,6 +4,8 @@
 
 #include "chromeos/services/secure_channel/pending_ble_listener_connection_request.h"
 
+#include <utility>
+
 #include "base/memory/ptr_util.h"
 #include "base/no_destructor.h"
 #include "chromeos/components/proximity_auth/logging/logging.h"
@@ -43,20 +45,24 @@
 PendingBleListenerConnectionRequest::Factory::BuildInstance(
     std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
     ConnectionPriority connection_priority,
-    PendingConnectionRequestDelegate* delegate) {
+    PendingConnectionRequestDelegate* delegate,
+    scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) {
   return base::WrapUnique(new PendingBleListenerConnectionRequest(
-      std::move(client_connection_parameters), connection_priority, delegate));
+      std::move(client_connection_parameters), connection_priority, delegate,
+      bluetooth_adapter));
 }
 
 PendingBleListenerConnectionRequest::PendingBleListenerConnectionRequest(
     std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
     ConnectionPriority connection_priority,
-    PendingConnectionRequestDelegate* delegate)
-    : PendingConnectionRequestBase<BleListenerFailureType>(
+    PendingConnectionRequestDelegate* delegate,
+    scoped_refptr<device::BluetoothAdapter> bluetooth_adapter)
+    : PendingBleConnectionRequestBase<BleListenerFailureType>(
           std::move(client_connection_parameters),
           connection_priority,
           kBleListenerReadableRequestTypeForLogging,
-          delegate) {}
+          delegate,
+          std::move(bluetooth_adapter)) {}
 
 PendingBleListenerConnectionRequest::~PendingBleListenerConnectionRequest() =
     default;
diff --git a/chromeos/services/secure_channel/pending_ble_listener_connection_request.h b/chromeos/services/secure_channel/pending_ble_listener_connection_request.h
index 7d6edea..e6fb7685 100644
--- a/chromeos/services/secure_channel/pending_ble_listener_connection_request.h
+++ b/chromeos/services/secure_channel/pending_ble_listener_connection_request.h
@@ -5,10 +5,12 @@
 #ifndef CHROMEOS_SERVICES_SECURE_CHANNEL_PENDING_BLE_LISTENER_CONNECTION_REQUEST_H_
 #define CHROMEOS_SERVICES_SECURE_CHANNEL_PENDING_BLE_LISTENER_CONNECTION_REQUEST_H_
 
+#include <memory>
+
 #include "base/macros.h"
 #include "chromeos/services/secure_channel/ble_listener_failure_type.h"
 #include "chromeos/services/secure_channel/client_connection_parameters.h"
-#include "chromeos/services/secure_channel/pending_connection_request_base.h"
+#include "chromeos/services/secure_channel/pending_ble_connection_request_base.h"
 #include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
 
 namespace chromeos {
@@ -17,7 +19,7 @@
 
 // ConnectionRequest corresponding to BLE connections in the listener role.
 class PendingBleListenerConnectionRequest
-    : public PendingConnectionRequestBase<BleListenerFailureType> {
+    : public PendingBleConnectionRequestBase<BleListenerFailureType> {
  public:
   class Factory {
    public:
@@ -28,7 +30,8 @@
     BuildInstance(std::unique_ptr<ClientConnectionParameters>
                       client_connection_parameters,
                   ConnectionPriority connection_priority,
-                  PendingConnectionRequestDelegate* delegate);
+                  PendingConnectionRequestDelegate* delegate,
+                  scoped_refptr<device::BluetoothAdapter> bluetooth_adapter);
 
    private:
     static Factory* test_factory_;
@@ -40,7 +43,8 @@
   PendingBleListenerConnectionRequest(
       std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
       ConnectionPriority connection_priority,
-      PendingConnectionRequestDelegate* delegate);
+      PendingConnectionRequestDelegate* delegate,
+      scoped_refptr<device::BluetoothAdapter> bluetooth_adapter);
 
   // PendingConnectionRequest<BleListenerFailureType>:
   void HandleConnectionFailure(BleListenerFailureType failure_detail) override;
diff --git a/chromeos/services/secure_channel/pending_ble_listener_connection_request_unittest.cc b/chromeos/services/secure_channel/pending_ble_listener_connection_request_unittest.cc
index 12872f0..879fcf7 100644
--- a/chromeos/services/secure_channel/pending_ble_listener_connection_request_unittest.cc
+++ b/chromeos/services/secure_channel/pending_ble_listener_connection_request_unittest.cc
@@ -5,6 +5,7 @@
 #include "chromeos/services/secure_channel/pending_ble_listener_connection_request.h"
 
 #include <memory>
+#include <utility>
 
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
@@ -12,6 +13,7 @@
 #include "chromeos/services/secure_channel/fake_client_connection_parameters.h"
 #include "chromeos/services/secure_channel/fake_pending_connection_request_delegate.h"
 #include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace chromeos {
@@ -36,12 +38,14 @@
         std::make_unique<FakeClientConnectionParameters>(kTestFeature);
     fake_client_connection_parameters_ =
         fake_client_connection_parameters.get();
+    mock_adapter_ =
+        base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
 
     pending_ble_listener_request_ =
         PendingBleListenerConnectionRequest::Factory::Get()->BuildInstance(
             std::move(fake_client_connection_parameters),
             ConnectionPriority::kLow,
-            fake_pending_connection_request_delegate_.get());
+            fake_pending_connection_request_delegate_.get(), mock_adapter_);
   }
 
   const base::Optional<
@@ -67,6 +71,7 @@
   std::unique_ptr<FakePendingConnectionRequestDelegate>
       fake_pending_connection_request_delegate_;
   FakeClientConnectionParameters* fake_client_connection_parameters_;
+  scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> mock_adapter_;
 
   std::unique_ptr<PendingConnectionRequest<BleListenerFailureType>>
       pending_ble_listener_request_;
diff --git a/chromeos/services/secure_channel/pending_connection_manager_impl.cc b/chromeos/services/secure_channel/pending_connection_manager_impl.cc
index e9b5517..f372325 100644
--- a/chromeos/services/secure_channel/pending_connection_manager_impl.cc
+++ b/chromeos/services/secure_channel/pending_connection_manager_impl.cc
@@ -43,16 +43,19 @@
 std::unique_ptr<PendingConnectionManager>
 PendingConnectionManagerImpl::Factory::BuildInstance(
     Delegate* delegate,
-    BleConnectionManager* ble_connection_manager) {
-  return base::WrapUnique(
-      new PendingConnectionManagerImpl(delegate, ble_connection_manager));
+    BleConnectionManager* ble_connection_manager,
+    scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) {
+  return base::WrapUnique(new PendingConnectionManagerImpl(
+      delegate, ble_connection_manager, bluetooth_adapter));
 }
 
 PendingConnectionManagerImpl::PendingConnectionManagerImpl(
     Delegate* delegate,
-    BleConnectionManager* ble_connection_manager)
+    BleConnectionManager* ble_connection_manager,
+    scoped_refptr<device::BluetoothAdapter> bluetooth_adapter)
     : PendingConnectionManager(delegate),
-      ble_connection_manager_(ble_connection_manager) {}
+      ble_connection_manager_(ble_connection_manager),
+      bluetooth_adapter_(bluetooth_adapter) {}
 
 PendingConnectionManagerImpl::~PendingConnectionManagerImpl() = default;
 
@@ -174,7 +177,7 @@
   bool success = connection_attempt->AddPendingConnectionRequest(
       PendingBleInitiatorConnectionRequest::Factory::Get()->BuildInstance(
           std::move(client_connection_parameters), connection_priority,
-          connection_attempt.get() /* delegate */));
+          connection_attempt.get() /* delegate */, bluetooth_adapter_));
 
   if (!success) {
     PA_LOG(ERROR) << "PendingConnectionManagerImpl::"
@@ -207,7 +210,7 @@
   bool success = connection_attempt->AddPendingConnectionRequest(
       PendingBleListenerConnectionRequest::Factory::Get()->BuildInstance(
           std::move(client_connection_parameters), connection_priority,
-          connection_attempt.get() /* delegate */));
+          connection_attempt.get() /* delegate */, bluetooth_adapter_));
 
   if (!success) {
     PA_LOG(ERROR) << "PendingConnectionManagerImpl::"
diff --git a/chromeos/services/secure_channel/pending_connection_manager_impl.h b/chromeos/services/secure_channel/pending_connection_manager_impl.h
index 8bc0984d..fb1d82d 100644
--- a/chromeos/services/secure_channel/pending_connection_manager_impl.h
+++ b/chromeos/services/secure_channel/pending_connection_manager_impl.h
@@ -21,6 +21,7 @@
 #include "chromeos/services/secure_channel/device_id_pair.h"
 #include "chromeos/services/secure_channel/pending_connection_manager.h"
 #include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
+#include "device/bluetooth/bluetooth_adapter.h"
 
 namespace chromeos {
 
@@ -46,7 +47,8 @@
     virtual ~Factory();
     virtual std::unique_ptr<PendingConnectionManager> BuildInstance(
         Delegate* delegate,
-        BleConnectionManager* ble_connection_manager);
+        BleConnectionManager* ble_connection_manager,
+        scoped_refptr<device::BluetoothAdapter> bluetooth_adapter);
 
    private:
     static Factory* test_factory_;
@@ -55,8 +57,10 @@
   ~PendingConnectionManagerImpl() override;
 
  private:
-  PendingConnectionManagerImpl(Delegate* delegate,
-                               BleConnectionManager* ble_connection_manager);
+  PendingConnectionManagerImpl(
+      Delegate* delegate,
+      BleConnectionManager* ble_connection_manager,
+      scoped_refptr<device::BluetoothAdapter> bluetooth_adapter);
 
   // PendingConnectionManager:
   void HandleConnectionRequest(
@@ -95,6 +99,7 @@
       details_to_attempt_details_map_;
 
   BleConnectionManager* ble_connection_manager_;
+  scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_;
 
   DISALLOW_COPY_AND_ASSIGN(PendingConnectionManagerImpl);
 };
diff --git a/chromeos/services/secure_channel/pending_connection_manager_impl_unittest.cc b/chromeos/services/secure_channel/pending_connection_manager_impl_unittest.cc
index 9224a48..a3da3fa 100644
--- a/chromeos/services/secure_channel/pending_connection_manager_impl_unittest.cc
+++ b/chromeos/services/secure_channel/pending_connection_manager_impl_unittest.cc
@@ -22,6 +22,7 @@
 #include "chromeos/services/secure_channel/fake_pending_connection_request.h"
 #include "chromeos/services/secure_channel/pending_ble_initiator_connection_request.h"
 #include "chromeos/services/secure_channel/pending_ble_listener_connection_request.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace chromeos {
@@ -211,7 +212,8 @@
   BuildInstance(
       std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
       ConnectionPriority connection_priority,
-      PendingConnectionRequestDelegate* delegate) override {
+      PendingConnectionRequestDelegate* delegate,
+      scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) override {
     EXPECT_EQ(expected_client_connection_parameters_,
               client_connection_parameters.get());
     EXPECT_EQ(*expected_connection_priority_, connection_priority);
@@ -257,7 +259,8 @@
   BuildInstance(
       std::unique_ptr<ClientConnectionParameters> client_connection_parameters,
       ConnectionPriority connection_priority,
-      PendingConnectionRequestDelegate* delegate) override {
+      PendingConnectionRequestDelegate* delegate,
+      scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) override {
     EXPECT_EQ(expected_client_connection_parameters_,
               client_connection_parameters.get());
     EXPECT_EQ(*expected_connection_priority_, connection_priority);
@@ -337,8 +340,12 @@
     PendingBleListenerConnectionRequest::Factory::SetFactoryForTesting(
         fake_pending_ble_listener_connection_request_factory_.get());
 
+    mock_adapter_ =
+        base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
+
     manager_ = PendingConnectionManagerImpl::Factory::Get()->BuildInstance(
-        fake_delegate_.get(), fake_ble_connection_manager_.get());
+        fake_delegate_.get(), fake_ble_connection_manager_.get(),
+        mock_adapter_);
   }
 
   void TearDown() override {
@@ -592,6 +599,7 @@
       fake_pending_ble_initiator_connection_request_factory_;
   std::unique_ptr<FakePendingBleListenerConnectionRequestFactory>
       fake_pending_ble_listener_connection_request_factory_;
+  scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> mock_adapter_;
 
   std::unique_ptr<PendingConnectionManager> manager_;
 
diff --git a/chromeos/services/secure_channel/public/mojom/secure_channel.mojom b/chromeos/services/secure_channel/public/mojom/secure_channel.mojom
index 9426169..f58a8c4 100644
--- a/chromeos/services/secure_channel/public/mojom/secure_channel.mojom
+++ b/chromeos/services/secure_channel/public/mojom/secure_channel.mojom
@@ -34,7 +34,13 @@
   REMOTE_DEVICE_INVALID_PSK,
 
   // Timeouts occurred trying to contact the remote device.
-  TIMEOUT_FINDING_DEVICE
+  TIMEOUT_FINDING_DEVICE,
+
+  // The local Bluetooth adapter is disabled (turned off).
+  ADAPTER_DISABLED,
+
+  // The local Bluetooth adapter is not present.
+  ADAPTER_NOT_PRESENT
 };
 
 enum ConnectionCreationDetail {
diff --git a/chromeos/services/secure_channel/secure_channel_impl.cc b/chromeos/services/secure_channel/secure_channel_impl.cc
index 1b103e50..0f973f20 100644
--- a/chromeos/services/secure_channel/secure_channel_impl.cc
+++ b/chromeos/services/secure_channel/secure_channel_impl.cc
@@ -71,7 +71,8 @@
 
 SecureChannelImpl::SecureChannelImpl(
     scoped_refptr<device::BluetoothAdapter> bluetooth_adapter)
-    : timer_factory_(TimerFactoryImpl::Factory::Get()->BuildInstance()),
+    : bluetooth_adapter_(std::move(bluetooth_adapter)),
+      timer_factory_(TimerFactoryImpl::Factory::Get()->BuildInstance()),
       remote_device_cache_(
           cryptauth::RemoteDeviceCache::Factory::Get()->BuildInstance()),
       ble_service_data_helper_(
@@ -79,13 +80,14 @@
               remote_device_cache_.get())),
       ble_connection_manager_(
           BleConnectionManagerImpl::Factory::Get()->BuildInstance(
-              bluetooth_adapter,
+              bluetooth_adapter_,
               ble_service_data_helper_.get(),
               timer_factory_.get())),
       pending_connection_manager_(
           PendingConnectionManagerImpl::Factory::Get()->BuildInstance(
               this /* delegate */,
-              ble_connection_manager_.get())),
+              ble_connection_manager_.get(),
+              bluetooth_adapter_)),
       active_connection_manager_(
           ActiveConnectionManagerImpl::Factory::Get()->BuildInstance(
               this /* delegate */)) {}
@@ -207,6 +209,17 @@
     return;
   }
 
+  // Check 4: Medium-specific verification.
+  switch (connection_medium) {
+    case ConnectionMedium::kBluetoothLowEnergy:
+      // Is the local Bluetooth adapter disabled or not present? If either,
+      // notify client and return early.
+      if (CheckIfBluetoothAdapterDisabledOrNotPresent(
+              api_fn_name, client_connection_parameters.get()))
+        return;
+      break;
+  }
+
   // At this point, the request has been deemed valid.
   ConnectionAttemptDetails connection_attempt_details(
       device_to_connect.GetDeviceId(), local_device.GetDeviceId(),
@@ -320,6 +333,26 @@
   return true;
 }
 
+bool SecureChannelImpl::CheckIfBluetoothAdapterDisabledOrNotPresent(
+    ApiFunctionName api_fn_name,
+    ClientConnectionParameters* client_connection_parameters) {
+  if (!bluetooth_adapter_->IsPresent()) {
+    RejectRequestForReason(
+        api_fn_name, mojom::ConnectionAttemptFailureReason::ADAPTER_NOT_PRESENT,
+        client_connection_parameters);
+    return true;
+  }
+
+  if (!bluetooth_adapter_->IsPowered()) {
+    RejectRequestForReason(
+        api_fn_name, mojom::ConnectionAttemptFailureReason::ADAPTER_DISABLED,
+        client_connection_parameters);
+    return true;
+  }
+
+  return false;
+}
+
 base::Optional<SecureChannelImpl::InvalidRemoteDeviceReason>
 SecureChannelImpl::AddDeviceToCacheIfPossible(
     ApiFunctionName api_fn_name,
diff --git a/chromeos/services/secure_channel/secure_channel_impl.h b/chromeos/services/secure_channel/secure_channel_impl.h
index 6bd6447..055322b 100644
--- a/chromeos/services/secure_channel/secure_channel_impl.h
+++ b/chromeos/services/secure_channel/secure_channel_impl.h
@@ -56,7 +56,8 @@
   ~SecureChannelImpl() override;
 
  private:
-  SecureChannelImpl(scoped_refptr<device::BluetoothAdapter> bluetooth_adapter);
+  explicit SecureChannelImpl(
+      scoped_refptr<device::BluetoothAdapter> bluetooth_adapter);
 
   enum class InvalidRemoteDeviceReason { kInvalidPublicKey, kInvalidPsk };
 
@@ -136,6 +137,12 @@
       ClientConnectionParameters* client_connection_parameters,
       bool is_local_device);
 
+  // Checks if |bluetooth_adapter_| is disabled or not present and rejects the
+  // connection request if so. Returns whether the request was rejected.
+  bool CheckIfBluetoothAdapterDisabledOrNotPresent(
+      ApiFunctionName api_fn_name,
+      ClientConnectionParameters* client_connection_parameters);
+
   // Validates |device| and adds it to the |remote_device_cache_| if it is
   // valid. If it is not valid, the reason is provided as a return type, and the
   // device is not added to the cache.
@@ -143,6 +150,7 @@
       ApiFunctionName api_fn_name,
       const cryptauth::RemoteDevice& device);
 
+  scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_;
   std::unique_ptr<TimerFactory> timer_factory_;
   std::unique_ptr<cryptauth::RemoteDeviceCache> remote_device_cache_;
   std::unique_ptr<BleServiceDataHelper> ble_service_data_helper_;
diff --git a/chromeos/services/secure_channel/secure_channel_service_unittest.cc b/chromeos/services/secure_channel/secure_channel_service_unittest.cc
index d528d23..e39e52b 100644
--- a/chromeos/services/secure_channel/secure_channel_service_unittest.cc
+++ b/chromeos/services/secure_channel/secure_channel_service_unittest.cc
@@ -178,7 +178,8 @@
   // PendingConnectionManagerImpl::Factory:
   std::unique_ptr<PendingConnectionManager> BuildInstance(
       PendingConnectionManager::Delegate* delegate,
-      BleConnectionManager* ble_connection_manager) override {
+      BleConnectionManager* ble_connection_manager,
+      scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) override {
     EXPECT_FALSE(instance_);
     EXPECT_EQ(fake_ble_connection_manager_factory_->instance(),
               ble_connection_manager);
@@ -321,6 +322,14 @@
   void SetUp() override {
     mock_adapter_ =
         base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
+    is_adapter_powered_ = true;
+    is_adapter_present_ = true;
+    ON_CALL(*mock_adapter_, IsPresent())
+        .WillByDefault(
+            Invoke(this, &SecureChannelServiceTest::is_adapter_present));
+    ON_CALL(*mock_adapter_, IsPowered())
+        .WillByDefault(
+            Invoke(this, &SecureChannelServiceTest::is_adapter_powered));
     device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);
 
     test_task_runner_ = base::MakeRefCounted<base::TestSimpleTaskRunner>();
@@ -584,6 +593,12 @@
 
   const cryptauth::RemoteDeviceList& test_devices() { return test_devices_; }
 
+  bool is_adapter_present() { return is_adapter_present_; }
+  void set_is_adapter_present(bool present) { is_adapter_present_ = present; }
+
+  bool is_adapter_powered() { return is_adapter_powered_; }
+  void set_is_adapter_powered(bool powered) { is_adapter_powered_ = powered; }
+
  private:
   void AttemptConnectionAndVerifyPendingConnection(
       const cryptauth::RemoteDevice& device_to_connect,
@@ -853,6 +868,9 @@
   std::unique_ptr<service_manager::TestConnectorFactory> connector_factory_;
   std::unique_ptr<service_manager::Connector> connector_;
 
+  bool is_adapter_powered_;
+  bool is_adapter_present_;
+
   mojom::SecureChannelPtr secure_channel_ptr_;
 
   DISALLOW_COPY_AND_ASSIGN(SecureChannelServiceTest);
@@ -948,6 +966,48 @@
       mojom::ConnectionAttemptFailureReason::LOCAL_DEVICE_INVALID_PSK);
 }
 
+TEST_F(SecureChannelServiceTest,
+       ListenForConnection_BluetoothAdapterNotPresent) {
+  FinishInitialization();
+
+  set_is_adapter_present(false);
+
+  CallListenForConnectionFromDeviceAndVerifyRejection(
+      test_devices()[0], test_devices()[1], "feature", ConnectionPriority::kLow,
+      mojom::ConnectionAttemptFailureReason::ADAPTER_NOT_PRESENT);
+}
+
+TEST_F(SecureChannelServiceTest,
+       InitiateConnection_BluetoothAdapterNotPresent) {
+  FinishInitialization();
+
+  set_is_adapter_present(false);
+
+  CallInitiateConnectionToDeviceAndVerifyRejection(
+      test_devices()[0], test_devices()[1], "feature", ConnectionPriority::kLow,
+      mojom::ConnectionAttemptFailureReason::ADAPTER_NOT_PRESENT);
+}
+
+TEST_F(SecureChannelServiceTest, ListenForConnection_BluetoothAdapterDisabled) {
+  FinishInitialization();
+
+  set_is_adapter_powered(false);
+
+  CallListenForConnectionFromDeviceAndVerifyRejection(
+      test_devices()[0], test_devices()[1], "feature", ConnectionPriority::kLow,
+      mojom::ConnectionAttemptFailureReason::ADAPTER_DISABLED);
+}
+
+TEST_F(SecureChannelServiceTest, InitiateConnection_BluetoothAdapterDisabled) {
+  FinishInitialization();
+
+  set_is_adapter_powered(false);
+
+  CallInitiateConnectionToDeviceAndVerifyRejection(
+      test_devices()[0], test_devices()[1], "feature", ConnectionPriority::kLow,
+      mojom::ConnectionAttemptFailureReason::ADAPTER_DISABLED);
+}
+
 TEST_F(SecureChannelServiceTest, CallsQueuedBeforeInitializationComplete) {
   CallInitiateConnectionToDeviceAndVerifyInitializationNotComplete(
       test_devices()[4], test_devices()[5], "feature",
diff --git a/components/arc/BUILD.gn b/components/arc/BUILD.gn
index c2ccf59..e8efb43 100644
--- a/components/arc/BUILD.gn
+++ b/components/arc/BUILD.gn
@@ -39,7 +39,6 @@
     "intent_helper/open_url_delegate.h",
     "lock_screen/arc_lock_screen_bridge.cc",
     "lock_screen/arc_lock_screen_bridge.h",
-    "metrics/arc_metrics_constants.h",
     "metrics/arc_metrics_service.cc",
     "metrics/arc_metrics_service.h",
     "midis/arc_midis_bridge.cc",
@@ -71,6 +70,7 @@
 
   public_deps = [
     ":arc_base",
+    ":arc_metrics_constants",
     ":prefs",
   ]
 
@@ -273,6 +273,12 @@
   ]
 }
 
+source_set("arc_metrics_constants") {
+  sources = [
+    "metrics/arc_metrics_constants.h",
+  ]
+}
+
 source_set("unit_tests") {
   testonly = true
   sources = [
diff --git a/components/content_settings/core/browser/content_settings_registry.cc b/components/content_settings/core/browser/content_settings_registry.cc
index abd4458..1cc5496 100644
--- a/components/content_settings/core/browser/content_settings_registry.cc
+++ b/components/content_settings/core/browser/content_settings_registry.cc
@@ -21,8 +21,8 @@
 
 namespace {
 
-base::LazyInstance<ContentSettingsRegistry>::DestructorAtExit g_instance =
-    LAZY_INSTANCE_INITIALIZER;
+base::LazyInstance<ContentSettingsRegistry>::DestructorAtExit
+    g_content_settings_registry_instance = LAZY_INSTANCE_INITIALIZER;
 
 // TODO(raymes): These overloaded functions make the registration code clearer.
 // When initializer lists are available they won't be needed. The initializer
@@ -73,7 +73,7 @@
 
 // static
 ContentSettingsRegistry* ContentSettingsRegistry::GetInstance() {
-  return g_instance.Pointer();
+  return g_content_settings_registry_instance.Pointer();
 }
 
 ContentSettingsRegistry::ContentSettingsRegistry()
diff --git a/components/content_settings/core/browser/website_settings_registry.cc b/components/content_settings/core/browser/website_settings_registry.cc
index 8eee62f..8d22b75 100644
--- a/components/content_settings/core/browser/website_settings_registry.cc
+++ b/components/content_settings/core/browser/website_settings_registry.cc
@@ -15,7 +15,7 @@
 namespace {
 
 base::LazyInstance<content_settings::WebsiteSettingsRegistry>::DestructorAtExit
-    g_instance = LAZY_INSTANCE_INITIALIZER;
+    g_website_settings_registry_instance = LAZY_INSTANCE_INITIALIZER;
 
 }  // namespace
 
@@ -23,7 +23,7 @@
 
 // static
 WebsiteSettingsRegistry* WebsiteSettingsRegistry::GetInstance() {
-  return g_instance.Pointer();
+  return g_website_settings_registry_instance.Pointer();
 }
 
 WebsiteSettingsRegistry::WebsiteSettingsRegistry() {
diff --git a/components/drive/chromeos/file_system.cc b/components/drive/chromeos/file_system.cc
index 5fc38d20..e35a472 100644
--- a/components/drive/chromeos/file_system.cc
+++ b/components/drive/chromeos/file_system.cc
@@ -935,13 +935,11 @@
     for (const auto& change : entry.second.list()) {
       DCHECK(!change.team_drive_id().empty());
       if (change.IsDelete()) {
-        const auto it =
-            team_drive_change_list_loaders_.find(change.team_drive_id());
-        DCHECK(it != team_drive_change_list_loaders_.end());
-        team_drive_change_list_loaders_.erase(it);
-        // If we were tracking the update status we can remove that as well.
-        last_update_metadata_.erase(change.team_drive_id());
-        removed_team_drives.insert(change.team_drive_id());
+        if (team_drive_change_list_loaders_.erase(change.team_drive_id()) > 0) {
+          // If we were tracking the update status we can remove that as well.
+          last_update_metadata_.erase(change.team_drive_id());
+          removed_team_drives.insert(change.team_drive_id());
+        }
       } else if (change.IsAddOrUpdate()) {
         // If this is an update (e.g. a renamed team drive), then just erase the
         // existing entry so we can re-add it with the new path.
diff --git a/components/drive/file_system_unittest.cc b/components/drive/file_system_unittest.cc
index 1740cbf..6560b57 100644
--- a/components/drive/file_system_unittest.cc
+++ b/components/drive/file_system_unittest.cc
@@ -1500,4 +1500,34 @@
   EXPECT_LE(now, team_drive_metadata["td_id_2_2"].last_update_check_time);
 }
 
+TEST_F(FileSystemTest, RemoveNonExistingTeamDrive) {
+  ASSERT_NO_FATAL_FAILURE(SetUpTestFileSystem(USE_SERVER_TIMESTAMP));
+  ASSERT_TRUE(SetupTeamDrives());
+
+  // The first load will trigger the loading of team drives.
+  ReadDirectorySync(base::FilePath::FromUTF8Unsafe("."));
+
+  // Create a file change with a delete team drive, ensure file_system_ does not
+  // crash.
+  const base::FilePath path =
+      util::GetDriveTeamDrivesRootPath().Append("team_drive_2");
+  std::unique_ptr<ResourceEntry> entry = GetResourceEntrySync(path);
+  ASSERT_TRUE(entry);
+
+  drive::FileChange change;
+  change.Update(path, *entry, FileChange::CHANGE_TYPE_DELETE);
+
+  // First time should be removed.
+  file_system_->OnTeamDrivesChanged(change);
+  std::set<std::string> expected_changes = {"td_id_2"};
+  EXPECT_EQ(expected_changes,
+            mock_directory_observer_->removed_team_drive_ids());
+
+  // Second time should be no changes, and no crash.
+  file_system_->OnTeamDrivesChanged(change);
+  expected_changes = {};
+  EXPECT_EQ(expected_changes,
+            mock_directory_observer_->removed_team_drive_ids());
+}
+
 }   // namespace drive
diff --git a/components/image_fetcher/OWNERS b/components/image_fetcher/OWNERS
index 88fe2d2..b8b98892 100644
--- a/components/image_fetcher/OWNERS
+++ b/components/image_fetcher/OWNERS
@@ -1,3 +1,4 @@
+fgorski@chromium.org
 markusheintz@chromium.org
 mathp@chromium.org
 treib@chromium.org
diff --git a/components/offline_pages/core/client_policy_controller.cc b/components/offline_pages/core/client_policy_controller.cc
index 03eb030..9e3ea54 100644
--- a/components/offline_pages/core/client_policy_controller.cc
+++ b/components/offline_pages/core/client_policy_controller.cc
@@ -27,7 +27,7 @@
                                      kUnlimitedPages, kUnlimitedPages)
           .SetExpirePeriod(base::TimeDelta::FromDays(30))
           .SetIsSupportedByRecentTabs(true)
-          .SetIsOnlyShownInOriginalTab(true)
+          .SetIsRestrictedToTabFromClientId(true)
           .Build()));
   policies_.insert(std::make_pair(
       kAsyncNamespace,
@@ -89,7 +89,7 @@
                                                     kUnlimitedPages, 1)
                          .SetIsRemovedOnCacheReset(true)
                          .SetExpirePeriod(base::TimeDelta::FromHours(1))
-                         .SetIsOnlyShownInOriginalTab(true)
+                         .SetIsRestrictedToTabFromClientId(true)
                          .Build()));
 
   // Fallback policy.
@@ -204,23 +204,25 @@
   return *recent_tab_namespace_cache_;
 }
 
-bool ClientPolicyController::IsRestrictedToOriginalTab(
+bool ClientPolicyController::IsRestrictedToTabFromClientId(
     const std::string& name_space) const {
-  return GetPolicy(name_space).feature_policy.only_shown_in_original_tab;
+  return GetPolicy(name_space)
+      .feature_policy.is_restricted_to_tab_from_client_id;
 }
 
 const std::vector<std::string>&
-ClientPolicyController::GetNamespacesRestrictedToOriginalTab() const {
-  if (show_in_original_tab_cache_)
-    return *show_in_original_tab_cache_;
+ClientPolicyController::GetNamespacesRestrictedToTabFromClientId() const {
+  if (restricted_to_tab_from_client_id_cache_)
+    return *restricted_to_tab_from_client_id_cache_;
 
-  show_in_original_tab_cache_ = std::make_unique<std::vector<std::string>>();
+  restricted_to_tab_from_client_id_cache_ =
+      std::make_unique<std::vector<std::string>>();
   for (const auto& policy_item : policies_) {
-    if (policy_item.second.feature_policy.only_shown_in_original_tab)
-      show_in_original_tab_cache_->emplace_back(policy_item.first);
+    if (policy_item.second.feature_policy.is_restricted_to_tab_from_client_id)
+      restricted_to_tab_from_client_id_cache_->emplace_back(policy_item.first);
   }
 
-  return *show_in_original_tab_cache_;
+  return *restricted_to_tab_from_client_id_cache_;
 }
 
 bool ClientPolicyController::IsDisabledWhenPrefetchDisabled(
diff --git a/components/offline_pages/core/client_policy_controller.h b/components/offline_pages/core/client_policy_controller.h
index 9084d9b..faef82cd 100644
--- a/components/offline_pages/core/client_policy_controller.h
+++ b/components/offline_pages/core/client_policy_controller.h
@@ -57,10 +57,13 @@
   const std::vector<std::string>& GetNamespacesShownAsRecentlyVisitedSite()
       const;
 
-  // Returns whether pages for |name_space| should never be shown outside the
-  // tab they were generated in.
-  bool IsRestrictedToOriginalTab(const std::string& name_space) const;
-  const std::vector<std::string>& GetNamespacesRestrictedToOriginalTab() const;
+  // Returns whether pages for |name_space| should only be opened in a
+  // specifically assigned tab.
+  // Note: For this restriction to work offline pages saved to this namespace
+  // must have the respective tab id set to their ClientId::id field.
+  bool IsRestrictedToTabFromClientId(const std::string& name_space) const;
+  const std::vector<std::string>& GetNamespacesRestrictedToTabFromClientId()
+      const;
 
   bool IsDisabledWhenPrefetchDisabled(const std::string& name_space) const;
   const std::vector<std::string>& GetNamespacesDisabledWhenPrefetchDisabled()
@@ -89,7 +92,8 @@
   mutable std::unique_ptr<std::vector<std::string>>
       user_requested_download_namespace_cache_;
   mutable std::unique_ptr<std::vector<std::string>> recent_tab_namespace_cache_;
-  mutable std::unique_ptr<std::vector<std::string>> show_in_original_tab_cache_;
+  mutable std::unique_ptr<std::vector<std::string>>
+      restricted_to_tab_from_client_id_cache_;
   mutable std::unique_ptr<std::vector<std::string>>
       disabled_when_prefetch_disabled_cache_;
 
diff --git a/components/offline_pages/core/client_policy_controller_unittest.cc b/components/offline_pages/core/client_policy_controller_unittest.cc
index a88e0be..ff3e7bf 100644
--- a/components/offline_pages/core/client_policy_controller_unittest.cc
+++ b/components/offline_pages/core/client_policy_controller_unittest.cc
@@ -35,7 +35,8 @@
   void ExpectUserRequestedDownloadSupport(std::string name_space,
                                           bool expectation);
   void ExpectRecentTab(std::string name_space, bool expectation);
-  void ExpectOnlyOriginalTab(std::string name_space, bool expectation);
+  void ExpectRestrictedToTabFromClientId(std::string name_space,
+                                         bool expectation);
   void ExpectDisabledWhenPrefetchDisabled(std::string name_space,
                                           bool expectation);
 
@@ -106,19 +107,21 @@
          " a recently visited site.";
 }
 
-void ClientPolicyControllerTest::ExpectOnlyOriginalTab(std::string name_space,
-                                                       bool expectation) {
+void ClientPolicyControllerTest::ExpectRestrictedToTabFromClientId(
+    std::string name_space,
+    bool expectation) {
   std::vector<std::string> cache =
-      controller()->GetNamespacesRestrictedToOriginalTab();
+      controller()->GetNamespacesRestrictedToTabFromClientId();
   auto result = std::find(cache.begin(), cache.end(), name_space);
   EXPECT_EQ(expectation, result != cache.end())
       << "Namespace " << name_space
       << " had incorrect restriction when getting namespaces restricted to"
-         " the original tab";
-  EXPECT_EQ(expectation, controller()->IsRestrictedToOriginalTab(name_space))
+         " the tab from the client id field";
+  EXPECT_EQ(expectation,
+            controller()->IsRestrictedToTabFromClientId(name_space))
       << "Namespace " << name_space
       << " had incorrect restriction when directly checking if the namespace"
-         " is restricted to the original tab";
+         " is restricted to the tab from the client id field";
 }
 
 void ClientPolicyControllerTest::ExpectDisabledWhenPrefetchDisabled(
@@ -147,7 +150,7 @@
   ExpectDownloadSupport(kUndefinedNamespace, false);
   ExpectUserRequestedDownloadSupport(kUndefinedNamespace, false);
   ExpectRecentTab(kUndefinedNamespace, false);
-  ExpectOnlyOriginalTab(kUndefinedNamespace, false);
+  ExpectRestrictedToTabFromClientId(kUndefinedNamespace, false);
   ExpectDisabledWhenPrefetchDisabled(kUndefinedNamespace, false);
 }
 
@@ -160,7 +163,7 @@
   ExpectDownloadSupport(kBookmarkNamespace, false);
   ExpectUserRequestedDownloadSupport(kBookmarkNamespace, false);
   ExpectRecentTab(kBookmarkNamespace, false);
-  ExpectOnlyOriginalTab(kBookmarkNamespace, false);
+  ExpectRestrictedToTabFromClientId(kBookmarkNamespace, false);
   ExpectDisabledWhenPrefetchDisabled(kBookmarkNamespace, false);
 }
 
@@ -173,7 +176,7 @@
   ExpectDownloadSupport(kLastNNamespace, false);
   ExpectUserRequestedDownloadSupport(kLastNNamespace, false);
   ExpectRecentTab(kLastNNamespace, true);
-  ExpectOnlyOriginalTab(kLastNNamespace, true);
+  ExpectRestrictedToTabFromClientId(kLastNNamespace, true);
   ExpectDisabledWhenPrefetchDisabled(kLastNNamespace, false);
 }
 
@@ -186,7 +189,7 @@
   ExpectDownloadSupport(kAsyncNamespace, true);
   ExpectUserRequestedDownloadSupport(kAsyncNamespace, true);
   ExpectRecentTab(kAsyncNamespace, false);
-  ExpectOnlyOriginalTab(kAsyncNamespace, false);
+  ExpectRestrictedToTabFromClientId(kAsyncNamespace, false);
   ExpectDisabledWhenPrefetchDisabled(kAsyncNamespace, false);
 }
 
@@ -199,7 +202,7 @@
   ExpectDownloadSupport(kCCTNamespace, false);
   ExpectUserRequestedDownloadSupport(kCCTNamespace, false);
   ExpectRecentTab(kCCTNamespace, false);
-  ExpectOnlyOriginalTab(kCCTNamespace, false);
+  ExpectRestrictedToTabFromClientId(kCCTNamespace, false);
   ExpectDisabledWhenPrefetchDisabled(kCCTNamespace, true);
 }
 
@@ -212,7 +215,7 @@
   ExpectDownloadSupport(kDownloadNamespace, true);
   ExpectUserRequestedDownloadSupport(kDownloadNamespace, true);
   ExpectRecentTab(kDownloadNamespace, false);
-  ExpectOnlyOriginalTab(kDownloadNamespace, false);
+  ExpectRestrictedToTabFromClientId(kDownloadNamespace, false);
   ExpectDisabledWhenPrefetchDisabled(kDownloadNamespace, false);
 }
 
@@ -226,7 +229,7 @@
   ExpectDownloadSupport(kNTPSuggestionsNamespace, true);
   ExpectUserRequestedDownloadSupport(kNTPSuggestionsNamespace, true);
   ExpectRecentTab(kNTPSuggestionsNamespace, false);
-  ExpectOnlyOriginalTab(kNTPSuggestionsNamespace, false);
+  ExpectRestrictedToTabFromClientId(kNTPSuggestionsNamespace, false);
   ExpectDisabledWhenPrefetchDisabled(kNTPSuggestionsNamespace, false);
 }
 
@@ -240,7 +243,7 @@
   ExpectDownloadSupport(kSuggestedArticlesNamespace, false);
   ExpectUserRequestedDownloadSupport(kSuggestedArticlesNamespace, false);
   ExpectRecentTab(kSuggestedArticlesNamespace, false);
-  ExpectOnlyOriginalTab(kSuggestedArticlesNamespace, false);
+  ExpectRestrictedToTabFromClientId(kSuggestedArticlesNamespace, false);
   ExpectDisabledWhenPrefetchDisabled(kSuggestedArticlesNamespace, true);
 }
 
@@ -254,7 +257,7 @@
   ExpectDownloadSupport(kLivePageSharingNamespace, false);
   ExpectUserRequestedDownloadSupport(kLivePageSharingNamespace, false);
   ExpectRecentTab(kLivePageSharingNamespace, false);
-  ExpectOnlyOriginalTab(kLivePageSharingNamespace, true);
+  ExpectRestrictedToTabFromClientId(kLivePageSharingNamespace, true);
   ExpectDisabledWhenPrefetchDisabled(kLivePageSharingNamespace, false);
 }
 
diff --git a/components/offline_pages/core/offline_page_client_policy.h b/components/offline_pages/core/offline_page_client_policy.h
index 33e8a23..de73ea2 100644
--- a/components/offline_pages/core/offline_page_client_policy.h
+++ b/components/offline_pages/core/offline_page_client_policy.h
@@ -53,8 +53,10 @@
   bool is_user_requested_download;
   // Whether pages are shown in recent tabs ui.
   bool is_supported_by_recent_tabs;
-  // Whether pages should only be viewed in the tab they were generated in.
-  bool only_shown_in_original_tab;
+  // Whether pages can only be viewed in a specific tab. Pages controlled by
+  // this policy must have their ClientId::id field set to their assigned tab's
+  // id.
+  bool is_restricted_to_tab_from_client_id;
   // Whether pages are removed on user-initiated cache reset. Defaults to true.
   bool is_removed_on_cache_reset;
   // Whether the namespace should be disabled if prefetching-related preferences
@@ -69,7 +71,7 @@
       : is_supported_by_download(false),
         is_user_requested_download(false),
         is_supported_by_recent_tabs(false),
-        only_shown_in_original_tab(false),
+        is_restricted_to_tab_from_client_id(false),
         is_removed_on_cache_reset(true),
         disabled_when_prefetch_disabled(false),
         is_suggested(false),
@@ -157,10 +159,10 @@
     return *this;
   }
 
-  OfflinePageClientPolicyBuilder& SetIsOnlyShownInOriginalTab(
-      const bool only_shown_in_original_tab) {
-    policy_.feature_policy.only_shown_in_original_tab =
-        only_shown_in_original_tab;
+  OfflinePageClientPolicyBuilder& SetIsRestrictedToTabFromClientId(
+      const bool is_restricted_to_tab_from_client_id) {
+    policy_.feature_policy.is_restricted_to_tab_from_client_id =
+        is_restricted_to_tab_from_client_id;
     return *this;
   }
 
diff --git a/components/omnibox/browser/bookmark_provider.cc b/components/omnibox/browser/bookmark_provider.cc
index b9035d4..42802e63 100644
--- a/components/omnibox/browser/bookmark_provider.cc
+++ b/components/omnibox/browser/bookmark_provider.cc
@@ -36,8 +36,6 @@
 void BookmarkProvider::Start(const AutocompleteInput& input,
                              bool minimal_changes) {
   TRACE_EVENT0("omnibox", "BookmarkProvider::Start");
-  if (minimal_changes)
-    return;
   matches_.clear();
 
   if (input.from_omnibox_focus() || input.text().empty())
diff --git a/components/password_manager/core/browser/BUILD.gn b/components/password_manager/core/browser/BUILD.gn
index 943f5bac7..aea0cee5 100644
--- a/components/password_manager/core/browser/BUILD.gn
+++ b/components/password_manager/core/browser/BUILD.gn
@@ -159,8 +159,6 @@
     "webdata/logins_table.cc",
     "webdata/logins_table.h",
     "webdata/logins_table_win.cc",
-    "webdata/password_web_data_service_win.cc",
-    "webdata/password_web_data_service_win.h",
   ]
 
   if (password_reuse_detection_support) {
diff --git a/components/password_manager/core/browser/webdata/password_web_data_service_win.cc b/components/password_manager/core/browser/webdata/password_web_data_service_win.cc
deleted file mode 100644
index efbc3916..0000000
--- a/components/password_manager/core/browser/webdata/password_web_data_service_win.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/password_manager/core/browser/webdata/password_web_data_service_win.h"
-
-#include "base/bind.h"
-#include "base/single_thread_task_runner.h"
-#include "components/os_crypt/ie7_password_win.h"
-#include "components/password_manager/core/browser/webdata/logins_table.h"
-#include "components/webdata/common/web_database_service.h"
-
-PasswordWebDataService::PasswordWebDataService(
-    scoped_refptr<WebDatabaseService> wdbs,
-    scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
-    const ProfileErrorCallback& callback)
-    : WebDataServiceBase(wdbs, callback, ui_task_runner) {}
-
-void PasswordWebDataService::AddIE7Login(const IE7PasswordInfo& info) {
-  wdbs_->ScheduleDBTask(
-      FROM_HERE,
-      base::Bind(&PasswordWebDataService::AddIE7LoginImpl, this, info));
-}
-
-void PasswordWebDataService::RemoveIE7Login(const IE7PasswordInfo& info) {
-  wdbs_->ScheduleDBTask(
-      FROM_HERE,
-      base::Bind(&PasswordWebDataService::RemoveIE7LoginImpl, this, info));
-}
-
-PasswordWebDataService::Handle PasswordWebDataService::GetIE7Login(
-    const IE7PasswordInfo& info,
-    WebDataServiceConsumer* consumer) {
-  return wdbs_->ScheduleDBTaskWithResult(
-      FROM_HERE,
-      base::Bind(&PasswordWebDataService::GetIE7LoginImpl, this, info),
-      consumer);
-}
-
-WebDatabase::State PasswordWebDataService::AddIE7LoginImpl(
-    const IE7PasswordInfo& info,
-    WebDatabase* db) {
-  return LoginsTable::FromWebDatabase(db)->AddIE7Login(info) ?
-      WebDatabase::COMMIT_NEEDED : WebDatabase::COMMIT_NOT_NEEDED;
-}
-
-WebDatabase::State PasswordWebDataService::RemoveIE7LoginImpl(
-    const IE7PasswordInfo& info,
-    WebDatabase* db) {
-  return LoginsTable::FromWebDatabase(db)->RemoveIE7Login(info) ?
-      WebDatabase::COMMIT_NEEDED : WebDatabase::COMMIT_NOT_NEEDED;
-}
-
-std::unique_ptr<WDTypedResult> PasswordWebDataService::GetIE7LoginImpl(
-    const IE7PasswordInfo& info,
-    WebDatabase* db) {
-  IE7PasswordInfo result;
-  LoginsTable::FromWebDatabase(db)->GetIE7Login(info, &result);
-  return std::unique_ptr<WDTypedResult>(
-      new WDResult<IE7PasswordInfo>(PASSWORD_IE7_RESULT, result));
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-PasswordWebDataService::PasswordWebDataService(
-    scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)
-    : WebDataServiceBase(nullptr, ProfileErrorCallback(), ui_task_runner) {}
-
-PasswordWebDataService::~PasswordWebDataService() {
-}
diff --git a/components/password_manager/core/browser/webdata/password_web_data_service_win.h b/components/password_manager/core/browser/webdata/password_web_data_service_win.h
deleted file mode 100644
index 08d0e535..0000000
--- a/components/password_manager/core/browser/webdata/password_web_data_service_win.h
+++ /dev/null
@@ -1,78 +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_PASSWORD_MANAGER_CORE_BROWSER_WEBDATA_PASSWORD_WEB_DATA_SERVICE_WIN_H_
-#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_WEBDATA_PASSWORD_WEB_DATA_SERVICE_WIN_H_
-
-#include <vector>
-
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/sequenced_task_runner_helpers.h"
-#include "components/webdata/common/web_data_results.h"
-#include "components/webdata/common/web_data_service_base.h"
-#include "components/webdata/common/web_data_service_consumer.h"
-#include "components/webdata/common/web_database.h"
-
-struct IE7PasswordInfo;
-class WebDatabaseService;
-
-namespace base {
-class SingleThreadTaskRunner;
-}
-
-namespace content {
-class BrowserContext;
-}
-
-// PasswordWebDataService is used to access IE7/8 Password data stored in the
-// web database. All data is retrieved and archived in an asynchronous way.
-
-class WebDataServiceConsumer;
-
-class PasswordWebDataService : public WebDataServiceBase {
- public:
-  // Retrieves a WebDataService for the given context.
-  static scoped_refptr<PasswordWebDataService> FromBrowserContext(
-      content::BrowserContext* context);
-
-  PasswordWebDataService(
-      scoped_refptr<WebDatabaseService> wdbs,
-      scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
-      const ProfileErrorCallback& callback);
-
-  // Adds |info| to the list of imported passwords from ie7/ie8.
-  void AddIE7Login(const IE7PasswordInfo& info);
-
-  // Removes |info| from the list of imported passwords from ie7/ie8.
-  void RemoveIE7Login(const IE7PasswordInfo& info);
-
-  // Gets the login matching the information in |info|. |consumer| will be
-  // notified when the request is done. The result is of type
-  // WDResult<IE7PasswordInfo>.
-  // If there is no match, the fields of the IE7PasswordInfo will be empty.
-  // All requests return a handle. The handle can be used to cancel the request.
-  Handle GetIE7Login(const IE7PasswordInfo& info,
-                     WebDataServiceConsumer* consumer);
-
- protected:
-  // For unit tests, passes a null callback.
-  PasswordWebDataService(
-      scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner);
-
-  ~PasswordWebDataService() override;
-
- private:
-  // The following methods are only invoked on the DB sequence.
-  WebDatabase::State AddIE7LoginImpl(const IE7PasswordInfo& info,
-                                     WebDatabase* db);
-  WebDatabase::State RemoveIE7LoginImpl(const IE7PasswordInfo& info,
-                                        WebDatabase* db);
-  std::unique_ptr<WDTypedResult> GetIE7LoginImpl(const IE7PasswordInfo& info,
-                                                 WebDatabase* db);
-
-  DISALLOW_COPY_AND_ASSIGN(PasswordWebDataService);
-};
-
-#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_WEBDATA_PASSWORD_WEB_DATA_SERVICE_WIN_H_
diff --git a/components/translate/core/browser/BUILD.gn b/components/translate/core/browser/BUILD.gn
index edcc98b..7032eb0 100644
--- a/components/translate/core/browser/BUILD.gn
+++ b/components/translate/core/browser/BUILD.gn
@@ -59,6 +59,7 @@
     "//components/strings",
     "//components/translate/core/common",
     "//components/variations",
+    "//components/variations/net",
     "//google_apis",
     "//net",
     "//services/metrics/public/cpp:metrics_cpp",
diff --git a/components/translate/core/browser/resources/translate.js b/components/translate/core/browser/resources/translate.js
index fc78eaa8..595695d 100644
--- a/components/translate/core/browser/resources/translate.js
+++ b/components/translate/core/browser/resources/translate.js
@@ -104,21 +104,29 @@
 
   /**
    * Callback invoked when Translate Element's ready state is known.
-   * @type {Function}
+   * @type {function}
    */
   var readyCallback;
 
   /**
    * Callback invoked when Translate Element's translation result is known.
-   * @type {Function}
+   * @type {function}
    */
   var resultCallback;
 
   /**
+   * Callback invoked when Translate Element requests load of javascript files.
+   * Currently main.js and element_main.js are expected to be loaded.
+   * @type {function(string)}
+   */
+  var loadJavascriptCallback;
+
+  /**
    * Listens to security policy violations to set |errorCode|.
    */
   document.addEventListener('securitypolicyviolation', function(event) {
-    if (securityOrigin.startsWith(event.blockedURI)) {
+    if (securityOrigin.startsWith(event.blockedURI) &&
+        event.effectiveDirective == 'script-src') {
       errorCode = ERROR['BAD_ORIGIN'];
       invokeReadyCallback();
     }
@@ -175,7 +183,7 @@
   return {
     /**
      * Setter for readyCallback. No op if already set.
-     * @param {Function} callback The function to be invoked.
+     * @param {function} callback The function to be invoked.
      */
     set readyCallback(callback) {
       if (!readyCallback) {
@@ -185,7 +193,7 @@
 
     /**
      * Setter for resultCallback. No op if already set.
-     * @param {Function} callback The function to be invoked.
+     * @param {function} callback The function to be invoked.
      */
     set resultCallback(callback) {
       if (!resultCallback) {
@@ -194,6 +202,16 @@
     },
 
     /**
+     * Setter for loadJavascriptCallback. No op if already set.
+     * @param {function(string)} callback The function to be invoked.
+     */
+    set loadJavascriptCallback(callback) {
+      if (!loadJavascriptCallback) {
+        loadJavascriptCallback = callback;
+      }
+    },
+
+    /**
      * Whether the library is ready.
      * The translate function should only be called when |libReady| is true.
      * @type {boolean}
@@ -374,6 +392,12 @@
         errorCode = ERROR['BAD_ORIGIN'];
         return;
       }
+
+      if (loadJavascriptCallback) {
+        loadJavascriptCallback(url);
+        return;
+      }
+
       var xhr = new XMLHttpRequest();
       xhr.open('GET', url, true);
       xhr.onreadystatechange = function() {
diff --git a/components/translate/core/browser/translate_language_list.cc b/components/translate/core/browser/translate_language_list.cc
index 69618a4..1fdd0c4 100644
--- a/components/translate/core/browser/translate_language_list.cc
+++ b/components/translate/core/browser/translate_language_list.cc
@@ -227,8 +227,12 @@
     NotifyEvent(__LINE__, message);
 
     bool result = language_list_fetcher_->Request(
-        url, base::BindOnce(&TranslateLanguageList::OnLanguageListFetchComplete,
-                            base::Unretained(this)));
+        url,
+        base::BindOnce(&TranslateLanguageList::OnLanguageListFetchComplete,
+                       base::Unretained(this)),
+        // Use the strictest mode for request headers, since incognito state is
+        // not known.
+        /*is_incognito=*/true);
     if (!result)
       NotifyEvent(__LINE__, "Request is omitted due to retry limit");
   }
diff --git a/components/translate/core/browser/translate_manager.cc b/components/translate/core/browser/translate_manager.cc
index 74ea2d39..725d883 100644
--- a/components/translate/core/browser/translate_manager.cc
+++ b/components/translate/core/browser/translate_manager.cc
@@ -369,7 +369,7 @@
       base::Bind(&TranslateManager::OnTranslateScriptFetchComplete,
                  GetWeakPtr(), source_lang, target_lang);
 
-  script->Request(callback);
+  script->Request(callback, translate_driver_->IsIncognito());
 }
 
 void TranslateManager::RevertTranslation() {
diff --git a/components/translate/core/browser/translate_script.cc b/components/translate/core/browser/translate_script.cc
index a87b45f3..1225ba6c 100644
--- a/components/translate/core/browser/translate_script.cc
+++ b/components/translate/core/browser/translate_script.cc
@@ -59,7 +59,8 @@
 TranslateScript::~TranslateScript() {
 }
 
-void TranslateScript::Request(const RequestCallback& callback) {
+void TranslateScript::Request(const RequestCallback& callback,
+                              bool is_incognito) {
   script_fetch_start_time_ = base::Time::Now().ToJsTime();
 
   DCHECK(data_.empty()) << "Do not fetch the script if it is already fetched";
@@ -73,11 +74,12 @@
 
   GURL translate_script_url = GetTranslateScriptURL();
 
-  fetcher_.reset(new TranslateURLFetcher);
+  fetcher_ = std::make_unique<TranslateURLFetcher>();
   fetcher_->set_extra_request_header(kRequestHeader);
   fetcher_->Request(translate_script_url,
                     base::BindOnce(&TranslateScript::OnScriptFetchComplete,
-                                   base::Unretained(this)));
+                                   base::Unretained(this)),
+                    is_incognito);
 }
 
 // static
@@ -111,8 +113,6 @@
       translate_script_url,
       kAlwaysUseSslQueryName,
       kAlwaysUseSslQueryValue);
-#if !defined(OS_IOS)
-  // iOS doesn't need to use specific loaders for the isolated world.
   translate_script_url = net::AppendQueryParameter(
       translate_script_url,
       kCssLoaderCallbackQueryName,
@@ -121,7 +121,6 @@
       translate_script_url,
       kJavascriptLoaderCallbackQueryName,
       kJavascriptLoaderCallbackQueryValue);
-#endif  // !defined(OS_IOS)
 
   translate_script_url = AddHostLocaleToUrl(translate_script_url);
   translate_script_url = AddApiKeyToUrl(translate_script_url);
diff --git a/components/translate/core/browser/translate_script.h b/components/translate/core/browser/translate_script.h
index 44ebf6e..cc829128 100644
--- a/components/translate/core/browser/translate_script.h
+++ b/components/translate/core/browser/translate_script.h
@@ -43,8 +43,9 @@
   void Clear() { data_.clear(); }
 
   // Fetches the JS translate script (the script that is injected in the page
-  // to translate it).
-  void Request(const RequestCallback& callback);
+  // to translate it). |is_incognito| is used during the fetch to determine
+  // which variations headers to add.
+  void Request(const RequestCallback& callback, bool is_incognito);
 
   // Returns the URL to be used to load the translate script.
   static GURL GetTranslateScriptURL();
diff --git a/components/translate/core/browser/translate_script_unittest.cc b/components/translate/core/browser/translate_script_unittest.cc
index 27192c3..605cc28 100644
--- a/components/translate/core/browser/translate_script_unittest.cc
+++ b/components/translate/core/browser/translate_script_unittest.cc
@@ -46,8 +46,9 @@
   }
 
   void Request() {
-    script_->Request(
-        base::Bind(&TranslateScriptTest::OnComplete, base::Unretained(this)));
+    script_->Request(base::BindRepeating(&TranslateScriptTest::OnComplete,
+                                         base::Unretained(this)),
+                     /*is_incognito=*/false);
   }
 
   const std::string& GetData() { return script_->data(); }
diff --git a/components/translate/core/browser/translate_url_fetcher.cc b/components/translate/core/browser/translate_url_fetcher.cc
index 364da62..9dae291 100644
--- a/components/translate/core/browser/translate_url_fetcher.cc
+++ b/components/translate/core/browser/translate_url_fetcher.cc
@@ -7,6 +7,7 @@
 #include "base/memory/ref_counted.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
 #include "components/translate/core/browser/translate_download_manager.h"
+#include "components/variations/net/variations_http_headers.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
@@ -28,7 +29,8 @@
 TranslateURLFetcher::~TranslateURLFetcher() {}
 
 bool TranslateURLFetcher::Request(const GURL& url,
-                                  TranslateURLFetcher::Callback callback) {
+                                  TranslateURLFetcher::Callback callback,
+                                  bool is_incognito) {
   // This function is not supposed to be called if the previous operation is not
   // finished.
   if (state_ == REQUESTING) {
@@ -98,8 +100,12 @@
   if (!extra_request_header_.empty())
     resource_request->headers.AddHeadersFromString(extra_request_header_);
 
-  simple_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
-                                                    traffic_annotation);
+  simple_loader_ =
+      variations::CreateSimpleURLLoaderWithVariationsHeadersUnknownSignedIn(
+          std::move(resource_request),
+          is_incognito ? variations::InIncognito::kYes
+                       : variations::InIncognito::kNo,
+          traffic_annotation);
   // Set retry parameter for HTTP status code 5xx. This doesn't work against
   // 106 (net::ERR_INTERNET_DISCONNECTED) and so on.
   // TranslateLanguageList handles network status, and implements retry.
diff --git a/components/translate/core/browser/translate_url_fetcher.h b/components/translate/core/browser/translate_url_fetcher.h
index 6bb37ae..58ac6b81 100644
--- a/components/translate/core/browser/translate_url_fetcher.h
+++ b/components/translate/core/browser/translate_url_fetcher.h
@@ -52,8 +52,9 @@
   // Requests to |url|. |callback| will be invoked when the function returns
   // true, and the request is finished asynchronously.
   // Returns false if the previous request is not finished, or the request
-  // is omitted due to retry limitation.
-  bool Request(const GURL& url, Callback callback);
+  // is omitted due to retry limitation. |is_incognito| is used during the fetch
+  // to determine which variations headers to add.
+  bool Request(const GURL& url, Callback callback, bool is_incognito);
 
   // Gets internal state.
   State state() { return state_; }
diff --git a/components/translate/ios/browser/resources/translate_ios.js b/components/translate/ios/browser/resources/translate_ios.js
index 3af90141..077db2b3 100644
--- a/components/translate/ios/browser/resources/translate_ios.js
+++ b/components/translate/ios/browser/resources/translate_ios.js
@@ -36,4 +36,13 @@
       'translationTime': cr.googleTranslate.translationTime});
 }
 
+/**
+ * Sets a callback to inform host to download javascript.
+ */
+cr.googleTranslate.loadJavascriptCallback = function(url) {
+  __gCrWeb.message.invokeOnHost({
+      'command': 'translate.loadjavascript',
+      'url': url});
+}
+
 }  // installTranslateCallbacks
diff --git a/components/translate/ios/browser/translate_controller.h b/components/translate/ios/browser/translate_controller.h
index 34333de..48c8ac2 100644
--- a/components/translate/ios/browser/translate_controller.h
+++ b/components/translate/ios/browser/translate_controller.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_TRANSLATE_IOS_BROWSER_TRANSLATE_CONTROLLER_H_
 #define COMPONENTS_TRANSLATE_IOS_BROWSER_TRANSLATE_CONTROLLER_H_
 
+#include <memory>
 #include <string>
 
 #include "base/gtest_prod_util.h"
@@ -13,17 +14,19 @@
 #include "base/memory/weak_ptr.h"
 #include "components/translate/core/common/translate_errors.h"
 #include "ios/web/public/web_state/web_state_observer.h"
+#include "services/network/public/cpp/simple_url_loader.h"
 
 @class JsTranslateManager;
 class GURL;
 
 namespace base {
 class DictionaryValue;
-}
+}  // namespace base
 
 namespace web {
+class NavigationContext;
 class WebState;
-}
+}  // namespace web
 
 namespace translate {
 
@@ -89,14 +92,25 @@
   // Return false if the command is invalid.
   bool OnTranslateReady(const base::DictionaryValue& command);
   bool OnTranslateComplete(const base::DictionaryValue& command);
+  bool OnTranslateLoadJavaScript(const base::DictionaryValue& command);
+
+  // Use to fetch additional scripts needed for translate.
+  void FetchScript(const std::string& url);
+  // The callback when the script is fetched or a server error occurred.
+  void OnScriptFetchComplete(std::unique_ptr<std::string> response_body);
 
   // web::WebStateObserver implementation:
   void WebStateDestroyed(web::WebState* web_state) override;
+  void DidStartNavigation(web::WebState* web_state,
+                          web::NavigationContext* navigation_context) override;
 
   // The WebState this instance is observing. Will be null after
   // WebStateDestroyed has been called.
   web::WebState* web_state_ = nullptr;
 
+  // Used to fetch additional scripts needed for translate.
+  std::unique_ptr<network::SimpleURLLoader> script_fetcher_;
+
   Observer* observer_;
   base::scoped_nsobject<JsTranslateManager> js_manager_;
   base::WeakPtrFactory<TranslateController> weak_method_factory_;
diff --git a/components/translate/ios/browser/translate_controller.mm b/components/translate/ios/browser/translate_controller.mm
index bdfe588e..8819b78 100644
--- a/components/translate/ios/browser/translate_controller.mm
+++ b/components/translate/ios/browser/translate_controller.mm
@@ -7,10 +7,18 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/logging.h"
+#include "base/strings/string16.h"
 #include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
+#include "components/translate/core/common/translate_util.h"
 #import "components/translate/ios/browser/js_translate_manager.h"
+#include "ios/web/public/browser_state.h"
+#include "ios/web/public/web_state/navigation_context.h"
 #include "ios/web/public/web_state/web_state.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "url/gurl.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -87,8 +95,9 @@
     return OnTranslateReady(command);
   if (out_string == "translate.status")
     return OnTranslateComplete(command);
+  if (out_string == "translate.loadjavascript")
+    return OnTranslateLoadJavaScript(command);
 
-  NOTREACHED();
   return false;
 }
 
@@ -102,7 +111,6 @@
       !command.GetDouble("errorCode", &error_code) ||
       error_code < TranslateErrors::NONE ||
       error_code >= TranslateErrors::TRANSLATE_ERROR_MAX) {
-    NOTREACHED();
     return false;
   }
 
@@ -110,7 +118,6 @@
       static_cast<TranslateErrors::Type>(error_code);
   if (error_type == TranslateErrors::NONE) {
     if (!command.HasKey("loadTime") || !command.HasKey("readyTime")) {
-      NOTREACHED();
       return false;
     }
     command.GetDouble("loadTime", &load_time);
@@ -131,7 +138,6 @@
       !command.GetDouble("errorCode", &error_code) ||
       error_code < TranslateErrors::NONE ||
       error_code >= TranslateErrors::TRANSLATE_ERROR_MAX) {
-    NOTREACHED();
     return false;
   }
 
@@ -140,7 +146,6 @@
   if (error_type == TranslateErrors::NONE) {
     if (!command.HasKey("originalPageLanguage") ||
         !command.HasKey("translationTime")) {
-      NOTREACHED();
       return false;
     }
     command.GetString("originalPageLanguage", &original_language);
@@ -153,6 +158,43 @@
   return true;
 }
 
+bool TranslateController::OnTranslateLoadJavaScript(
+    const base::DictionaryValue& command) {
+  std::string url;
+  if (!command.HasKey("url") || !command.GetString("url", &url)) {
+    return false;
+  }
+
+  FetchScript(url);
+
+  return true;
+}
+
+void TranslateController::FetchScript(const std::string& url) {
+  GURL security_origin = translate::GetTranslateSecurityOrigin();
+  if (url.find(security_origin.spec()) || script_fetcher_) {
+    return;
+  }
+
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = GURL(url);
+
+  script_fetcher_ = network::SimpleURLLoader::Create(
+      std::move(resource_request), NO_TRAFFIC_ANNOTATION_YET);
+  script_fetcher_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+      web_state_->GetBrowserState()->GetURLLoaderFactory(),
+      base::BindOnce(&TranslateController::OnScriptFetchComplete,
+                     base::Unretained(this)));
+}
+
+void TranslateController::OnScriptFetchComplete(
+    std::unique_ptr<std::string> response_body) {
+  if (response_body) {
+    web_state_->ExecuteJavaScript(base::UTF8ToUTF16(*response_body));
+  }
+  script_fetcher_.reset();
+}
+
 // web::WebStateObserver implementation.
 
 void TranslateController::WebStateDestroyed(web::WebState* web_state) {
@@ -160,6 +202,16 @@
   web_state_->RemoveScriptCommandCallback(kCommandPrefix);
   web_state_->RemoveObserver(this);
   web_state_ = nullptr;
+
+  script_fetcher_.reset();
+}
+
+void TranslateController::DidStartNavigation(
+    web::WebState* web_state,
+    web::NavigationContext* navigation_context) {
+  if (!navigation_context->IsSameDocument()) {
+    script_fetcher_.reset();
+  }
 }
 
 }  // namespace translate
diff --git a/components/ukm/test_ukm_recorder.cc b/components/ukm/test_ukm_recorder.cc
index dbf810a..445348f9 100644
--- a/components/ukm/test_ukm_recorder.cc
+++ b/components/ukm/test_ukm_recorder.cc
@@ -56,14 +56,6 @@
   return false;
 }
 
-void TestUkmRecorder::AddEntry(mojom::UkmEntryPtr entry) {
-  const bool should_run_callback =
-      entry && entry_hash_to_wait_for_ == entry->event_hash;
-  UkmRecorderImpl::AddEntry(std::move(entry));
-  if (should_run_callback && on_add_entry_)
-    std::move(on_add_entry_).Run();
-}
-
 const UkmSource* TestUkmRecorder::GetSourceForSourceId(
     SourceId source_id) const {
   const UkmSource* source = nullptr;
@@ -76,12 +68,6 @@
   return source;
 }
 
-void TestUkmRecorder::SetOnAddEntryCallback(base::StringPiece entry_name,
-                                            base::OnceClosure on_add_entry) {
-  on_add_entry_ = std::move(on_add_entry);
-  entry_hash_to_wait_for_ = base::HashMetricName(entry_name);
-}
-
 std::vector<const mojom::UkmEntry*> TestUkmRecorder::GetEntriesByName(
     base::StringPiece entry_name) const {
   uint64_t hash = base::HashMetricName(entry_name);
diff --git a/components/ukm/test_ukm_recorder.h b/components/ukm/test_ukm_recorder.h
index fab3c1ca..6950c3c 100644
--- a/components/ukm/test_ukm_recorder.h
+++ b/components/ukm/test_ukm_recorder.h
@@ -31,8 +31,6 @@
   bool ShouldRestrictToWhitelistedSourceIds() const override;
   bool ShouldRestrictToWhitelistedEntries() const override;
 
-  void AddEntry(mojom::UkmEntryPtr entry) override;
-
   size_t sources_count() const { return sources().size(); }
 
   size_t entries_count() const { return entries().size(); }
@@ -49,10 +47,6 @@
   // Gets UkmSource data for a single SourceId.
   const UkmSource* GetSourceForSourceId(ukm::SourceId source_id) const;
 
-  // Sets a callback that will be called when recording an entry for entry name.
-  void SetOnAddEntryCallback(base::StringPiece entry_name,
-                             base::OnceClosure on_add_entry);
-
   // Gets all of the entries recorded for entry name.
   std::vector<const mojom::UkmEntry*> GetEntriesByName(
       base::StringPiece entry_name) const;
@@ -81,9 +75,6 @@
                                        base::StringPiece metric_name);
 
  private:
-  uint64_t entry_hash_to_wait_for_;
-  base::OnceClosure on_add_entry_;
-
   DISALLOW_COPY_AND_ASSIGN(TestUkmRecorder);
 };
 
diff --git a/components/ukm/ukm_recorder_impl.h b/components/ukm/ukm_recorder_impl.h
index 4acf6d88..7f3084f 100644
--- a/components/ukm/ukm_recorder_impl.h
+++ b/components/ukm/ukm_recorder_impl.h
@@ -82,7 +82,6 @@
   }
 
   // UkmRecorder:
-  void AddEntry(mojom::UkmEntryPtr entry) override;
   void UpdateSourceURL(SourceId source_id, const GURL& url) override;
   void UpdateAppURL(SourceId source_id, const GURL& url) override;
   void RecordNavigation(
@@ -127,6 +126,8 @@
 
   void RecordSource(std::unique_ptr<UkmSource> source);
 
+  void AddEntry(mojom::UkmEntryPtr entry) override;
+
   // Load sampling configurations from field-trial information.
   void LoadExperimentSamplingInfo();
 
diff --git a/components/viz/host/gpu_host_impl.cc b/components/viz/host/gpu_host_impl.cc
index 3b9b27a..10603fc 100644
--- a/components/viz/host/gpu_host_impl.cc
+++ b/components/viz/host/gpu_host_impl.cc
@@ -368,9 +368,6 @@
 #if defined(OS_ANDROID)
     std::string build_fp =
         base::android::BuildInfo::GetInstance()->android_build_fp();
-    // TODO(ericrk): Remove this after it's up for a few days.
-    // https://crbug.com/699122.
-    CHECK(!build_fp.empty());
     shader_prefix_key_ += "-" + build_fp;
 #endif
   }
@@ -529,9 +526,9 @@
   delegate_->DisableGpuCompositing();
 }
 
+#if defined(OS_WIN)
 void GpuHostImpl::SetChildSurface(gpu::SurfaceHandle parent,
                                   gpu::SurfaceHandle child) {
-#if defined(OS_WIN)
   constexpr char kBadMessageError[] = "Bad parenting request from gpu process.";
   if (!params_.in_process) {
     DWORD parent_process_id = 0;
@@ -555,10 +552,8 @@
                                                                  child)) {
     LOG(ERROR) << kBadMessageError;
   }
-#else
-  NOTREACHED();
-#endif
 }
+#endif  // defined(OS_WIN)
 
 void GpuHostImpl::StoreShaderToDisk(int32_t client_id,
                                     const std::string& key,
diff --git a/components/viz/host/gpu_host_impl.h b/components/viz/host/gpu_host_impl.h
index c96d6bdf..3e9eb53 100644
--- a/components/viz/host/gpu_host_impl.h
+++ b/components/viz/host/gpu_host_impl.h
@@ -105,14 +105,14 @@
     bool in_process = false;
 
     // Whether caching GPU shader on disk is disabled or not.
-    bool disable_gpu_shader_disk_cache;
+    bool disable_gpu_shader_disk_cache = false;
 
     // A string representing the product name and version; used to build a
     // prefix for shader keys.
     std::string product;
 
     // Number of frames to CompositorFrame activation deadline.
-    base::Optional<uint32_t> deadline_to_synchronize_surfaces = base::nullopt;
+    base::Optional<uint32_t> deadline_to_synchronize_surfaces;
 
     // Task runner corresponding to the main thread.
     scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner;
@@ -203,8 +203,10 @@
                       gpu::error::ContextLostReason reason,
                       const GURL& active_url) override;
   void DisableGpuCompositing() override;
+#if defined(OS_WIN)
   void SetChildSurface(gpu::SurfaceHandle parent,
                        gpu::SurfaceHandle child) override;
+#endif
   void StoreShaderToDisk(int32_t client_id,
                          const std::string& key,
                          const std::string& shader) override;
diff --git a/components/webdata_services/web_data_service_wrapper.cc b/components/webdata_services/web_data_service_wrapper.cc
index 746398d..5bb235a 100644
--- a/components/webdata_services/web_data_service_wrapper.cc
+++ b/components/webdata_services/web_data_service_wrapper.cc
@@ -32,10 +32,6 @@
 #include "components/webdata/common/web_database_service.h"
 #include "components/webdata/common/webdata_constants.h"
 
-#if defined(OS_WIN)
-#include "components/password_manager/core/browser/webdata/password_web_data_service_win.h"
-#endif
-
 #if !defined(OS_IOS)
 #include "components/payments/content/payment_manifest_web_data_service.h"
 #include "components/payments/content/payment_method_manifest_table.h"
@@ -160,13 +156,6 @@
                        base::Bind(show_error_callback, ERROR_LOADING_TOKEN));
   token_web_data_->Init();
 
-#if defined(OS_WIN)
-  password_web_data_ = new PasswordWebDataService(
-      profile_database_, ui_task_runner,
-      base::Bind(show_error_callback, ERROR_LOADING_PASSWORD));
-  password_web_data_->Init();
-#endif
-
 #if !defined(OS_IOS)
   payment_manifest_web_data_ = new payments::PaymentManifestWebDataService(
       profile_database_,
@@ -210,10 +199,6 @@
   keyword_web_data_->ShutdownOnUISequence();
   token_web_data_->ShutdownOnUISequence();
 
-#if defined(OS_WIN)
-  password_web_data_->ShutdownOnUISequence();
-#endif
-
 #if !defined(OS_IOS)
   payment_manifest_web_data_->ShutdownOnUISequence();
 #endif
@@ -242,13 +227,6 @@
   return token_web_data_.get();
 }
 
-#if defined(OS_WIN)
-scoped_refptr<PasswordWebDataService>
-WebDataServiceWrapper::GetPasswordWebData() {
-  return password_web_data_.get();
-}
-#endif
-
 #if !defined(OS_IOS)
 scoped_refptr<payments::PaymentManifestWebDataService>
 WebDataServiceWrapper::GetPaymentManifestWebData() {
diff --git a/components/webdata_services/web_data_service_wrapper.h b/components/webdata_services/web_data_service_wrapper.h
index 76330da..cc430039 100644
--- a/components/webdata_services/web_data_service_wrapper.h
+++ b/components/webdata_services/web_data_service_wrapper.h
@@ -19,10 +19,6 @@
 class TokenWebData;
 class WebDatabaseService;
 
-#if defined(OS_WIN)
-class PasswordWebDataService;
-#endif
-
 #if !defined(OS_IOS)
 namespace payments {
 class PaymentManifestWebDataService;
@@ -88,9 +84,6 @@
   GetAccountAutofillWebData();
   virtual scoped_refptr<KeywordWebDataService> GetKeywordWebData();
   virtual scoped_refptr<TokenWebData> GetTokenWebData();
-#if defined(OS_WIN)
-  virtual scoped_refptr<PasswordWebDataService> GetPasswordWebData();
-#endif
 #if !defined(OS_IOS)
   virtual scoped_refptr<payments::PaymentManifestWebDataService>
   GetPaymentManifestWebData();
@@ -109,10 +102,6 @@
   scoped_refptr<KeywordWebDataService> keyword_web_data_;
   scoped_refptr<TokenWebData> token_web_data_;
 
-#if defined(OS_WIN)
-  scoped_refptr<PasswordWebDataService> password_web_data_;
-#endif
-
 #if !defined(OS_IOS)
   scoped_refptr<payments::PaymentManifestWebDataService>
       payment_manifest_web_data_;
diff --git a/content/browser/accessibility/browser_accessibility_manager_win.cc b/content/browser/accessibility/browser_accessibility_manager_win.cc
index 7bc5c248..1a3eecf 100644
--- a/content/browser/accessibility/browser_accessibility_manager_win.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_win.cc
@@ -179,10 +179,18 @@
       FireWinAccessibilityEvent(EVENT_OBJECT_REORDER, node);
       break;
     case Event::LIVE_REGION_CHANGED:
-      // NVDA and JAWS are inconsistent about speaking this event in content.
-      // Because of this, and because Firefox does not currently fire it, we
-      // are avoiding this event for now.
-      // FireWinAccessibilityEvent(EVENT_OBJECT_LIVEREGIONCHANGED, node);
+      // This event is redundant with the IA2_EVENT_TEXT_INSERTED events;
+      // however, JAWS 2018 and earlier do not process the text inserted
+      // events when "virtual cursor mode" is turned off (Insert+Z).
+      // Fortunately, firing the redudant event does not cause duplicate
+      // verbalizations in either screen reader.
+      // Future versions of JAWS may process the text inserted event when
+      // in focus mode, and so at some point the live region
+      // changed events may truly become redundant with the text inserted
+      // events. Note: Firefox does not fire this event, but JAWS processes
+      // Firefox live region events differently (utilizes MSAA's
+      // EVENT_OBJECT_SHOW).
+      FireWinAccessibilityEvent(EVENT_OBJECT_LIVEREGIONCHANGED, node);
       break;
     case Event::LOAD_COMPLETE:
       FireWinAccessibilityEvent(IA2_EVENT_DOCUMENT_LOAD_COMPLETE, node);
diff --git a/content/browser/android/gesture_listener_manager.cc b/content/browser/android/gesture_listener_manager.cc
index 67632979..4d03f78e 100644
--- a/content/browser/android/gesture_listener_manager.cc
+++ b/content/browser/android/gesture_listener_manager.cc
@@ -111,7 +111,7 @@
   ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
   if (j_obj.is_null())
     return;
-  Java_GestureListenerManagerImpl_onDestroy(env, j_obj);
+  Java_GestureListenerManagerImpl_onNativeDestroyed(env, j_obj);
 }
 
 void GestureListenerManager::ResetGestureDetection(
diff --git a/content/browser/android/ime_adapter_android.cc b/content/browser/android/ime_adapter_android.cc
index 6bb9735..774cdd0 100644
--- a/content/browser/android/ime_adapter_android.cc
+++ b/content/browser/android/ime_adapter_android.cc
@@ -150,7 +150,7 @@
   JNIEnv* env = AttachCurrentThread();
   ScopedJavaLocalRef<jobject> obj = java_ime_adapter_.get(env);
   if (!obj.is_null())
-    Java_ImeAdapterImpl_destroy(env, obj);
+    Java_ImeAdapterImpl_onNativeDestroyed(env, obj);
 }
 
 void ImeAdapterAndroid::UpdateRenderProcessConnection(
diff --git a/content/browser/android/select_popup.cc b/content/browser/android/select_popup.cc
index 44d187ce..404e219 100644
--- a/content/browser/android/select_popup.cc
+++ b/content/browser/android/select_popup.cc
@@ -53,7 +53,7 @@
   ScopedJavaLocalRef<jobject> j_obj = java_obj_.get(env);
   if (j_obj.is_null())
     return;
-  Java_SelectPopup_destroy(env, j_obj);
+  Java_SelectPopup_onNativeDestroyed(env, j_obj);
 }
 
 void SelectPopup::ShowMenu(RenderFrameHost* frame,
diff --git a/content/browser/android/text_suggestion_host_android.cc b/content/browser/android/text_suggestion_host_android.cc
index ac308472..0f072fb 100644
--- a/content/browser/android/text_suggestion_host_android.cc
+++ b/content/browser/android/text_suggestion_host_android.cc
@@ -64,7 +64,7 @@
   JNIEnv* env = AttachCurrentThread();
   ScopedJavaLocalRef<jobject> obj = java_text_suggestion_host_.get(env);
   if (!obj.is_null())
-    Java_TextSuggestionHost_destroy(env, obj);
+    Java_TextSuggestionHost_onNativeDestroyed(env, obj);
 }
 
 void TextSuggestionHostAndroid::UpdateRenderProcessConnection(
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.cc b/content/browser/devtools/render_frame_devtools_agent_host.cc
index 700c766..f80fb07 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.cc
+++ b/content/browser/devtools/render_frame_devtools_agent_host.cc
@@ -427,14 +427,7 @@
 
 bool RenderFrameDevToolsAgentHost::AttachSession(DevToolsSession* session,
                                                  TargetRegistry* registry) {
-  DevToolsManager* manager = DevToolsManager::GetInstance();
-  if (manager->delegate() && web_contents()) {
-    if (!manager->delegate()->AllowInspectingWebContents(web_contents()))
-      return false;
-  }
-  const bool is_webui =
-      frame_host_ && (frame_host_->web_ui() || frame_host_->pending_web_ui());
-  if (!session->client()->MayAttachToRenderer(frame_host_, is_webui))
+  if (!ShouldAllowSession(session, frame_host_))
     return false;
 
   session->SetRenderer(frame_host_ ? frame_host_->GetProcess()->GetID()
@@ -599,14 +592,10 @@
   agent_ptr_.reset();
 
   std::vector<DevToolsSession*> restricted_sessions;
-  const bool is_webui =
-      frame_host && (frame_host->web_ui() || frame_host->pending_web_ui());
-
   for (DevToolsSession* session : sessions()) {
-    if (!session->client()->MayAttachToRenderer(frame_host, is_webui))
+    if (!ShouldAllowSession(session, frame_host))
       restricted_sessions.push_back(session);
   }
-
   if (!restricted_sessions.empty())
     ForceDetachRestrictedSessions(restricted_sessions);
 
@@ -959,4 +948,19 @@
   return frame_tree_node_ && frame_tree_node_->parent();
 }
 
+bool RenderFrameDevToolsAgentHost::ShouldAllowSession(
+    DevToolsSession* session,
+    RenderFrameHostImpl* frame_host) {
+  DevToolsManager* manager = DevToolsManager::GetInstance();
+  if (manager->delegate() && frame_host) {
+    if (!manager->delegate()->AllowInspectingRenderFrameHost(frame_host))
+      return false;
+  }
+  const bool is_webui =
+      frame_host && (frame_host->web_ui() || frame_host->pending_web_ui());
+  if (!session->client()->MayAttachToRenderer(frame_host, is_webui))
+    return false;
+  return true;
+}
+
 }  // namespace content
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.h b/content/browser/devtools/render_frame_devtools_agent_host.h
index a0867d1..37f0c8d1 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.h
+++ b/content/browser/devtools/render_frame_devtools_agent_host.h
@@ -190,6 +190,9 @@
   void RevokePolicy();
   void SetFrameTreeNode(FrameTreeNode* frame_tree_node);
 
+  bool ShouldAllowSession(DevToolsSession* session,
+                          RenderFrameHostImpl* frame_host);
+
 #if defined(OS_ANDROID)
   device::mojom::WakeLock* GetWakeLock();
 #endif
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 1a58a87..94905e7 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -4075,7 +4075,7 @@
       auto factory_request = mojo::MakeRequest(&factory_proxy_info);
       GetContentClient()->browser()->WillCreateURLLoaderFactory(
           browser_context, this, false /* is_navigation */, common_params.url,
-          &factory_request);
+          &factory_request, nullptr /* bypass_redirect_checks */);
       // Keep DevTools proxy lasy, i.e. closest to the network.
       RenderFrameDevToolsAgentHost::WillCreateURLLoaderFactory(
           this, false /* is_navigation */, false /* is_download */,
@@ -4811,10 +4811,9 @@
   bool bypass_redirect_checks = false;
 
   if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
-    bypass_redirect_checks =
-        GetContentClient()->browser()->WillCreateURLLoaderFactory(
-            context, this, false /* is_navigation */, url,
-            &default_factory_request);
+    GetContentClient()->browser()->WillCreateURLLoaderFactory(
+        context, this, false /* is_navigation */, url, &default_factory_request,
+        &bypass_redirect_checks);
   }
 
   // Keep DevTools proxy lasy, i.e. closest to the network.
diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc
index a135b69d..0097241 100644
--- a/content/browser/gpu/gpu_process_host.cc
+++ b/content/browser/gpu/gpu_process_host.cc
@@ -872,13 +872,6 @@
   }
 }
 
-#if defined(OS_ANDROID)
-void GpuProcessHost::OnDestroyingVideoSurfaceAck() {
-  TRACE_EVENT0("gpu", "GpuProcessHost::OnDestroyingVideoSurfaceAck");
-  if (!send_destroying_video_surface_done_cb_.is_null())
-    base::ResetAndReturn(&send_destroying_video_surface_done_cb_).Run();
-}
-#endif
 
 void GpuProcessHost::OnProcessLaunched() {
   UMA_HISTOGRAM_TIMES("GPU.GPUProcessLaunchTime",
@@ -952,14 +945,13 @@
 #endif
 }
 
-void GpuProcessHost::BlockDomainFrom3DAPIs(const GURL& url,
-                                           Delegate::DomainGuilt guilt) {
+void GpuProcessHost::BlockDomainFrom3DAPIs(const GURL& url, DomainGuilt guilt) {
   GpuDataManagerImpl::DomainGuilt gpu_data_manager_guilt;
   switch (guilt) {
-    case Delegate::DomainGuilt::kKnown:
+    case DomainGuilt::kKnown:
       gpu_data_manager_guilt = GpuDataManagerImpl::DOMAIN_GUILT_KNOWN;
       break;
-    case Delegate::DomainGuilt::kUnknown:
+    case DomainGuilt::kUnknown:
       gpu_data_manager_guilt = GpuDataManagerImpl::DOMAIN_GUILT_UNKNOWN;
   }
   GpuDataManagerImpl::GetInstance()->BlockDomainFrom3DAPIs(
@@ -1118,9 +1110,6 @@
 
   if (gpu_host_)
     gpu_host_->SendOutstandingReplies();
-
-  if (!send_destroying_video_surface_done_cb_.is_null())
-    base::ResetAndReturn(&send_destroying_video_surface_done_cb_).Run();
 }
 
 void GpuProcessHost::RecordProcessCrash() {
diff --git a/content/browser/gpu/gpu_process_host.h b/content/browser/gpu/gpu_process_host.h
index e56abaef..c9a47f6 100644
--- a/content/browser/gpu/gpu_process_host.h
+++ b/content/browser/gpu/gpu_process_host.h
@@ -144,8 +144,7 @@
           gpu_feature_info_for_hardware_gpu) override;
   void DidFailInitialize() override;
   void DidCreateContextSuccessfully() override;
-  void BlockDomainFrom3DAPIs(const GURL& url,
-                             Delegate::DomainGuilt guilt) override;
+  void BlockDomainFrom3DAPIs(const GURL& url, DomainGuilt guilt) override;
   void DisableGpuCompositing() override;
   bool GpuAccessAllowed() const override;
   gpu::ShaderCacheFactory* GetShaderCacheFactory() override;
@@ -162,10 +161,7 @@
   void SendGpuProcessMessage(IPC::Message* message) override;
 #endif
 
-// Message handlers.
-#if defined(OS_ANDROID)
-  void OnDestroyingVideoSurfaceAck();
-#endif
+  // Message handlers.
   void OnFieldTrialActivated(const std::string& trial_name);
 
   bool LaunchGpuProcess();
@@ -183,9 +179,6 @@
   // GPU process id in case GPU is not in-process.
   base::ProcessId process_id_ = base::kNullProcessId;
 
-  // A callback to signal the completion of a SendDestroyingVideoSurface call.
-  base::Closure send_destroying_video_surface_done_cb_;
-
   // Qeueud messages to send when the process launches.
   base::queue<IPC::Message*> queued_messages_;
 
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index d903495..91f6f32 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -1511,8 +1511,8 @@
     auto factory_request = mojo::MakeRequest(&factory_info);
     bool use_proxy = GetContentClient()->browser()->WillCreateURLLoaderFactory(
         partition->browser_context(), frame_tree_node->current_frame_host(),
-        true /* is_navigation */, new_request->url, &factory_request);
-    bypass_redirect_checks = use_proxy;
+        true /* is_navigation */, new_request->url, &factory_request,
+        &bypass_redirect_checks);
     if (RenderFrameDevToolsAgentHost::WillCreateURLLoaderFactory(
             frame_tree_node->current_frame_host(), true, false,
             &factory_request)) {
@@ -1644,7 +1644,8 @@
   auto* frame = frame_tree_node->current_frame_host();
   GetContentClient()->browser()->WillCreateURLLoaderFactory(
       frame->GetSiteInstance()->GetBrowserContext(), frame,
-      true /* is_navigation */, url, &factory);
+      true /* is_navigation */, url, &factory,
+      nullptr /* bypass_redirect_checks */);
   it->second->Clone(std::move(factory));
 }
 
diff --git a/content/browser/service_worker/service_worker_navigation_loader.cc b/content/browser/service_worker/service_worker_navigation_loader.cc
index 3c47df54..803a4100b 100644
--- a/content/browser/service_worker/service_worker_navigation_loader.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader.cc
@@ -234,9 +234,10 @@
       fetch_dispatcher_->MaybeStartNavigationPreloadWithURLLoader(
           resource_request_, url_loader_factory_getter_.get(),
           base::DoNothing(/* TODO(crbug/762357): metrics? */));
+
+  // Record worker start time here as |fetch_dispatcher_| will start a service
+  // worker if there is no running service worker.
   response_head_.service_worker_start_time = base::TimeTicks::Now();
-  response_head_.load_timing.send_start = base::TimeTicks::Now();
-  response_head_.load_timing.send_end = base::TimeTicks::Now();
   fetch_dispatcher_->Run();
 }
 
@@ -276,7 +277,12 @@
       "initial_worker_status",
       EmbeddedWorkerInstance::StatusToString(initial_worker_status));
 
-  response_head_.service_worker_ready_time = base::TimeTicks::Now();
+  // At this point a service worker is running and the fetch event is about
+  // to dispatch. Record some load timings.
+  base::TimeTicks now = base::TimeTicks::Now();
+  response_head_.service_worker_ready_time = now;
+  response_head_.load_timing.send_start = now;
+  response_head_.load_timing.send_end = now;
 
   // Note that we don't record worker preparation time in S13nServiceWorker
   // path for now. If we want to measure worker preparation time we can
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 733a479..acec5df 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -1309,7 +1309,7 @@
     if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
       GetContentClient()->browser()->WillCreateURLLoaderFactory(
           browser_context(), nullptr, false /* is_navigation */, GURL(),
-          &request);
+          &request, nullptr /* bypass_redirect_checks */);
     }
     GetNetworkContext()->CreateURLLoaderFactory(std::move(request),
                                                 std::move(params));
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index 8135b93..55aed48 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -219,7 +219,6 @@
     "java/src/org/chromium/content/browser/selection/SmartSelectionProvider.java",
     "java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java",
     "java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java",
-    "java/src/org/chromium/content/browser/webcontents/WebContentsUserData.java",
     "java/src/org/chromium/content/common/ContentSwitchUtils.java",
     "java/src/org/chromium/content/common/ServiceManagerConnectionImpl.java",
     "java/src/org/chromium/content/common/SurfaceWrapper.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentUiEventHandler.java b/content/public/android/java/src/org/chromium/content/browser/ContentUiEventHandler.java
index 3d50e19..be063a1 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ContentUiEventHandler.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ContentUiEventHandler.java
@@ -9,12 +9,12 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
+import org.chromium.base.UserData;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.content.browser.input.ImeAdapterImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.ViewEventSink.InternalAccessDelegate;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.device.gamepad.GamepadList;
@@ -25,7 +25,7 @@
  * content components.
  */
 @JNINamespace("content")
-public class ContentUiEventHandler {
+public class ContentUiEventHandler implements UserData {
     private final WebContentsImpl mWebContents;
     private InternalAccessDelegate mEventDelegate;
     private long mNativeContentUiEventHandler;
@@ -36,8 +36,8 @@
     }
 
     public static ContentUiEventHandler fromWebContents(WebContents webContents) {
-        return WebContentsUserData.fromWebContents(
-                webContents, ContentUiEventHandler.class, UserDataFactoryLazyHolder.INSTANCE);
+        return ((WebContentsImpl) webContents)
+                .getOrSetUserData(ContentUiEventHandler.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     public ContentUiEventHandler(WebContents webContents) {
diff --git a/content/public/android/java/src/org/chromium/content/browser/Gamepad.java b/content/public/android/java/src/org/chromium/content/browser/Gamepad.java
index 0da266c..3aa5047 100644
--- a/content/public/android/java/src/org/chromium/content/browser/Gamepad.java
+++ b/content/public/android/java/src/org/chromium/content/browser/Gamepad.java
@@ -6,9 +6,9 @@
 
 import android.content.Context;
 
+import org.chromium.base.UserData;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.device.gamepad.GamepadList;
 
@@ -16,7 +16,7 @@
  * Encapsulates component class {@link GamepadList} for use in content, with regards
  * to its state according to content being attached to/detached from window.
  */
-class Gamepad implements WindowEventObserver {
+class Gamepad implements WindowEventObserver, UserData {
     private final Context mContext;
 
     private static final class UserDataFactoryLazyHolder {
@@ -24,8 +24,8 @@
     }
 
     public static Gamepad from(WebContents webContents) {
-        return WebContentsUserData.fromWebContents(
-                webContents, Gamepad.class, UserDataFactoryLazyHolder.INSTANCE);
+        return ((WebContentsImpl) webContents)
+                .getOrSetUserData(Gamepad.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     public Gamepad(WebContents webContents) {
diff --git a/content/public/android/java/src/org/chromium/content/browser/GestureListenerManagerImpl.java b/content/public/android/java/src/org/chromium/content/browser/GestureListenerManagerImpl.java
index 69d72264..fb898d6 100644
--- a/content/public/android/java/src/org/chromium/content/browser/GestureListenerManagerImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/GestureListenerManagerImpl.java
@@ -10,6 +10,7 @@
 import org.chromium.base.ObserverList;
 import org.chromium.base.ObserverList.RewindableIterator;
 import org.chromium.base.TraceEvent;
+import org.chromium.base.UserData;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.blink_public.web.WebInputEventType;
@@ -17,7 +18,6 @@
 import org.chromium.content.browser.selection.SelectionPopupControllerImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.GestureListenerManager;
 import org.chromium.content_public.browser.GestureStateListener;
 import org.chromium.content_public.browser.ViewEventSink.InternalAccessDelegate;
@@ -29,11 +29,11 @@
  * Implementation of the interface {@link GestureListenerManager}. Manages
  * the {@link GestureStateListener} instances, and invokes them upon
  * notification of various events.
- * Instantiated object is held inside {@link WebContentsUserData} that is
- * managed by {@link WebContents}.
+ * Instantiated object is held inside {@link UserDataHost} that is managed by {@link WebContents}.
  */
 @JNINamespace("content")
-public class GestureListenerManagerImpl implements GestureListenerManager, WindowEventObserver {
+public class GestureListenerManagerImpl
+        implements GestureListenerManager, WindowEventObserver, UserData {
     private static final class UserDataFactoryLazyHolder {
         private static final UserDataFactory<GestureListenerManagerImpl> INSTANCE =
                 GestureListenerManagerImpl::new;
@@ -67,8 +67,9 @@
      *         Creates one if not present.
      */
     public static GestureListenerManagerImpl fromWebContents(WebContents webContents) {
-        return WebContentsUserData.fromWebContents(
-                webContents, GestureListenerManagerImpl.class, UserDataFactoryLazyHolder.INSTANCE);
+        return ((WebContentsImpl) webContents)
+                .getOrSetUserData(
+                        GestureListenerManagerImpl.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     public GestureListenerManagerImpl(WebContents webContents) {
@@ -258,7 +259,7 @@
     }
 
     @CalledByNative
-    private void onDestroy() {
+    private void onNativeDestroyed() {
         for (mIterator.rewind(); mIterator.hasNext();) mIterator.next().onDestroyed();
         mListeners.clear();
         mNativeGestureListenerManager = 0;
diff --git a/content/public/android/java/src/org/chromium/content/browser/JavascriptInjectorImpl.java b/content/public/android/java/src/org/chromium/content/browser/JavascriptInjectorImpl.java
index 5fa36f0..c041621 100644
--- a/content/public/android/java/src/org/chromium/content/browser/JavascriptInjectorImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/JavascriptInjectorImpl.java
@@ -6,10 +6,11 @@
 
 import android.util.Pair;
 
+import org.chromium.base.UserData;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
+import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.JavascriptInjector;
 import org.chromium.content_public.browser.WebContents;
 
@@ -23,7 +24,7 @@
  * Implementation class of the interface {@link JavascriptInjector}.
  */
 @JNINamespace("content")
-public class JavascriptInjectorImpl implements JavascriptInjector {
+public class JavascriptInjectorImpl implements JavascriptInjector, UserData {
     private static final class UserDataFactoryLazyHolder {
         private static final UserDataFactory<JavascriptInjectorImpl> INSTANCE =
                 JavascriptInjectorImpl::new;
@@ -39,8 +40,8 @@
      *         Creates one if not present.
      */
     public static JavascriptInjector fromWebContents(WebContents webContents) {
-        return WebContentsUserData.fromWebContents(
-                webContents, JavascriptInjectorImpl.class, UserDataFactoryLazyHolder.INSTANCE);
+        return ((WebContentsImpl) webContents)
+                .getOrSetUserData(JavascriptInjectorImpl.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     public JavascriptInjectorImpl(WebContents webContents) {
diff --git a/content/public/android/java/src/org/chromium/content/browser/JoystickHandler.java b/content/public/android/java/src/org/chromium/content/browser/JoystickHandler.java
index 39695e9..e76a872 100644
--- a/content/public/android/java/src/org/chromium/content/browser/JoystickHandler.java
+++ b/content/public/android/java/src/org/chromium/content/browser/JoystickHandler.java
@@ -7,9 +7,10 @@
 import android.view.InputDevice;
 import android.view.MotionEvent;
 
+import org.chromium.base.UserData;
 import org.chromium.content.browser.input.ImeAdapterImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.ImeEventObserver;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.EventForwarder;
@@ -17,7 +18,7 @@
 /**
  * Bridges content and joystick device event conversion and forwarding.
  */
-public class JoystickHandler implements ImeEventObserver {
+public class JoystickHandler implements ImeEventObserver, UserData {
     private final EventForwarder mEventForwarder;
 
     // Whether joystick scroll is enabled.  It's disabled when an editable field is focused.
@@ -28,8 +29,8 @@
     }
 
     public static JoystickHandler fromWebContents(WebContents webContents) {
-        return WebContentsUserData.fromWebContents(
-                webContents, JoystickHandler.class, UserDataFactoryLazyHolder.INSTANCE);
+        return ((WebContentsImpl) webContents)
+                .getOrSetUserData(JoystickHandler.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     /**
diff --git a/content/public/android/java/src/org/chromium/content/browser/PopupController.java b/content/public/android/java/src/org/chromium/content/browser/PopupController.java
index a905fa4..941a2d5 100644
--- a/content/public/android/java/src/org/chromium/content/browser/PopupController.java
+++ b/content/public/android/java/src/org/chromium/content/browser/PopupController.java
@@ -4,9 +4,10 @@
 
 package org.chromium.content.browser;
 
+import org.chromium.base.UserData;
 import org.chromium.content.browser.selection.SelectionPopupControllerImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.WebContents;
 
 import java.util.ArrayList;
@@ -15,7 +16,7 @@
 /**
  * Controls all the popup views on content view.
  */
-public class PopupController {
+public class PopupController implements UserData {
     /** Interface for popup views that expose a method for hiding itself. */
     public interface HideablePopup {
         /**
@@ -31,8 +32,8 @@
     private final List<HideablePopup> mHideablePopups = new ArrayList<>();
 
     public static PopupController fromWebContents(WebContents webContents) {
-        return WebContentsUserData.fromWebContents(
-                webContents, PopupController.class, UserDataFactoryLazyHolder.INSTANCE);
+        return ((WebContentsImpl) webContents)
+                .getOrSetUserData(PopupController.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     private PopupController(WebContents webContents) {}
diff --git a/content/public/android/java/src/org/chromium/content/browser/ViewEventSinkImpl.java b/content/public/android/java/src/org/chromium/content/browser/ViewEventSinkImpl.java
index bc8b5b27..bd7050d 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ViewEventSinkImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ViewEventSinkImpl.java
@@ -7,9 +7,9 @@
 import android.content.res.Configuration;
 
 import org.chromium.base.TraceEvent;
+import org.chromium.base.UserData;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.ViewEventSink;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.ViewAndroidDelegate;
@@ -18,7 +18,7 @@
 /**
  * Implementation of the interface {@link ViewEventSink}.
  */
-public final class ViewEventSinkImpl implements ViewEventSink, ActivityStateObserver {
+public final class ViewEventSinkImpl implements ViewEventSink, ActivityStateObserver, UserData {
     private final WebContentsImpl mWebContents;
 
     // Whether the container view has view-level focus.
@@ -40,8 +40,8 @@
     }
 
     public static ViewEventSinkImpl from(WebContents webContents) {
-        return WebContentsUserData.fromWebContents(
-                webContents, ViewEventSinkImpl.class, UserDataFactoryLazyHolder.INSTANCE);
+        return ((WebContentsImpl) webContents)
+                .getOrSetUserData(ViewEventSinkImpl.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     public ViewEventSinkImpl(WebContents webContents) {
diff --git a/content/public/android/java/src/org/chromium/content/browser/WindowEventObserverManager.java b/content/public/android/java/src/org/chromium/content/browser/WindowEventObserverManager.java
index 1cd90d9..4eb55db 100644
--- a/content/public/android/java/src/org/chromium/content/browser/WindowEventObserverManager.java
+++ b/content/public/android/java/src/org/chromium/content/browser/WindowEventObserverManager.java
@@ -8,9 +8,9 @@
 
 import org.chromium.base.ActivityState;
 import org.chromium.base.ObserverList;
+import org.chromium.base.UserData;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.display.DisplayAndroid;
@@ -19,7 +19,7 @@
 /**
  * Manages {@link WindowEventObserver} instances used for WebContents.
  */
-public final class WindowEventObserverManager implements DisplayAndroidObserver {
+public final class WindowEventObserverManager implements DisplayAndroidObserver, UserData {
     private final ObserverList<WindowEventObserver> mWindowEventObservers = new ObserverList<>();
 
     private WindowAndroid mWindowAndroid;
@@ -36,8 +36,9 @@
     }
 
     public static WindowEventObserverManager from(WebContents webContents) {
-        return WebContentsUserData.fromWebContents(
-                webContents, WindowEventObserverManager.class, UserDataFactoryLazyHolder.INSTANCE);
+        return ((WebContentsImpl) webContents)
+                .getOrSetUserData(
+                        WindowEventObserverManager.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     private WindowEventObserverManager(WebContents webContents) {
diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java
index 0cf23c8c..70c745903 100644
--- a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java
@@ -27,6 +27,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeProvider;
 
+import org.chromium.base.UserData;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -36,7 +37,6 @@
 import org.chromium.content.browser.accessibility.captioning.CaptioningController;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.AccessibilitySnapshotCallback;
 import org.chromium.content_public.browser.AccessibilitySnapshotNode;
 import org.chromium.content_public.browser.WebContents;
@@ -54,7 +54,8 @@
  */
 @JNINamespace("content")
 public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider
-        implements AccessibilityStateChangeListener, WebContentsAccessibility, WindowEventObserver {
+        implements AccessibilityStateChangeListener, WebContentsAccessibility, WindowEventObserver,
+                   UserData {
     // Constants from AccessibilityNodeInfo defined in the K SDK.
     private static final int ACTION_COLLAPSE = 0x00080000;
     private static final int ACTION_EXPAND = 0x00040000;
@@ -140,8 +141,9 @@
     }
 
     public static WebContentsAccessibilityImpl fromWebContents(WebContents webContents) {
-        return WebContentsUserData.fromWebContents(webContents, WebContentsAccessibilityImpl.class,
-                UserDataFactoryLazyHolder.INSTANCE);
+        return ((WebContentsImpl) webContents)
+                .getOrSetUserData(
+                        WebContentsAccessibilityImpl.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     protected WebContentsAccessibilityImpl(WebContents webContents) {
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java
index a80191f..09ec4c7 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java
@@ -31,6 +31,7 @@
 
 import org.chromium.base.Log;
 import org.chromium.base.TraceEvent;
+import org.chromium.base.UserData;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -43,7 +44,6 @@
 import org.chromium.content.browser.picker.InputDialogContainer;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.ImeAdapter;
 import org.chromium.content_public.browser.ImeEventObserver;
 import org.chromium.content_public.browser.InputMethodManagerWrapper;
@@ -81,7 +81,7 @@
  * lifetime of the object.
  */
 @JNINamespace("content")
-public class ImeAdapterImpl implements ImeAdapter, WindowEventObserver {
+public class ImeAdapterImpl implements ImeAdapter, WindowEventObserver, UserData {
     private static final String TAG = "cr_Ime";
     private static final boolean DEBUG_LOGS = false;
 
@@ -173,8 +173,8 @@
      * @return {@link ImeAdapter} object.
      */
     public static ImeAdapterImpl fromWebContents(WebContents webContents) {
-        return WebContentsUserData.fromWebContents(
-                webContents, ImeAdapterImpl.class, UserDataFactoryLazyHolder.INSTANCE);
+        return ((WebContentsImpl) webContents)
+                .getOrSetUserData(ImeAdapterImpl.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     /**
@@ -685,7 +685,7 @@
     }
 
     @CalledByNative
-    private void destroy() {
+    private void onNativeDestroyed() {
         resetAndHideKeyboard();
         mNativeImeAdapterAndroid = 0;
         mIsConnected = false;
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/SelectPopup.java b/content/public/android/java/src/org/chromium/content/browser/input/SelectPopup.java
index 87d6545..e05f3abd 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/SelectPopup.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/SelectPopup.java
@@ -8,6 +8,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import org.chromium.base.UserData;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -18,7 +19,6 @@
 import org.chromium.content.browser.accessibility.WebContentsAccessibilityImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.base.ViewAndroidDelegate;
@@ -31,8 +31,8 @@
  * Handles the popup UI for the lt&;select&gt; HTML tag support.
  */
 @JNINamespace("content")
-public class SelectPopup
-        implements HideablePopup, ViewAndroidDelegate.ContainerViewObserver, WindowEventObserver {
+public class SelectPopup implements HideablePopup, ViewAndroidDelegate.ContainerViewObserver,
+                                    WindowEventObserver, UserData {
     /** UI for Select popup. */
     public interface Ui {
         /**
@@ -62,8 +62,8 @@
      * @return {@link SelectPopup} object.
      */
     public static SelectPopup fromWebContents(WebContents webContents) {
-        return WebContentsUserData.fromWebContents(
-                webContents, SelectPopup.class, UserDataFactoryLazyHolder.INSTANCE);
+        return ((WebContentsImpl) webContents)
+                .getOrSetUserData(SelectPopup.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     @CalledByNative
@@ -172,7 +172,7 @@
     }
 
     @CalledByNative
-    private void destroy() {
+    private void onNativeDestroyed() {
         mNativeSelectPopup = 0;
     }
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/TextSuggestionHost.java b/content/public/android/java/src/org/chromium/content/browser/input/TextSuggestionHost.java
index e5c42a5f..fb9ec20 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/TextSuggestionHost.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/TextSuggestionHost.java
@@ -6,6 +6,7 @@
 
 import android.content.Context;
 
+import org.chromium.base.UserData;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -15,7 +16,6 @@
 import org.chromium.content.browser.WindowEventObserverManager;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.ViewAndroidDelegate;
 import org.chromium.ui.base.WindowAndroid;
@@ -26,7 +26,7 @@
  * the commands in that menu (by calling back to the C++ class).
  */
 @JNINamespace("content")
-public class TextSuggestionHost implements WindowEventObserver, HideablePopup {
+public class TextSuggestionHost implements WindowEventObserver, HideablePopup, UserData {
     private long mNativeTextSuggestionHost;
     private final WebContentsImpl mWebContents;
     private final Context mContext;
@@ -49,8 +49,8 @@
      * @return {@link TextSuggestionHost} object.
      */
     public static TextSuggestionHost fromWebContents(WebContents webContents) {
-        return WebContentsUserData.fromWebContents(
-                webContents, TextSuggestionHost.class, UserDataFactoryLazyHolder.INSTANCE);
+        return ((WebContentsImpl) webContents)
+                .getOrSetUserData(TextSuggestionHost.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     /**
@@ -200,7 +200,7 @@
     }
 
     @CalledByNative
-    private void destroy() {
+    private void onNativeDestroyed() {
         hidePopups();
         mNativeTextSuggestionHost = 0;
     }
diff --git a/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java b/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
index da52e15..5bcea30 100644
--- a/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
@@ -36,6 +36,7 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Log;
+import org.chromium.base.UserData;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -51,7 +52,6 @@
 import org.chromium.content.browser.input.ImeAdapterImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.ActionModeCallbackHelper;
 import org.chromium.content_public.browser.ImeEventObserver;
 import org.chromium.content_public.browser.SelectionClient;
@@ -73,7 +73,7 @@
 @TargetApi(Build.VERSION_CODES.M)
 public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
         implements ImeEventObserver, SelectionPopupController, WindowEventObserver, HideablePopup,
-                   ContainerViewObserver {
+                   ContainerViewObserver, UserData {
     private static final String TAG = "SelectionPopupCtlr"; // 20 char limit
 
     /**
@@ -199,8 +199,9 @@
      *         {@link #create()} is not called yet.
      */
     public static SelectionPopupControllerImpl fromWebContents(WebContents webContents) {
-        return WebContentsUserData.fromWebContents(webContents, SelectionPopupControllerImpl.class,
-                UserDataFactoryLazyHolder.INSTANCE);
+        return ((WebContentsImpl) webContents)
+                .getOrSetUserData(
+                        SelectionPopupControllerImpl.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     /**
diff --git a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java
index 1c262fb0..25856a5 100644
--- a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java
@@ -20,6 +20,8 @@
 import org.chromium.base.Callback;
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.UserData;
+import org.chromium.base.UserDataHost;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -51,9 +53,7 @@
 import org.chromium.ui.base.WindowAndroid;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.UUID;
 
 /**
@@ -188,7 +188,7 @@
     private boolean mInitialized;
 
     private static class WebContentsInternalsImpl implements WebContentsInternals {
-        public HashMap<Class<?>, WebContentsUserData> userDataMap;
+        public UserDataHost userDataHost;
         public ViewAndroidDelegate viewAndroidDelegate;
     }
 
@@ -216,7 +216,7 @@
 
         mInternalsHolder = internalsHolder;
         WebContentsInternalsImpl internals = new WebContentsInternalsImpl();
-        internals.userDataMap = new HashMap<>();
+        internals.userDataHost = new UserDataHost();
         mInternalsHolder.set(internals);
 
         mRenderCoordinates = new RenderCoordinatesImpl();
@@ -319,9 +319,13 @@
 
     @Override
     public void destroy() {
+        // Note that |WebContents.destroy| is not guaranteed to be invoked.
+        // Any resource release relying on this method will likely be leaked.
+
         if (!ThreadUtils.runningOnUiThread()) {
             throw new IllegalStateException("Attempting to destroy WebContents on non-UI thread");
         }
+
         if (mNativeWebContentsAndroid != 0) nativeDestroyWebContents(mNativeWebContentsAndroid);
     }
 
@@ -797,40 +801,42 @@
      *         not created yet, or {@code userDataFactory} is null, or the internal data
      *         storage is already garbage-collected.
      */
-    public <T> T getOrSetUserData(Class<T> key, UserDataFactory<T> userDataFactory) {
+    public <T extends UserData> T getOrSetUserData(
+            Class<T> key, UserDataFactory<T> userDataFactory) {
         // For tests that go without calling |initialize|.
         if (!mInitialized) return null;
 
-        Map<Class<?>, WebContentsUserData> userDataMap = getUserDataMap();
+        UserDataHost userDataHost = getUserDataHost();
 
         // Map can be null after WebView gets gc'ed on its way to destruction.
-        if (userDataMap == null) {
-            Log.e(TAG, "UserDataMap can't be found");
+        if (userDataHost == null) {
+            Log.e(TAG, "UserDataHost can't be found");
             return null;
         }
 
-        WebContentsUserData data = userDataMap.get(key);
+        T data = userDataHost.getUserData(key);
         if (data == null && userDataFactory != null) {
-            assert !userDataMap.containsKey(key); // Do not allow duplicated Data
+            assert userDataHost.getUserData(key) == null; // Do not allow overwriting
 
             T object = userDataFactory.create(this);
             assert key.isInstance(object);
-            userDataMap.put(key, new WebContentsUserData(object));
+
             // Retrieves from the map again to return null in case |setUserData| fails
             // to store the object.
-            data = userDataMap.get(key);
+            data = userDataHost.setUserData(key, object);
         }
-        return data != null ? key.cast(data.getObject()) : null;
+        return key.cast(data);
     }
 
     /**
-     * @return {@code UserDataMap} that contains internal user data. {@code null} if
-     *         the map is already gc'ed.
+     * @return {@code UserDataHost} that contains internal user data. {@code null} if
+     *         it is already gc'ed.
      */
-    private Map<Class<?>, WebContentsUserData> getUserDataMap() {
+    private UserDataHost getUserDataHost() {
+        if (mInternalsHolder == null) return null;
         WebContentsInternals internals = mInternalsHolder.get();
         if (internals == null) return null;
-        return ((WebContentsInternalsImpl) internals).userDataMap;
+        return ((WebContentsInternalsImpl) internals).userDataHost;
     }
 
     // WindowEventObserver
diff --git a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsUserData.java b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsUserData.java
deleted file mode 100644
index 0bf39a5..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsUserData.java
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser.webcontents;
-
-import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
-import org.chromium.content_public.browser.WebContents;
-
-/**
- * Holds an object to be stored in {@code userDataMap} in {@link WebContents} for those
- * classes that have the lifetime of {@link WebContents} without hanging directly onto it.
- * To create an object of a class {@code MyClass}, define a static method
- * {@code fromWebContents()} where you call:
- * <code>
- * WebContentsUserData.fromWebContents(webContents, MyClass.class, MyClass::new);
- * </code>
- *
- * {@code MyClass} should have a contstructor that accepts only one parameter:
- * <code>
- * public MyClass(WebContents webContents);
- * </code>
- */
-public final class WebContentsUserData {
-    private final Object mObject;
-
-    WebContentsUserData(Object object) {
-        mObject = object;
-    }
-
-    Object getObject() {
-        return mObject;
-    }
-
-    /**
-     * Looks up the generic object of the given web contents.
-     *
-     * @param webContents The web contents for which to lookup the object.
-     * @param key Class instance of the object used as the key.
-     * @param userDataFactory Factory that creates an object of the generic class. Creates a new
-     *        instance and returns it if not created yet.
-     * @return The object of the given web contents. Can be null if the object was not set,
-     *         the user data map is already garbage-collected, or {@link WebContents#initialize()}
-     *         is not called yet.
-     *
-     */
-    public static <T> T fromWebContents(
-            WebContents webContents, Class<T> key, UserDataFactory<T> userDataFactory) {
-        return ((WebContentsImpl) webContents).getOrSetUserData(key, userDataFactory);
-    }
-}
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/ContentTextSelectionTest.java b/content/public/android/javatests/src/org/chromium/content/browser/ContentTextSelectionTest.java
index 10dd92d..6039657 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/ContentTextSelectionTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/ContentTextSelectionTest.java
@@ -27,7 +27,6 @@
 import org.chromium.content.browser.input.ChromiumBaseInputConnection;
 import org.chromium.content.browser.input.ImeTestUtils;
 import org.chromium.content.browser.selection.SelectionPopupControllerImpl;
-import org.chromium.content_public.browser.ImeAdapter;
 import org.chromium.content_public.browser.SelectionClient;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.ContentJUnit4ClassRunner;
@@ -107,8 +106,7 @@
         mActivityTestRule.waitForActiveShellToBeDoneLoading();
 
         mWebContents = mActivityTestRule.getWebContents();
-        mSelectionPopupController =
-                SelectionPopupControllerImpl.fromWebContents(mActivityTestRule.getWebContents());
+        mSelectionPopupController = mActivityTestRule.getSelectionPopupController();
         waitForSelectActionBarVisible(false);
         waitForPastePopupStatus(false);
     }
@@ -635,8 +633,7 @@
 
     private CharSequence getTextBeforeCursor(final int length, final int flags) {
         final ChromiumBaseInputConnection connection =
-                (ChromiumBaseInputConnection) ImeAdapter
-                        .fromWebContents(mActivityTestRule.getWebContents())
+                (ChromiumBaseInputConnection) mActivityTestRule.getImeAdapter()
                         .getInputConnectionForTest();
         return ImeTestUtils.runBlockingOnHandlerNoException(
                 connection.getHandler(), new Callable<CharSequence>() {
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java
index 442515f4..9bd3b1c 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java
@@ -26,6 +26,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.base.test.util.UrlUtils;
@@ -34,6 +35,7 @@
 import org.chromium.content_shell_apk.ContentShellActivityTestRule;
 
 import java.lang.reflect.Method;
+import java.util.concurrent.ExecutionException;
 
 /**
  * Tests for WebContentsAccessibility. Actually tests WebContentsAccessibilityImpl that
@@ -64,8 +66,7 @@
      * returns something not null.
      */
     private AccessibilityNodeProvider enableAccessibilityAndWaitForNodeProvider() {
-        final WebContentsAccessibilityImpl wcax =
-                WebContentsAccessibilityImpl.fromWebContents(mActivityTestRule.getWebContents());
+        final WebContentsAccessibilityImpl wcax = mActivityTestRule.getWebContentsAccessibility();
         wcax.setState(true);
         wcax.setAccessibilityEnabledForTesting();
 
@@ -220,7 +221,8 @@
             }
         });
 
-        int virtualViewId = findNodeMatching(provider, View.NO_ID, matcher);
+        int virtualViewId = ThreadUtils.runOnUiThreadBlockingNoException(
+                () -> findNodeMatching(provider, View.NO_ID, matcher));
         Assert.assertNotEquals(View.NO_ID, virtualViewId);
         return virtualViewId;
     }
@@ -313,11 +315,11 @@
                     }
                 });
 
-        boolean result1 = provider.performAction(
-                editFieldVirtualViewId, AccessibilityNodeInfo.ACTION_FOCUS, null);
-        boolean result2 = provider.performAction(
-                editFieldVirtualViewId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
-        boolean result3 = provider.performAction(editFieldVirtualViewId,
+        boolean result1 = performActionOnUiThread(
+                provider, editFieldVirtualViewId, AccessibilityNodeInfo.ACTION_FOCUS, null);
+        boolean result2 = performActionOnUiThread(provider, editFieldVirtualViewId,
+                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+        boolean result3 = performActionOnUiThread(provider, editFieldVirtualViewId,
                 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
 
         // Assert all actions are performed successfully.
@@ -339,20 +341,26 @@
 
         // Simulate swipe left.
         for (int i = 3; i >= 0; i--) {
-            boolean result = provider.performAction(editFieldVirtualViewId,
+            boolean result = performActionOnUiThread(provider, editFieldVirtualViewId,
                     AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, args);
             // Assert that the index of the character traversed is correct.
             Assert.assertEquals(i, mostRecentCharIndex.value);
         }
         // Simulate swipe right.
         for (int i = 0; i <= 3; i++) {
-            boolean result = provider.performAction(editFieldVirtualViewId,
+            boolean result = performActionOnUiThread(provider, editFieldVirtualViewId,
                     AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, args);
             // Assert that the index of the character traversed is correct.
             Assert.assertEquals(i, mostRecentCharIndex.value);
         }
     }
 
+    private static boolean performActionOnUiThread(AccessibilityNodeProvider provider, int viewId,
+            int action, Bundle args) throws ExecutionException {
+        return ThreadUtils.runOnUiThreadBlocking(
+                () -> provider.performAction(viewId, action, args));
+    }
+
     /**
      * Text fields should expose ACTION_SET_TEXT
      */
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java b/content/public/android/javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java
index dfa0503..33601bd 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java
@@ -60,7 +60,7 @@
 
     public void setUpForUrl(String url) throws Exception {
         launchContentShellWithUrlSync(url);
-        mSelectionPopupController = SelectionPopupControllerImpl.fromWebContents(getWebContents());
+        mSelectionPopupController = getSelectionPopupController();
 
         final ImeAdapter imeAdapter = getImeAdapter();
         InputConnectionProvider provider =
@@ -126,10 +126,6 @@
         resetAllStates();
     }
 
-    SelectionPopupControllerImpl getSelectionPopupController() {
-        return mSelectionPopupController;
-    }
-
     TestCallbackHelperContainer getTestCallBackHelperContainer() {
         return mCallbackContainer;
     }
@@ -307,10 +303,6 @@
         });
     }
 
-    ImeAdapterImpl getImeAdapter() {
-        return ImeAdapterImpl.fromWebContents(getWebContents());
-    }
-
     ChromiumBaseInputConnection getInputConnection() {
         try {
             return ThreadUtils.runOnUiThreadBlocking(new Callable<ChromiumBaseInputConnection>() {
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/input/SelectPopupTest.java b/content/public/android/javatests/src/org/chromium/content/browser/input/SelectPopupTest.java
index 827e1db87..effd882 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/input/SelectPopupTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/input/SelectPopupTest.java
@@ -24,7 +24,6 @@
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer.OnPageFinishedHelper;
-import org.chromium.content_public.browser.test.util.WebContentsUtils;
 import org.chromium.content_shell_apk.ContentShellActivityTestRule;
 import org.chromium.content_shell_apk.ContentShellActivityTestRule.RerunWithUpdatedContainerView;
 
@@ -61,7 +60,7 @@
 
         @Override
         public boolean isSatisfied() {
-            return WebContentsUtils.isSelectPopupVisible(mActivityTestRule.getWebContents());
+            return mActivityTestRule.getSelectPopup().isVisibleForTesting();
         }
     }
 
@@ -72,7 +71,7 @@
 
         @Override
         public boolean isSatisfied() {
-            return !WebContentsUtils.isSelectPopupVisible(mActivityTestRule.getWebContents());
+            return !mActivityTestRule.getSelectPopup().isVisibleForTesting();
         }
     }
 
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/input/TextSuggestionMenuTest.java b/content/public/android/javatests/src/org/chromium/content/browser/input/TextSuggestionMenuTest.java
index 03e7e8d..31ad458 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/input/TextSuggestionMenuTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/input/TextSuggestionMenuTest.java
@@ -282,7 +282,7 @@
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                TextSuggestionHost.fromWebContents(webContents)
+                getTextSuggestionHost(webContents)
                         .getTextSuggestionsPopupWindowForTesting()
                         .dismiss();
             }
@@ -313,12 +313,11 @@
             @Override
             public boolean isSatisfied() {
                 SuggestionsPopupWindow suggestionsPopupWindow =
-                        TextSuggestionHost.fromWebContents(webContents)
+                        getTextSuggestionHost(webContents)
                                 .getTextSuggestionsPopupWindowForTesting();
 
                 SuggestionsPopupWindow spellCheckPopupWindow =
-                        TextSuggestionHost.fromWebContents(webContents)
-                                .getSpellCheckPopupWindowForTesting();
+                        getTextSuggestionHost(webContents).getSpellCheckPopupWindowForTesting();
 
                 return suggestionsPopupWindow == null && spellCheckPopupWindow == null;
             }
@@ -327,16 +326,14 @@
 
     private View getContentView(WebContents webContents) {
         SuggestionsPopupWindow suggestionsPopupWindow =
-                TextSuggestionHost.fromWebContents(webContents)
-                        .getTextSuggestionsPopupWindowForTesting();
+                getTextSuggestionHost(webContents).getTextSuggestionsPopupWindowForTesting();
 
         if (suggestionsPopupWindow != null) {
             return suggestionsPopupWindow.getContentViewForTesting();
         }
 
         SuggestionsPopupWindow spellCheckPopupWindow =
-                TextSuggestionHost.fromWebContents(webContents)
-                        .getSpellCheckPopupWindowForTesting();
+                getTextSuggestionHost(webContents).getSpellCheckPopupWindowForTesting();
 
         if (spellCheckPopupWindow != null) {
             return spellCheckPopupWindow.getContentViewForTesting();
@@ -345,6 +342,11 @@
         return null;
     }
 
+    private TextSuggestionHost getTextSuggestionHost(WebContents webContents) {
+        return ThreadUtils.runOnUiThreadBlockingNoException(
+                () -> TextSuggestionHost.fromWebContents(webContents));
+    }
+
     private ListView getSuggestionList(WebContents webContents) {
         View contentView = getContentView(webContents);
         return (ListView) contentView.findViewById(R.id.suggestionContainer);
diff --git a/content/public/app/mojo/content_browser_manifest.json b/content/public/app/mojo/content_browser_manifest.json
index 1f3224a0..3648f50 100644
--- a/content/public/app/mojo/content_browser_manifest.json
+++ b/content/public/app/mojo/content_browser_manifest.json
@@ -1,7 +1,10 @@
 {
   "name": "content_browser",
   "display_name": "Content (browser process)",
-  "options": { "can_connect_to_other_services_as_any_user": true },
+  "options": {
+    "can_connect_to_other_services_as_any_user": true,
+    "can_connect_to_other_services_with_any_instance_name": true
+  },
   "interface_provider_specs": {
     "service_manager:connector": {
       "provides": {
@@ -126,7 +129,6 @@
         ],
         "service_manager": [
           "service_manager:client_process",
-          "service_manager:instance_name",
           "service_manager:service_manager"
         ],
         "shape_detection": [
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 8e652e4b..cbd9987 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -672,7 +672,8 @@
     RenderFrameHost* frame,
     bool is_navigation,
     const GURL& url,
-    network::mojom::URLLoaderFactoryRequest* factory_request) {
+    network::mojom::URLLoaderFactoryRequest* factory_request,
+    bool* bypass_redirect_checks) {
   return false;
 }
 
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index bc2b501..32fb2b5 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -1138,6 +1138,9 @@
   // requests for the URLLoaderFactory. Otherwise |*factory_request| is left
   // unmodified and this must return |false|.
   //
+  // |bypass_redirect_checks| will be set to true when the embedder will be
+  // handling redirect security checks.
+  //
   // Always called on the UI thread and only when the Network Service is
   // enabled.
   //
@@ -1149,7 +1152,8 @@
       RenderFrameHost* frame,
       bool is_navigation,
       const GURL& url,
-      network::mojom::URLLoaderFactoryRequest* factory_request);
+      network::mojom::URLLoaderFactoryRequest* factory_request,
+      bool* bypass_redirect_checks);
 
   // Allows the embedder to intercept a WebSocket connection. |*request|
   // is always valid upon entry and MUST be valid upon return. The embedder
diff --git a/content/public/browser/devtools_manager_delegate.cc b/content/public/browser/devtools_manager_delegate.cc
index 5284bd8b..be5ffe2 100644
--- a/content/public/browser/devtools_manager_delegate.cc
+++ b/content/public/browser/devtools_manager_delegate.cc
@@ -23,7 +23,8 @@
   return std::string();
 }
 
-bool DevToolsManagerDelegate::AllowInspectingWebContents(WebContents* wc) {
+bool DevToolsManagerDelegate::AllowInspectingRenderFrameHost(
+    RenderFrameHost* rfh) {
   return true;
 }
 
diff --git a/content/public/browser/devtools_manager_delegate.h b/content/public/browser/devtools_manager_delegate.h
index 781af156..a31878d1 100644
--- a/content/public/browser/devtools_manager_delegate.h
+++ b/content/public/browser/devtools_manager_delegate.h
@@ -20,6 +20,7 @@
 namespace content {
 
 class DevToolsAgentHostClient;
+class RenderFrameHost;
 class WebContents;
 
 class CONTENT_EXPORT DevToolsManagerDelegate {
@@ -36,8 +37,8 @@
   // Returns DevToolsAgentHost title to use for given |web_contents| target.
   virtual std::string GetTargetDescription(WebContents* web_contents);
 
-  // Returns whether embedder allows to inspect given |web_contents|.
-  virtual bool AllowInspectingWebContents(WebContents* web_contents);
+  // Returns whether embedder allows to inspect given |rfh|.
+  virtual bool AllowInspectingRenderFrameHost(RenderFrameHost* rfh);
 
   // Returns all targets embedder would like to report as debuggable
   // remotely.
diff --git a/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/util/WebContentsUtils.java b/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/util/WebContentsUtils.java
index 48a09372..9f39714 100644
--- a/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/util/WebContentsUtils.java
+++ b/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/util/WebContentsUtils.java
@@ -8,9 +8,14 @@
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.content.browser.input.SelectPopup;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
+import org.chromium.content_public.browser.GestureListenerManager;
+import org.chromium.content_public.browser.ImeAdapter;
 import org.chromium.content_public.browser.RenderFrameHost;
+import org.chromium.content_public.browser.ViewEventSink;
 import org.chromium.content_public.browser.WebContents;
 
+import java.util.concurrent.ExecutionException;
+
 /**
  * Collection of test-only WebContents utilities.
  */
@@ -55,6 +60,43 @@
             ((WebContentsImpl) webContents).simulateRendererKilledForTesting(wasOomProtected));
     }
 
+    /**
+     * Returns {@link ImeAdapter} instance associated with a given {@link WebContents}.
+     * @param webContents The WebContents in use.
+     */
+    public static ImeAdapter getImeAdapter(WebContents webContents) {
+        try {
+            return ThreadUtils.runOnUiThreadBlocking(() -> ImeAdapter.fromWebContents(webContents));
+        } catch (ExecutionException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns {@link GestureListenerManager} instance associated with a given {@link WebContents}.
+     * @param webContents The WebContents in use.
+     */
+    public static GestureListenerManager getGestureListenerManager(WebContents webContents) {
+        try {
+            return ThreadUtils.runOnUiThreadBlocking(
+                    () -> GestureListenerManager.fromWebContents(webContents));
+        } catch (ExecutionException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns {@link ViewEventSink} instance associated with a given {@link WebContents}.
+     * @param webContents The WebContents in use.
+     */
+    public static ViewEventSink getViewEventSink(WebContents webContents) {
+        try {
+            return ThreadUtils.runOnUiThreadBlocking(() -> ViewEventSink.from(webContents));
+        } catch (ExecutionException e) {
+            return null;
+        }
+    }
+
     private static native void nativeReportAllFrameSubmissions(
             WebContents webContents, boolean enabled);
     private static native RenderFrameHost nativeGetFocusedFrame(WebContents webContents);
diff --git a/content/public/test/fake_service_worker_context.cc b/content/public/test/fake_service_worker_context.cc
index 14dec324..77df13c0 100644
--- a/content/public/test/fake_service_worker_context.cc
+++ b/content/public/test/fake_service_worker_context.cc
@@ -89,8 +89,9 @@
 
 void FakeServiceWorkerContext::StopAllServiceWorkersForOrigin(
     const GURL& origin) {
-  NOTREACHED();
+  stop_all_service_workers_for_origin_calls_.push_back(origin);
 }
+
 void FakeServiceWorkerContext::StopAllServiceWorkers(base::OnceClosure) {
   NOTREACHED();
 }
diff --git a/content/public/test/fake_service_worker_context.h b/content/public/test/fake_service_worker_context.h
index 4c9243d..00b6651 100644
--- a/content/public/test/fake_service_worker_context.h
+++ b/content/public/test/fake_service_worker_context.h
@@ -79,12 +79,18 @@
     return start_service_worker_and_dispatch_long_running_message_calls_;
   };
 
+  const std::vector<GURL>& stop_all_service_workers_for_origin_calls() {
+    return stop_all_service_workers_for_origin_calls_;
+  }
+
  private:
   bool start_service_worker_for_navigation_hint_called_ = false;
 
   std::vector<StartServiceWorkerAndDispatchLongRunningMessageArgs>
       start_service_worker_and_dispatch_long_running_message_calls_;
 
+  std::vector<GURL> stop_all_service_workers_for_origin_calls_;
+
   base::ObserverList<ServiceWorkerContextObserver, true>::Unchecked observers_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeServiceWorkerContext);
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 7fd44e3..92563d7 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -3731,8 +3731,17 @@
 
 std::unique_ptr<blink::WebServiceWorkerProvider>
 RenderFrameImpl::CreateServiceWorkerProvider() {
+  // Bail-out if we are about to be navigated away.
+  // We check that DocumentLoader is attached since:
+  // - This serves as the signal since the DocumentLoader is detached in
+  //   FrameLoader::PrepareForCommit().
+  // - Creating ServiceWorkerProvider in
+  //   RenderFrameImpl::CreateServiceWorkerProvider() assumes that there is a
+  //   DocumentLoader attached to the frame.
+  if (!frame_->GetDocumentLoader())
+    return nullptr;
+
   // At this point we should have non-null data source.
-  DCHECK(frame_->GetDocumentLoader());
   if (!ChildThreadImpl::current())
     return nullptr;  // May be null in some tests.
   ServiceWorkerNetworkProvider* provider =
diff --git a/content/shell/android/javatests/src/org/chromium/content_shell_apk/ContentShellActivityTestRule.java b/content/shell/android/javatests/src/org/chromium/content_shell_apk/ContentShellActivityTestRule.java
index b7674a7b..ca9390c 100644
--- a/content/shell/android/javatests/src/org/chromium/content_shell_apk/ContentShellActivityTestRule.java
+++ b/content/shell/android/javatests/src/org/chromium/content_shell_apk/ContentShellActivityTestRule.java
@@ -25,6 +25,10 @@
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.content.browser.RenderCoordinatesImpl;
+import org.chromium.content.browser.accessibility.WebContentsAccessibilityImpl;
+import org.chromium.content.browser.input.ImeAdapterImpl;
+import org.chromium.content.browser.input.SelectPopup;
+import org.chromium.content.browser.selection.SelectionPopupControllerImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content_public.browser.JavascriptInjector;
 import org.chromium.content_public.browser.LoadUrlParams;
@@ -157,12 +161,59 @@
     }
 
     /**
+     * Returns the {@link SelectionPopupControllerImpl} of the WebContents.
+     */
+    public SelectionPopupControllerImpl getSelectionPopupController() {
+        try {
+            return ThreadUtils.runOnUiThreadBlocking(() -> {
+                return SelectionPopupControllerImpl.fromWebContents(
+                        getActivity().getActiveShell().getWebContents());
+            });
+        } catch (ExecutionException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the {@link ImeAdapterImpl} of the WebContents.
+     */
+    public ImeAdapterImpl getImeAdapter() {
+        try {
+            return ThreadUtils.runOnUiThreadBlocking(
+                    () -> ImeAdapterImpl.fromWebContents(getWebContents()));
+        } catch (ExecutionException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the {@link SelectPopup} of the WebContents.
+     */
+    public SelectPopup getSelectPopup() {
+        try {
+            return ThreadUtils.runOnUiThreadBlocking(
+                    () -> SelectPopup.fromWebContents(getWebContents()));
+        } catch (ExecutionException e) {
+            return null;
+        }
+    }
+
+    public WebContentsAccessibilityImpl getWebContentsAccessibility() {
+        try {
+            return ThreadUtils.runOnUiThreadBlocking(
+                    () -> WebContentsAccessibilityImpl.fromWebContents(getWebContents()));
+        } catch (ExecutionException e) {
+            return null;
+        }
+    }
+
+    /**
      * Returns the RenderCoordinates of the WebContents.
      */
     public RenderCoordinatesImpl getRenderCoordinates() {
         try {
             return ThreadUtils.runOnUiThreadBlocking(
-                    () -> { return ((WebContentsImpl) getWebContents()).getRenderCoordinates(); });
+                    () -> ((WebContentsImpl) getWebContents()).getRenderCoordinates());
         } catch (ExecutionException e) {
             return null;
         }
diff --git a/content/test/data/accessibility/event/live-region-add-expected-win.txt b/content/test/data/accessibility/event/live-region-add-expected-win.txt
index a57cfad..2768682f 100644
--- a/content/test/data/accessibility/event/live-region-add-expected-win.txt
+++ b/content/test/data/accessibility/event/live-region-add-expected-win.txt
@@ -1,3 +1,4 @@
+EVENT_OBJECT_LIVEREGIONCHANGED on <div#live> role=DIV
 EVENT_OBJECT_REORDER on <div#live> role=DIV
 EVENT_OBJECT_SHOW on <p> role=P
 IA2_EVENT_TEXT_INSERTED on <div#live> role=DIV new_text={'<obj>' start=6 end=7}
diff --git a/content/test/data/accessibility/event/live-region-change-expected-win.txt b/content/test/data/accessibility/event/live-region-change-expected-win.txt
index aa60a845..d85c9ec 100644
--- a/content/test/data/accessibility/event/live-region-change-expected-win.txt
+++ b/content/test/data/accessibility/event/live-region-change-expected-win.txt
@@ -1,2 +1,3 @@
+EVENT_OBJECT_LIVEREGIONCHANGED on <div#live> role=DIV
 IA2_EVENT_TEXT_INSERTED on <div#live> role=DIV new_text={'After' start=0 end=5}
 IA2_EVENT_TEXT_REMOVED on <div#live> role=DIV old_text={'Before' start=0 end=6}
diff --git a/extensions/shell/browser/shell_content_browser_client.cc b/extensions/shell/browser/shell_content_browser_client.cc
index 2a32fe0..321a89e9 100644
--- a/extensions/shell/browser/shell_content_browser_client.cc
+++ b/extensions/shell/browser/shell_content_browser_client.cc
@@ -291,12 +291,16 @@
     content::RenderFrameHost* frame,
     bool is_navigation,
     const GURL& url,
-    network::mojom::URLLoaderFactoryRequest* factory_request) {
+    network::mojom::URLLoaderFactoryRequest* factory_request,
+    bool* bypass_redirect_checks) {
   auto* web_request_api =
       extensions::BrowserContextKeyedAPIFactory<extensions::WebRequestAPI>::Get(
           browser_context);
-  return web_request_api->MaybeProxyURLLoaderFactory(frame, is_navigation,
-                                                     factory_request);
+  bool use_proxy = web_request_api->MaybeProxyURLLoaderFactory(
+      frame, is_navigation, factory_request);
+  if (bypass_redirect_checks)
+    *bypass_redirect_checks = use_proxy;
+  return use_proxy;
 }
 
 bool ShellContentBrowserClient::HandleExternalProtocol(
diff --git a/extensions/shell/browser/shell_content_browser_client.h b/extensions/shell/browser/shell_content_browser_client.h
index 1176623..7b419386 100644
--- a/extensions/shell/browser/shell_content_browser_client.h
+++ b/extensions/shell/browser/shell_content_browser_client.h
@@ -78,7 +78,8 @@
       content::RenderFrameHost* frame_host,
       bool is_navigation,
       const GURL& url,
-      network::mojom::URLLoaderFactoryRequest* factory_request) override;
+      network::mojom::URLLoaderFactoryRequest* factory_request,
+      bool* bypass_redirect_checks) override;
   bool HandleExternalProtocol(
       const GURL& url,
       content::ResourceRequestInfo::WebContentsGetter web_contents_getter,
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
index cfc7bad..8fe1155 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
@@ -67,7 +67,8 @@
 @property(nonatomic, strong) GridLayout* defaultLayout;
 // The layout used while the grid is being reordered.
 @property(nonatomic, strong) UICollectionViewLayout* reorderingLayout;
-
+// YES if, when reordering is enabled, the order of the cells has changed.
+@property(nonatomic, assign) BOOL hasChangedOrder;
 @end
 
 @implementation GridViewController
@@ -88,6 +89,7 @@
 @synthesize emptyStateAnimator = _emptyStateAnimator;
 @synthesize defaultLayout = _defaultLayout;
 @synthesize reorderingLayout = _reorderingLayout;
+@synthesize hasChangedOrder = _hasChangedOrder;
 
 - (instancetype)init {
   if (self = [super init]) {
@@ -286,7 +288,7 @@
   GridItem* item = self.items[source];
   [self.items removeObjectAtIndex:source];
   [self.items insertObject:item atIndex:destination];
-
+  self.hasChangedOrder = YES;
   [self.delegate gridViewController:self
                   didMoveItemWithID:item.identifier
                             toIndex:destination];
@@ -587,6 +589,8 @@
       if (!moving) {
         gesture.enabled = NO;
       } else {
+        base::RecordAction(
+            base::UserMetricsAction("MobileTabGridBeganReordering"));
         CGPoint cellCenter =
             [self.collectionView cellForItemAtIndexPath:path].center;
         self.itemReorderTouchPoint =
@@ -624,12 +628,14 @@
                                             animated:YES];
       }];
       [self.collectionView endInteractiveMovement];
+      [self recordInteractiveReordering];
       [CATransaction commit];
       break;
     }
     case UIGestureRecognizerStateCancelled:
       self.itemReorderTouchPoint = CGPointZero;
       [self.collectionView cancelInteractiveMovement];
+      [self recordInteractiveReordering];
       [self.collectionView setCollectionViewLayout:self.defaultLayout
                                           animated:YES];
       // Re-enable cancelled gesture.
@@ -649,4 +655,14 @@
   [self.collectionView updateInteractiveMovementTargetPosition:targetLocation];
 }
 
+- (void)recordInteractiveReordering {
+  if (self.hasChangedOrder) {
+    base::RecordAction(base::UserMetricsAction("MobileTabGridReordered"));
+  } else {
+    base::RecordAction(
+        base::UserMetricsAction("MobileTabGridEndedWithoutReordering"));
+  }
+  self.hasChangedOrder = NO;
+}
+
 @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 8b7deb17..974c3d5c 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
@@ -1245,14 +1245,20 @@
 - (void)closeAllButtonTapped:(id)sender {
   switch (self.currentPage) {
     case TabGridPageIncognitoTabs:
+      base::RecordAction(
+          base::UserMetricsAction("MobileTabGridCloseAllIncognitoTabs"));
       [self.incognitoTabsDelegate closeAllItems];
       break;
     case TabGridPageRegularTabs:
       DCHECK_EQ(self.undoCloseAllAvailable,
                 self.regularTabsViewController.gridEmpty);
       if (self.undoCloseAllAvailable) {
+        base::RecordAction(
+            base::UserMetricsAction("MobileTabGridUndoCloseAllRegularTabs"));
         [self.regularTabsDelegate undoCloseAllItems];
       } else {
+        base::RecordAction(
+            base::UserMetricsAction("MobileTabGridCloseAllRegularTabs"));
         [self.regularTabsDelegate saveAndCloseAllItems];
       }
       self.undoCloseAllAvailable = !self.undoCloseAllAvailable;
diff --git a/ios/web/public/app/mojo/web_browser_manifest.json b/ios/web/public/app/mojo/web_browser_manifest.json
index 1dc73c6..0cdaa6e 100644
--- a/ios/web/public/app/mojo/web_browser_manifest.json
+++ b/ios/web/public/app/mojo/web_browser_manifest.json
@@ -1,7 +1,10 @@
 {
   "name": "web_browser",
   "display_name": "Web",
-  "options": { "can_connect_to_other_services_as_any_user": true },
+  "options": {
+    "can_connect_to_other_services_as_any_user": true,
+    "can_connect_to_other_services_with_any_instance_name": true
+  },
   "interface_provider_specs": {
     "service_manager:connector": {
       "provides": {
@@ -13,7 +16,6 @@
         "*": [ "app" ],
         "service_manager": [
           "service_manager:client_process",
-          "service_manager:instance_name",
           "service_manager:service_manager"
         ]
       }
diff --git a/ios/web/public/web_state/web_frame.h b/ios/web/public/web_state/web_frame.h
index 7fe0be10..7907a9f 100644
--- a/ios/web/public/web_state/web_frame.h
+++ b/ios/web/public/web_state/web_frame.h
@@ -9,10 +9,10 @@
 
 #include "base/macros.h"
 #include "base/supports_user_data.h"
+#include "base/time/time.h"
 #include "url/gurl.h"
 
 namespace base {
-class TimeDelta;
 class Value;
 }
 
diff --git a/ios/web/web_state/web_frame_impl.h b/ios/web/web_state/web_frame_impl.h
index d6474774..75f213d 100644
--- a/ios/web/web_state/web_frame_impl.h
+++ b/ios/web/web_state/web_frame_impl.h
@@ -36,17 +36,12 @@
 
   // The associated web state.
   WebState* GetWebState();
-  // Detaches the receiver from the associated  WebState.
-  void DetachFromWebState();
 
   // WebFrame implementation
   std::string GetFrameId() const override;
   bool IsMainFrame() const override;
   GURL GetSecurityOrigin() const override;
 
-  // WebStateObserver implementation
-  void WebStateDestroyed(web::WebState* web_state) override;
-
   bool CallJavaScriptFunction(
       const std::string& name,
       const std::vector<base::Value>& parameters) override;
@@ -56,6 +51,9 @@
       base::OnceCallback<void(const base::Value*)> callback,
       base::TimeDelta timeout) override;
 
+  // WebStateObserver implementation
+  void WebStateDestroyed(web::WebState* web_state) override;
+
  private:
   // Calls the JavaScript function |name| in the frame context in the same
   // manner as the inherited CallJavaScriptFunction functions. If
@@ -65,6 +63,11 @@
                               const std::vector<base::Value>& parameters,
                               bool reply_with_result);
 
+  // Detaches the receiver from the associated  WebState.
+  void DetachFromWebState();
+  // Returns the script command name to use for this WebFrame.
+  const std::string GetScriptCommandPrefix();
+
   // A structure to store the callbacks associated with the
   // |CallJavaScriptFunction| requests.
   typedef base::CancelableOnceCallback<void(void)> TimeoutCallback;
diff --git a/ios/web/web_state/web_frame_impl.mm b/ios/web/web_state/web_frame_impl.mm
index 12e9687..1ff3518 100644
--- a/ios/web/web_state/web_frame_impl.mm
+++ b/ios/web/web_state/web_frame_impl.mm
@@ -43,11 +43,10 @@
   DCHECK(web_state);
   web_state->AddObserver(this);
 
-  const std::string command_prefix = kJavaScriptReplyCommandPrefix + frame_id;
   web_state->AddScriptCommandCallback(
       base::BindRepeating(&WebFrameImpl::OnJavaScriptReply,
                           base::Unretained(this), base::Unretained(web_state)),
-      command_prefix);
+      GetScriptCommandPrefix());
 }
 
 WebFrameImpl::~WebFrameImpl() {
@@ -55,16 +54,6 @@
   DetachFromWebState();
 }
 
-void WebFrameImpl::DetachFromWebState() {
-  if (web_state_) {
-    const std::string command_prefix =
-        kJavaScriptReplyCommandPrefix + frame_id_;
-    web_state_->RemoveScriptCommandCallback(command_prefix);
-    web_state_->RemoveObserver(this);
-    web_state_ = nullptr;
-  }
-}
-
 WebState* WebFrameImpl::GetWebState() {
   return web_state_;
 }
@@ -126,7 +115,7 @@
 bool WebFrameImpl::CallJavaScriptFunction(
     const std::string& name,
     const std::vector<base::Value>& parameters) {
-  return CallJavaScriptFunction(name, parameters, /*replyWithResult=*/false);
+  return CallJavaScriptFunction(name, parameters, /*reply_with_result=*/false);
 }
 
 bool WebFrameImpl::CallJavaScriptFunction(
@@ -145,7 +134,7 @@
 
   base::PostDelayedTaskWithTraits(FROM_HERE, {web::WebThread::UI},
                                   timeout_callback_ptr->callback(), timeout);
-  return CallJavaScriptFunction(name, parameters, /*replyWithResult=*/true);
+  return CallJavaScriptFunction(name, parameters, /*reply_with_result=*/true);
 }
 
 void WebFrameImpl::CancelRequest(int message_id) {
@@ -182,8 +171,7 @@
   }
 
   const std::string command_string = command->GetString();
-  if (command_string !=
-      (std::string(kJavaScriptReplyCommandPrefix) + frame_id_ + ".reply")) {
+  if (command_string != (GetScriptCommandPrefix() + ".reply")) {
     NOTREACHED();
     return false;
   }
@@ -211,6 +199,18 @@
   return true;
 }
 
+void WebFrameImpl::DetachFromWebState() {
+  if (web_state_) {
+    web_state_->RemoveScriptCommandCallback(GetScriptCommandPrefix());
+    web_state_->RemoveObserver(this);
+    web_state_ = nullptr;
+  }
+}
+
+const std::string WebFrameImpl::GetScriptCommandPrefix() {
+  return kJavaScriptReplyCommandPrefix + frame_id_;
+}
+
 void WebFrameImpl::WebStateDestroyed(web::WebState* web_state) {
   CancelPendingRequests();
   DetachFromWebState();
@@ -221,6 +221,6 @@
     std::unique_ptr<TimeoutCallback> timeout)
     : completion(std::move(completion)), timeout_callback(std::move(timeout)) {}
 
-WebFrameImpl::RequestCallbacks::~RequestCallbacks(){};
+WebFrameImpl::RequestCallbacks::~RequestCallbacks() {}
 
 }  // namespace web
diff --git a/ios/web/web_state/web_frame_impl_inttest.mm b/ios/web/web_state/web_frame_impl_inttest.mm
index cbcc0b6..a028b50e 100644
--- a/ios/web/web_state/web_frame_impl_inttest.mm
+++ b/ios/web/web_state/web_frame_impl_inttest.mm
@@ -165,7 +165,7 @@
     return;
   }
 
-  ASSERT_TRUE(LoadHtml("<p>>"));
+  ASSERT_TRUE(LoadHtml("<p>"));
 
   WebFrame* main_frame = GetMainWebFrameForWebState(web_state());
   ASSERT_TRUE(main_frame);
@@ -220,13 +220,13 @@
     return called;
   }));
 
-  EXPECT_EQ(1, [ExecuteJavaScript(@"sensitiveValue") intValue]);
+  EXPECT_NSEQ(@1, ExecuteJavaScript(@"sensitiveValue"));
 
   ExecuteJavaScript(@"replayInterceptedMessage()");
 
   // Value should not increase because replaying message should not re-execute
   // the called function.
-  EXPECT_EQ(1, [ExecuteJavaScript(@"sensitiveValue") intValue]);
+  EXPECT_NSEQ(@1, ExecuteJavaScript(@"sensitiveValue"));
 }
 
 }  // namespace web
diff --git a/media/capabilities/in_memory_video_decode_stats_db_impl.cc b/media/capabilities/in_memory_video_decode_stats_db_impl.cc
index 1612245..0da134b 100644
--- a/media/capabilities/in_memory_video_decode_stats_db_impl.cc
+++ b/media/capabilities/in_memory_video_decode_stats_db_impl.cc
@@ -15,6 +15,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/task/post_task.h"
 #include "media/base/bind_to_current_loop.h"
+#include "media/capabilities/video_decode_stats_db_impl.h"
 #include "media/capabilities/video_decode_stats_db_provider.h"
 
 namespace media {
@@ -41,6 +42,9 @@
 
 InMemoryVideoDecodeStatsDBImpl::~InMemoryVideoDecodeStatsDBImpl() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (seed_db_)
+    seed_db_->set_dependent_db(nullptr);
 }
 
 void InMemoryVideoDecodeStatsDBImpl::Initialize(InitializeCB init_cb) {
@@ -69,8 +73,13 @@
   DVLOG(2) << __func__ << (db ? " has" : " null") << " seed db";
 
   db_init_ = true;
+
+  CHECK(!seed_db_) << __func__ << " Already have a seed_db_?";
   seed_db_ = db;
 
+  if (seed_db_)
+    seed_db_->set_dependent_db(this);
+
   // Hard coding success = true. There are rare cases (e.g. disk corruption)
   // where an incognito profile may fail to acquire a reference to the base
   // profile's DB. But this just means incognito is in the same boat as guest
diff --git a/media/capabilities/in_memory_video_decode_stats_db_unittest.cc b/media/capabilities/in_memory_video_decode_stats_db_unittest.cc
index 6f6264bf..9083cf51 100644
--- a/media/capabilities/in_memory_video_decode_stats_db_unittest.cc
+++ b/media/capabilities/in_memory_video_decode_stats_db_unittest.cc
@@ -8,8 +8,10 @@
 #include "base/bind_helpers.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
+#include "base/test/gtest_util.h"
 #include "base/test/scoped_task_environment.h"
 #include "media/capabilities/in_memory_video_decode_stats_db_impl.h"
+#include "media/capabilities/video_decode_stats_db_impl.h"
 #include "media/capabilities/video_decode_stats_db_provider.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -388,4 +390,14 @@
   scoped_task_environment_.RunUntilIdle();
 }
 
+TEST_F(SeededInMemoryDBTest, SeedDBTearDownRace) {
+  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
+
+  // Establish depends-on connection from InMemoryDB to SeedDB.
+  InitializeEmptyDB();
+
+  // Destroying the seed-db dependency should trigger a crash.
+  EXPECT_CHECK_DEATH(seed_db_.reset());
+}
+
 }  // namespace media
diff --git a/media/capabilities/video_decode_stats_db.cc b/media/capabilities/video_decode_stats_db.cc
index 2978a28f..d8618911 100644
--- a/media/capabilities/video_decode_stats_db.cc
+++ b/media/capabilities/video_decode_stats_db.cc
@@ -95,4 +95,9 @@
   return !(x == y);
 }
 
+VideoDecodeStatsDB::~VideoDecodeStatsDB() {
+  // Tracking down crash. See https://crbug/865321.
+  CHECK(!dependent_db_) << __func__ << " Destroying before dependent_db_!";
+}
+
 }  // namespace media
diff --git a/media/capabilities/video_decode_stats_db.h b/media/capabilities/video_decode_stats_db.h
index 7656cc1..899f4fda 100644
--- a/media/capabilities/video_decode_stats_db.h
+++ b/media/capabilities/video_decode_stats_db.h
@@ -66,7 +66,7 @@
     uint64_t frames_decoded_power_efficient;
   };
 
-  virtual ~VideoDecodeStatsDB() = default;
+  virtual ~VideoDecodeStatsDB();
 
   // Run asynchronous initialization of database. Initialization must complete
   // before calling other APIs. Initialization must be RE-RUN after calling
@@ -97,6 +97,22 @@
   // DO NOT use the database until |callback| is run. When finished, users must
   // RE-RUN Initialize() before performing further I/O.
   virtual void DestroyStats(base::OnceClosure destroy_done_cb) = 0;
+
+  // Tracking down root cause of crash probable UAF (https://crbug/865321).
+  // We will CHECK if a |dependent_db_| is found to be set during destruction.
+  // Dependent DB should always be destroyed and unhooked before |this|.
+  void set_dependent_db(VideoDecodeStatsDB* dependent) {
+    // One of these should be non-null.
+    CHECK(!dependent_db_ || !dependent);
+    // They shouldn't already match.
+    CHECK(dependent_db_ != dependent);
+
+    dependent_db_ = dependent;
+  }
+
+ private:
+  // See set_dependent_db().
+  VideoDecodeStatsDB* dependent_db_ = nullptr;
 };
 
 MEDIA_EXPORT bool operator==(const VideoDecodeStatsDB::VideoDescKey& x,
diff --git a/media/capabilities/video_decode_stats_db_unittest.cc b/media/capabilities/video_decode_stats_db_unittest.cc
index 6bb83107..cd69e9c 100644
--- a/media/capabilities/video_decode_stats_db_unittest.cc
+++ b/media/capabilities/video_decode_stats_db_unittest.cc
@@ -22,6 +22,7 @@
 using leveldb_proto::test::FakeDB;
 using testing::Pointee;
 using testing::Eq;
+using testing::_;
 
 namespace media {
 
diff --git a/media/gpu/h264_decoder.cc b/media/gpu/h264_decoder.cc
index 260849b..ed0290b 100644
--- a/media/gpu/h264_decoder.cc
+++ b/media/gpu/h264_decoder.cc
@@ -13,6 +13,7 @@
 #include "base/optional.h"
 #include "base/stl_util.h"
 #include "media/gpu/h264_decoder.h"
+#include "media/video/h264_level_limits.h"
 
 namespace media {
 
@@ -964,42 +965,6 @@
   return true;
 }
 
-static int LevelToMaxDpbMbs(int level) {
-  // See table A-1 in spec.
-  switch (level) {
-    case 10:
-      return 396;
-    case 11:
-      return 900;
-    case 12:  //  fallthrough
-    case 13:  //  fallthrough
-    case 20:
-      return 2376;
-    case 21:
-      return 4752;
-    case 22:  //  fallthrough
-    case 30:
-      return 8100;
-    case 31:
-      return 18000;
-    case 32:
-      return 20480;
-    case 40:  //  fallthrough
-    case 41:
-      return 32768;
-    case 42:
-      return 34816;
-    case 50:
-      return 110400;
-    case 51:  //  fallthrough
-    case 52:
-      return 184320;
-    default:
-      DVLOG(1) << "Invalid codec level (" << level << ")";
-      return 0;
-  }
-}
-
 bool H264Decoder::UpdateMaxNumReorderFrames(const H264SPS* sps) {
   if (sps->vui_parameters_present_flag && sps->bitstream_restriction_flag) {
     max_num_reorder_frames_ =
@@ -1066,8 +1031,17 @@
     return false;
   }
 
-  int level = sps->level_idc;
-  int max_dpb_mbs = LevelToMaxDpbMbs(level);
+  // Spec A.3.1 and A.3.2
+  // For Baseline, Constrained Baseline and Main profile, the indicated level is
+  // Level 1b if level_idc is equal to 11 and constraint_set3_flag is equal to 1
+  uint8_t level = base::checked_cast<uint8_t>(sps->level_idc);
+  if ((sps->profile_idc == H264SPS::kProfileIDCBaseline ||
+       sps->profile_idc == H264SPS::kProfileIDCConstrainedBaseline ||
+       sps->profile_idc == H264SPS::kProfileIDCMain) &&
+      level == 11 && sps->constraint_set3_flag) {
+    level = 9;  // Level 1b
+  }
+  int max_dpb_mbs = base::checked_cast<int>(H264LevelToMaxDpbMbs(level));
   if (max_dpb_mbs == 0)
     return false;
 
diff --git a/media/gpu/v4l2/v4l2_device.cc b/media/gpu/v4l2/v4l2_device.cc
index c548415..8f7b6de 100644
--- a/media/gpu/v4l2/v4l2_device.cc
+++ b/media/gpu/v4l2/v4l2_device.cc
@@ -226,6 +226,79 @@
 }
 
 // static
+int32_t V4L2Device::VideoCodecProfileToV4L2H264Profile(
+    VideoCodecProfile profile) {
+  switch (profile) {
+    case H264PROFILE_BASELINE:
+      return V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE;
+    case H264PROFILE_MAIN:
+      return V4L2_MPEG_VIDEO_H264_PROFILE_MAIN;
+    case H264PROFILE_EXTENDED:
+      return V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED;
+    case H264PROFILE_HIGH:
+      return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH;
+    case H264PROFILE_HIGH10PROFILE:
+      return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10;
+    case H264PROFILE_HIGH422PROFILE:
+      return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422;
+    case H264PROFILE_HIGH444PREDICTIVEPROFILE:
+      return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE;
+    case H264PROFILE_SCALABLEBASELINE:
+      return V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_BASELINE;
+    case H264PROFILE_SCALABLEHIGH:
+      return V4L2_MPEG_VIDEO_H264_PROFILE_SCALABLE_HIGH;
+    case H264PROFILE_STEREOHIGH:
+      return V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH;
+    case H264PROFILE_MULTIVIEWHIGH:
+      return V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH;
+    default:
+      DVLOGF(1) << "Add more cases as needed";
+      return -1;
+  }
+}
+
+// static
+int32_t V4L2Device::H264LevelIdcToV4L2H264Level(uint8_t level_idc) {
+  switch (level_idc) {
+    case 10:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_1_0;
+    case 9:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_1B;
+    case 11:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_1_1;
+    case 12:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_1_2;
+    case 13:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_1_3;
+    case 20:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_2_0;
+    case 21:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_2_1;
+    case 22:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_2_2;
+    case 30:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_3_0;
+    case 31:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_3_1;
+    case 32:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_3_2;
+    case 40:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_4_0;
+    case 41:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_4_1;
+    case 42:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_4_2;
+    case 50:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_5_0;
+    case 51:
+      return V4L2_MPEG_VIDEO_H264_LEVEL_5_1;
+    default:
+      DVLOGF(1) << "Unrecognized level_idc: " << static_cast<int>(level_idc);
+      return -1;
+  }
+}
+
+// static
 gfx::Size V4L2Device::CodedSizeFromV4L2Format(struct v4l2_format format) {
   gfx::Size coded_size;
   gfx::Size visible_size;
diff --git a/media/gpu/v4l2/v4l2_device.h b/media/gpu/v4l2/v4l2_device.h
index 0fafd790..fa25dae 100644
--- a/media/gpu/v4l2/v4l2_device.h
+++ b/media/gpu/v4l2/v4l2_device.h
@@ -49,6 +49,10 @@
   // Convert format requirements requested by a V4L2 device to gfx::Size.
   static gfx::Size CodedSizeFromV4L2Format(struct v4l2_format format);
 
+  // Convert required H264 profile and level to V4L2 enums.
+  static int32_t VideoCodecProfileToV4L2H264Profile(VideoCodecProfile profile);
+  static int32_t H264LevelIdcToV4L2H264Level(uint8_t level_idc);
+
   enum class Type {
     kDecoder,
     kEncoder,
diff --git a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
index 3d90254f..12f0d15 100644
--- a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
@@ -1017,6 +1017,7 @@
     if (it == surfaces_at_device_.end()) {
       VLOGF(1) << "Got invalid surface from device.";
       NOTIFY_ERROR(PLATFORM_FAILURE);
+      return;
     }
 
     it->second->SetDecoded();
diff --git a/media/gpu/v4l2/v4l2_video_decode_accelerator.cc b/media/gpu/v4l2/v4l2_video_decode_accelerator.cc
index 5308e95e..85e90de0 100644
--- a/media/gpu/v4l2/v4l2_video_decode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_video_decode_accelerator.cc
@@ -20,6 +20,7 @@
 #include "base/posix/eintr_wrapper.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "media/base/media_switches.h"
@@ -29,6 +30,7 @@
 #include "media/video/h264_parser.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gl/gl_context.h"
+#include "ui/gl/gl_fence_egl.h"
 #include "ui/gl/scoped_binders.h"
 
 #define DVLOGF(level) DVLOG(level) << __func__ << "(): "
@@ -84,13 +86,6 @@
   const int32_t input_id;
 };
 
-struct V4L2VideoDecodeAccelerator::EGLSyncKHRRef {
-  EGLSyncKHRRef(EGLDisplay egl_display, EGLSyncKHR egl_sync);
-  ~EGLSyncKHRRef();
-  EGLDisplay const egl_display;
-  EGLSyncKHR egl_sync;
-};
-
 V4L2VideoDecodeAccelerator::BitstreamBufferRef::BitstreamBufferRef(
     base::WeakPtr<Client>& client,
     scoped_refptr<base::SingleThreadTaskRunner>& client_task_runner,
@@ -114,18 +109,6 @@
   }
 }
 
-V4L2VideoDecodeAccelerator::EGLSyncKHRRef::EGLSyncKHRRef(EGLDisplay egl_display,
-                                                         EGLSyncKHR egl_sync)
-    : egl_display(egl_display), egl_sync(egl_sync) {}
-
-V4L2VideoDecodeAccelerator::EGLSyncKHRRef::~EGLSyncKHRRef() {
-  // We don't check for eglDestroySyncKHR failures, because if we get here
-  // with a valid sync object, something went wrong and we are getting
-  // destroyed anyway.
-  if (egl_sync != EGL_NO_SYNC_KHR)
-    eglDestroySyncKHR(egl_display, egl_sync);
-}
-
 V4L2VideoDecodeAccelerator::InputRecord::InputRecord()
     : at_device(false), address(NULL), length(0), bytes_used(0), input_id(-1) {}
 
@@ -134,7 +117,6 @@
 V4L2VideoDecodeAccelerator::OutputRecord::OutputRecord()
     : state(kFree),
       egl_image(EGL_NO_IMAGE_KHR),
-      egl_sync(EGL_NO_SYNC_KHR),
       picture_id(-1),
       texture_id(0),
       cleared(false) {}
@@ -301,6 +283,9 @@
   DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread());
   DCHECK_EQ(decoder_state_, kInitialized);
 
+  if (IsDestroyPending())
+    return;
+
   // Subscribe to the resolution change event.
   struct v4l2_event_subscription sub;
   memset(&sub, 0, sizeof(sub));
@@ -355,6 +340,9 @@
   DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread());
   DCHECK_EQ(decoder_state_, kAwaitingPictureBuffers);
 
+  if (IsDestroyPending())
+    return;
+
   uint32_t req_buffer_count = output_dpb_size_ + kDpbOutputBufferExtraCount;
   if (image_processor_device_)
     req_buffer_count += kDpbOutputBufferExtraCountForImageProcessor;
@@ -398,9 +386,9 @@
     OutputRecord& output_record = output_buffer_map_[i];
     DCHECK_EQ(output_record.state, kFree);
     DCHECK_EQ(output_record.egl_image, EGL_NO_IMAGE_KHR);
-    DCHECK_EQ(output_record.egl_sync, EGL_NO_SYNC_KHR);
+    DCHECK(!output_record.egl_fence);
     DCHECK_EQ(output_record.picture_id, -1);
-    DCHECK_EQ(output_record.cleared, false);
+    DCHECK(!output_record.cleared);
     DCHECK(output_record.processor_input_fds.empty());
 
     output_record.picture_id = buffers[i].id();
@@ -499,6 +487,9 @@
   DVLOGF(3) << "index=" << buffer_index << ", picture_id=" << picture_buffer_id;
   DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread());
 
+  if (IsDestroyPending())
+    return;
+
   // It's possible that while waiting for the EGLImages to be allocated and
   // assigned, we have already decoded more of the stream and saw another
   // resolution change. This is a normal situation, in such a case either there
@@ -517,7 +508,7 @@
 
   OutputRecord& output_record = output_buffer_map_[buffer_index];
   DCHECK_EQ(output_record.egl_image, EGL_NO_IMAGE_KHR);
-  DCHECK_EQ(output_record.egl_sync, EGL_NO_SYNC_KHR);
+  DCHECK(!output_record.egl_fence);
   DCHECK_EQ(output_record.state, kFree);
   DCHECK_EQ(std::count(free_output_buffers_.begin(), free_output_buffers_.end(),
                        buffer_index),
@@ -587,6 +578,9 @@
             << ", stride=" << stride;
   DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread());
 
+  if (IsDestroyPending())
+    return;
+
   const auto iter =
       std::find_if(output_buffer_map_.begin(), output_buffer_map_.end(),
                    [picture_buffer_id](const OutputRecord& output_record) {
@@ -678,7 +672,7 @@
   // Must be run on child thread, as we'll insert a sync in the EGL context.
   DCHECK(child_task_runner_->BelongsToCurrentThread());
 
-  std::unique_ptr<EGLSyncKHRRef> egl_sync_ref;
+  std::unique_ptr<gl::GLFenceEGL> egl_fence;
 
   if (!make_context_current_cb_.is_null()) {
     if (!make_context_current_cb_.Run()) {
@@ -687,24 +681,21 @@
       return;
     }
 
-    EGLSyncKHR egl_sync = EGL_NO_SYNC_KHR;
 // TODO(posciak): https://crbug.com/450898.
 #if defined(ARCH_CPU_ARMEL)
-    egl_sync = eglCreateSyncKHR(egl_display_, EGL_SYNC_FENCE_KHR, NULL);
-    if (egl_sync == EGL_NO_SYNC_KHR) {
-      VLOGF(1) << "eglCreateSyncKHR() failed";
+    egl_fence = gl::GLFenceEGL::Create();
+    if (!egl_fence) {
+      VLOGF(1) << "gl::GLFenceEGL::Create() failed";
       NOTIFY_ERROR(PLATFORM_FAILURE);
       return;
     }
 #endif
-
-    egl_sync_ref.reset(new EGLSyncKHRRef(egl_display_, egl_sync));
   }
 
   decoder_thread_.task_runner()->PostTask(
       FROM_HERE, base::Bind(&V4L2VideoDecodeAccelerator::ReusePictureBufferTask,
                             base::Unretained(this), picture_buffer_id,
-                            base::Passed(&egl_sync_ref)));
+                            base::Passed(&egl_fence)));
 }
 
 void V4L2VideoDecodeAccelerator::Flush() {
@@ -727,6 +718,10 @@
   VLOGF(2);
   DCHECK(child_task_runner_->BelongsToCurrentThread());
 
+  // Signal any waiting/sleeping tasks to early exit as soon as possible to
+  // avoid waiting too long for the decoder_thread_ to Stop().
+  destroy_pending_.Signal();
+
   // We're destroying; cancel all callbacks.
   client_ptr_factory_.reset();
   weak_this_factory_.InvalidateWeakPtrs();
@@ -775,6 +770,9 @@
   TRACE_EVENT1("media,gpu", "V4L2VDA::DecodeTask", "input_id",
                bitstream_buffer.id());
 
+  if (IsDestroyPending())
+    return;
+
   std::unique_ptr<BitstreamBufferRef> bitstream_record(
       new BitstreamBufferRef(decode_client_, decode_task_runner_,
                              &bitstream_buffer, bitstream_buffer.id()));
@@ -815,6 +813,9 @@
   DCHECK_NE(decoder_state_, kUninitialized);
   TRACE_EVENT0("media,gpu", "V4L2VDA::DecodeBufferTask");
 
+  if (IsDestroyPending())
+    return;
+
   decoder_decode_buffer_tasks_scheduled_--;
 
   if (decoder_state_ != kInitialized && decoder_state_ != kDecoding) {
@@ -1179,6 +1180,9 @@
   DCHECK_NE(decoder_state_, kUninitialized);
   TRACE_EVENT0("media,gpu", "V4L2VDA::ServiceDeviceTask");
 
+  if (IsDestroyPending())
+    return;
+
   if (decoder_state_ == kResetting) {
     DVLOGF(3) << "early out: kResetting state";
     return;
@@ -1533,23 +1537,33 @@
   OutputRecord& output_record = output_buffer_map_[buffer];
   DCHECK_EQ(output_record.state, kFree);
   DCHECK_NE(output_record.picture_id, -1);
-  if (output_record.egl_sync != EGL_NO_SYNC_KHR) {
-    TRACE_EVENT0("media,gpu",
-                 "V4L2VDA::EnqueueOutputRecord: eglClientWaitSyncKHR");
-    // If we have to wait for completion, wait.  Note that
-    // free_output_buffers_ is a FIFO queue, so we always wait on the
-    // buffer that has been in the queue the longest.
-    if (eglClientWaitSyncKHR(egl_display_, output_record.egl_sync, 0,
-                             EGL_FOREVER_KHR) == EGL_FALSE) {
-      // This will cause tearing, but is safe otherwise.
-      DVLOGF(1) << "eglClientWaitSyncKHR failed!";
+  if (output_record.egl_fence) {
+    TRACE_EVENT0(
+        "media,gpu",
+        "V4L2VDA::EnqueueOutputRecord: GLFenceEGL::ClientWaitWithTimeoutNanos");
+    // If we have to wait for completion, wait. Note that free_output_buffers_
+    // is a FIFO queue, so we always wait on the buffer that has been in the
+    // queue the longest. Every 100ms we check whether the decoder is shutting
+    // down, or we might get stuck waiting on a fence that will never come.
+    while (!IsDestroyPending()) {
+      const EGLTimeKHR wait_ns =
+          base::TimeDelta::FromMilliseconds(100).InNanoseconds();
+      EGLint result =
+          output_record.egl_fence->ClientWaitWithTimeoutNanos(wait_ns);
+      if (result == EGL_CONDITION_SATISFIED_KHR) {
+        break;
+      } else if (result == EGL_FALSE) {
+        // This will cause tearing, but is safe otherwise.
+        DVLOGF(1) << "GLFenceEGL::ClientWaitWithTimeoutNanos failed!";
+        break;
+      }
+      DCHECK_EQ(result, EGL_TIMEOUT_EXPIRED_KHR);
     }
-    if (eglDestroySyncKHR(egl_display_, output_record.egl_sync) != EGL_TRUE) {
-      VLOGF(1) << "eglDestroySyncKHR failed!";
-      NOTIFY_ERROR(PLATFORM_FAILURE);
+
+    if (IsDestroyPending())
       return false;
-    }
-    output_record.egl_sync = EGL_NO_SYNC_KHR;
+
+    output_record.egl_fence.reset();
   }
   struct v4l2_buffer qbuf;
   std::unique_ptr<struct v4l2_plane[]> qbuf_planes(
@@ -1580,11 +1594,14 @@
 
 void V4L2VideoDecodeAccelerator::ReusePictureBufferTask(
     int32_t picture_buffer_id,
-    std::unique_ptr<EGLSyncKHRRef> egl_sync_ref) {
+    std::unique_ptr<gl::GLFenceEGL> egl_fence) {
   DVLOGF(4) << "picture_buffer_id=" << picture_buffer_id;
   DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread());
   TRACE_EVENT0("media,gpu", "V4L2VDA::ReusePictureBufferTask");
 
+  if (IsDestroyPending())
+    return;
+
   // We run ReusePictureBufferTask even if we're in kResetting.
   if (decoder_state_ == kError) {
     DVLOGF(4) << "early out: kError state";
@@ -1605,7 +1622,7 @@
     // It's possible that we've already posted a DismissPictureBuffer for this
     // picture, but it has not yet executed when this ReusePictureBuffer was
     // posted to us by the client. In that case just ignore this (we've already
-    // dismissed it and accounted for that) and let the sync object get
+    // dismissed it and accounted for that) and let the fence object get
     // destroyed.
     DVLOGF(3) << "got picture id= " << picture_buffer_id
               << " not in use (anymore?).";
@@ -1619,15 +1636,13 @@
     return;
   }
 
-  DCHECK_EQ(output_record.egl_sync, EGL_NO_SYNC_KHR);
+  DCHECK(!output_record.egl_fence);
   output_record.state = kFree;
   free_output_buffers_.push_back(index);
   decoder_frames_at_client_--;
-  if (egl_sync_ref) {
-    output_record.egl_sync = egl_sync_ref->egl_sync;
-    // Take ownership of the EGLSync.
-    egl_sync_ref->egl_sync = EGL_NO_SYNC_KHR;
-  }
+  // Take ownership of the EGL fence.
+  output_record.egl_fence = std::move(egl_fence);
+
   // We got a buffer back, so enqueue it back.
   Enqueue();
 }
@@ -1637,6 +1652,9 @@
   DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread());
   TRACE_EVENT0("media,gpu", "V4L2VDA::FlushTask");
 
+  if (IsDestroyPending())
+    return;
+
   if (decoder_state_ == kError) {
     VLOGF(2) << "early out: kError state";
     return;
@@ -1751,6 +1769,9 @@
   DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread());
   TRACE_EVENT0("media,gpu", "V4L2VDA::ResetTask");
 
+  if (IsDestroyPending())
+    return;
+
   if (decoder_state_ == kError) {
     VLOGF(2) << "early out: kError state";
     return;
@@ -1822,6 +1843,9 @@
   DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread());
   TRACE_EVENT0("media,gpu", "V4L2VDA::ResetDoneTask");
 
+  if (IsDestroyPending())
+    return;
+
   if (decoder_state_ == kError) {
     VLOGF(2) << "early out: kError state";
     return;
@@ -1857,6 +1881,8 @@
 
   // DestroyTask() should run regardless of decoder_state_.
 
+  decoder_state_ = kDestroying;
+
   StopDevicePoll();
   StopOutputStream();
   StopInputStream();
@@ -1871,9 +1897,6 @@
 
   image_processor_ = nullptr;
 
-  // Set our state to kError.  Just in case.
-  decoder_state_ = kError;
-
   DestroyInputBuffers();
   DestroyOutputBuffers();
 }
@@ -1941,7 +1964,7 @@
     if (output_record.state == kAtDevice) {
       output_record.state = kFree;
       free_output_buffers_.push_back(i);
-      DCHECK_EQ(output_record.egl_sync, EGL_NO_SYNC_KHR);
+      DCHECK(!output_record.egl_fence);
     }
   }
   output_buffer_queued_count_ = 0;
@@ -2050,9 +2073,14 @@
                             base::Unretained(this), event_pending));
 }
 
+bool V4L2VideoDecodeAccelerator::IsDestroyPending() {
+  return destroy_pending_.IsSignaled();
+}
+
 void V4L2VideoDecodeAccelerator::NotifyError(Error error) {
   VLOGF(1);
 
+  // Notifying the client should only happen from the client's thread.
   if (!child_task_runner_->BelongsToCurrentThread()) {
     child_task_runner_->PostTask(
         FROM_HERE, base::Bind(&V4L2VideoDecodeAccelerator::NotifyError,
@@ -2060,6 +2088,7 @@
     return;
   }
 
+  // Notify the decoder's client an error has occurred.
   if (client_) {
     client_->NotifyError(error);
     client_ptr_factory_.reset();
@@ -2077,9 +2106,11 @@
     return;
   }
 
-  // Post NotifyError only if we are already initialized, as the API does
-  // not allow doing so before that.
-  if (decoder_state_ != kError && decoder_state_ != kUninitialized)
+  // Notifying the client of an error will only happen if we are already
+  // initialized, as the API does not allow doing so before that. Subsequent
+  // errors and errors while destroying will be suppressed.
+  if (decoder_state_ != kError && decoder_state_ != kUninitialized &&
+      decoder_state_ != kDestroying)
     NotifyError(error);
 
   decoder_state_ = kError;
@@ -2570,12 +2601,7 @@
                      egl_display_, output_record.egl_image));
     }
 
-    if (output_record.egl_sync != EGL_NO_SYNC_KHR) {
-      if (eglDestroySyncKHR(egl_display_, output_record.egl_sync) != EGL_TRUE) {
-        VLOGF(1) << "eglDestroySyncKHR failed.";
-        success = false;
-      }
-    }
+    output_record.egl_fence.reset();
 
     DVLOGF(3) << "dismissing PictureBuffer id=" << output_record.picture_id;
     child_task_runner_->PostTask(
diff --git a/media/gpu/v4l2/v4l2_video_decode_accelerator.h b/media/gpu/v4l2/v4l2_video_decode_accelerator.h
index 7dbdeaf..d7d7bbd4 100644
--- a/media/gpu/v4l2/v4l2_video_decode_accelerator.h
+++ b/media/gpu/v4l2/v4l2_video_decode_accelerator.h
@@ -33,6 +33,12 @@
 #include "ui/gfx/geometry/size.h"
 #include "ui/gl/gl_bindings.h"
 
+namespace gl {
+
+class GLFenceEGL;
+
+}  // namespace gl
+
 namespace media {
 
 class H264Parser;
@@ -145,7 +151,8 @@
     // Requested new PictureBuffers via ProvidePictureBuffers(), awaiting
     // AssignPictureBuffers().
     kAwaitingPictureBuffers,
-    kError,  // Error in kDecoding state.
+    kError,       // Error in kDecoding state.
+    kDestroying,  // Destroying state, when shutting down the decoder.
   };
 
   enum OutputRecordState {
@@ -163,9 +170,6 @@
   // Decode() to DecodeTask().
   struct BitstreamBufferRef;
 
-  // Auto-destruction reference for EGLSync (for message-passing).
-  struct EGLSyncKHRRef;
-
   // Record for decoded pictures that can be sent to PictureReady.
   struct PictureRecord {
     PictureRecord(bool cleared, const Picture& picture);
@@ -192,7 +196,8 @@
     ~OutputRecord();
     OutputRecordState state;
     EGLImageKHR egl_image;  // EGLImageKHR for the output buffer.
-    EGLSyncKHR egl_sync;    // sync the compositor's use of the EGLImage.
+    std::unique_ptr<gl::GLFenceEGL> egl_fence;  // sync the compositor's use of
+                                                // the EGLImage.
     int32_t picture_id;     // picture buffer id as returned to PictureReady().
     GLuint texture_id;
     bool cleared;           // Whether the texture is cleared and safe to render
@@ -286,11 +291,11 @@
   bool EnqueueInputRecord();
   bool EnqueueOutputRecord();
 
-  // Process a ReusePictureBuffer() API call.  The API call create an EGLSync
-  // object on the main (GPU process) thread; we will record this object so we
-  // can wait on it before reusing the buffer.
+  // Task to flag the specified picture buffer for reuse, executed on the
+  // decoder_thread_. The picture buffer can only be reused after the specified
+  // fence has been signaled.
   void ReusePictureBufferTask(int32_t picture_buffer_id,
-                              std::unique_ptr<EGLSyncKHRRef> egl_sync_ref);
+                              std::unique_ptr<gl::GLFenceEGL> egl_fence);
 
   // Flush() task.  Child thread should not submit any more buffers until it
   // receives the NotifyFlushDone callback.  This task will schedule an empty
@@ -356,6 +361,9 @@
   // Safe from any thread.
   //
 
+  // Check whether a destroy is scheduled.
+  bool IsDestroyPending();
+
   // Error notification (using PostTask() to child thread, if necessary).
   void NotifyError(Error error);
 
@@ -450,6 +458,9 @@
   // Decoder state machine state.
   State decoder_state_;
 
+  // Waitable event signaled when the decoder is destroying.
+  base::WaitableEvent destroy_pending_;
+
   Config::OutputMode output_mode_;
 
   // BitstreamBuffer we're presently reading.
diff --git a/media/gpu/v4l2/v4l2_video_encode_accelerator.cc b/media/gpu/v4l2/v4l2_video_encode_accelerator.cc
index fa56ddd..52ddbe7 100644
--- a/media/gpu/v4l2/v4l2_video_encode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_video_encode_accelerator.cc
@@ -244,10 +244,7 @@
       free_image_processor_output_buffers_.push_back(i);
   }
 
-  // TODO(johnylin): pass |config.h264_output_level| to InitControl() for
-  //                 updating the correlative V4L2_CID_MPEG_VIDEO_H264_LEVEL
-  //                 ctrl value. https://crbug.com/863327
-  if (!InitControls())
+  if (!InitControls(config))
     return false;
 
   if (!CreateOutputBuffers())
@@ -259,8 +256,8 @@
   }
 
   RequestEncodingParametersChange(
-      config.initial_bitrate,
-      config.initial_framerate.value_or(kDefaultFramerate));
+      config.initial_bitrate, config.initial_framerate.value_or(
+                                  VideoEncodeAccelerator::kDefaultFramerate));
 
   encoder_state_ = kInitialized;
 
@@ -1205,7 +1202,7 @@
   return device_->Ioctl(VIDIOC_S_EXT_CTRLS, &ext_ctrls) == 0;
 }
 
-bool V4L2VideoEncodeAccelerator::InitControls() {
+bool V4L2VideoEncodeAccelerator::InitControls(const Config& config) {
   std::vector<struct v4l2_ext_control> ctrls;
   struct v4l2_ext_control ctrl;
 
@@ -1257,10 +1254,29 @@
     ctrl.value = 51;
     ctrls.push_back(ctrl);
 
-    // Use H.264 level 4.0 to match the supported max resolution.
+    // Set H.264 profile.
+    int32_t profile_value =
+        V4L2Device::VideoCodecProfileToV4L2H264Profile(config.output_profile);
+    if (profile_value < 0) {
+      NOTIFY_ERROR(kInvalidArgumentError);
+      return false;
+    }
+    memset(&ctrl, 0, sizeof(ctrl));
+    ctrl.id = V4L2_CID_MPEG_VIDEO_H264_PROFILE;
+    ctrl.value = profile_value;
+    ctrls.push_back(ctrl);
+
+    // Set H.264 output level from config. Use Level 4.0 as fallback default.
+    int32_t level_value = V4L2Device::H264LevelIdcToV4L2H264Level(
+        config.h264_output_level.value_or(
+            VideoEncodeAccelerator::kDefaultH264Level));
+    if (level_value < 0) {
+      NOTIFY_ERROR(kInvalidArgumentError);
+      return false;
+    }
     memset(&ctrl, 0, sizeof(ctrl));
     ctrl.id = V4L2_CID_MPEG_VIDEO_H264_LEVEL;
-    ctrl.value = V4L2_MPEG_VIDEO_H264_LEVEL_4_0;
+    ctrl.value = level_value;
     ctrls.push_back(ctrl);
 
     // Ask not to put SPS and PPS into separate bitstream buffers.
diff --git a/media/gpu/v4l2/v4l2_video_encode_accelerator.h b/media/gpu/v4l2/v4l2_video_encode_accelerator.h
index f52173ce..8c71a2be 100644
--- a/media/gpu/v4l2/v4l2_video_encode_accelerator.h
+++ b/media/gpu/v4l2/v4l2_video_encode_accelerator.h
@@ -92,7 +92,6 @@
   };
 
   enum {
-    kDefaultFramerate = 30,
     // These are rather subjectively tuned.
     kInputBufferCount = 2,
     kOutputBufferCount = 2,
@@ -195,8 +194,8 @@
   // Set up the device to the output format requested in Initialize().
   bool SetOutputFormat(VideoCodecProfile output_profile);
 
-  // Initialize device controls with default values.
-  bool InitControls();
+  // Initialize device controls with |config| or default values.
+  bool InitControls(const Config& config);
 
   // Create the buffers we need.
   bool CreateInputBuffers();
diff --git a/media/gpu/vaapi/accelerated_video_encoder.h b/media/gpu/vaapi/accelerated_video_encoder.h
index 4d7fa39c..95b6e07 100644
--- a/media/gpu/vaapi/accelerated_video_encoder.h
+++ b/media/gpu/vaapi/accelerated_video_encoder.h
@@ -15,6 +15,7 @@
 #include "media/base/video_bitrate_allocation.h"
 #include "media/base/video_codecs.h"
 #include "media/gpu/codec_picture.h"
+#include "media/video/video_encode_accelerator.h"
 #include "ui/gfx/geometry/size.h"
 
 namespace media {
@@ -116,14 +117,10 @@
     DISALLOW_COPY_AND_ASSIGN(EncodeJob);
   };
 
-  // Initializes the encoder to encode frames of |visible_size| into a stream
-  // for |profile|, at |initial_bitrate| and |initial_framerate|.
+  // Initializes the encoder with requested parameter set |config|.
   // Returns false if the requested set of parameters is not supported,
   // true on success.
-  virtual bool Initialize(const gfx::Size& visible_size,
-                          VideoCodecProfile profile,
-                          uint32_t initial_bitrate,
-                          uint32_t initial_framerate) = 0;
+  virtual bool Initialize(const VideoEncodeAccelerator::Config& config) = 0;
 
   // Updates current framerate and/or bitrate to |framerate| in FPS
   // and the specified video bitrate allocation.
diff --git a/media/gpu/vaapi/h264_encoder.cc b/media/gpu/vaapi/h264_encoder.cc
index a5919317..761b54b 100644
--- a/media/gpu/vaapi/h264_encoder.cc
+++ b/media/gpu/vaapi/h264_encoder.cc
@@ -6,6 +6,7 @@
 
 #include "base/bits.h"
 #include "base/stl_util.h"
+#include "media/video/h264_level_limits.h"
 
 #define DVLOGF(level) DVLOG(level) << __func__ << "(): "
 
@@ -31,9 +32,6 @@
 constexpr int kBitRateScale = 0;  // bit_rate_scale for SPS HRD parameters.
 constexpr int kCPBSizeScale = 0;  // cpb_size_scale for SPS HRD parameters.
 
-// Default to H264 profile 4.1.
-constexpr int kDefaultLevelIDC = 41;
-
 // 4:2:0
 constexpr int kChromaFormatIDC = 1;
 }  // namespace
@@ -59,27 +57,30 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
 
-bool H264Encoder::Initialize(const gfx::Size& visible_size,
-                             VideoCodecProfile profile,
-                             uint32_t initial_bitrate,
-                             uint32_t initial_framerate) {
+bool H264Encoder::Initialize(const VideoEncodeAccelerator::Config& config) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  switch (profile) {
+  switch (config.output_profile) {
     case H264PROFILE_BASELINE:
     case H264PROFILE_MAIN:
     case H264PROFILE_HIGH:
       break;
 
     default:
-      NOTIMPLEMENTED() << "Unsupported profile " << GetProfileName(profile);
+      NOTIMPLEMENTED() << "Unsupported profile "
+                       << GetProfileName(config.output_profile);
       return false;
   }
 
-  DCHECK(!visible_size.IsEmpty());
-  visible_size_ = visible_size;
+  if (config.input_visible_size.IsEmpty()) {
+    DVLOGF(1) << "Input visible size could not be empty";
+    return false;
+  }
+  visible_size_ = config.input_visible_size;
   // For 4:2:0, the pixel sizes have to be even.
-  DCHECK_EQ(visible_size_.width() % 2, 0);
-  DCHECK_EQ(visible_size_.height() % 2, 0);
+  if ((visible_size_.width() % 2 != 0) || (visible_size_.height() % 2 != 0)) {
+    DVLOGF(1) << "The pixel sizes are not even: " << visible_size_.ToString();
+    return false;
+  }
   constexpr size_t kH264MacroblockSizeInPixels = 16;
   coded_size_ = gfx::Size(
       base::bits::Align(visible_size_.width(), kH264MacroblockSizeInPixels),
@@ -87,9 +88,17 @@
   mb_width_ = coded_size_.width() / kH264MacroblockSizeInPixels;
   mb_height_ = coded_size_.height() / kH264MacroblockSizeInPixels;
 
-  profile_ = profile;
+  profile_ = config.output_profile;
+  level_ = config.h264_output_level.value_or(
+      VideoEncodeAccelerator::kDefaultH264Level);
+  uint32_t initial_framerate = config.initial_framerate.value_or(
+      VideoEncodeAccelerator::kDefaultFramerate);
+  if (!CheckH264LevelLimits(profile_, level_, config.initial_bitrate,
+                            initial_framerate, mb_width_ * mb_height_))
+    return false;
+
   VideoBitrateAllocation initial_bitrate_allocation;
-  initial_bitrate_allocation.SetBitrate(0, 0, initial_bitrate);
+  initial_bitrate_allocation.SetBitrate(0, 0, config.initial_bitrate);
   if (!UpdateRates(initial_bitrate_allocation, initial_framerate))
     return false;
 
@@ -234,7 +243,10 @@
       return;
   }
 
-  current_sps_.level_idc = kDefaultLevelIDC;
+  H264SPS::GetLevelConfigFromProfileLevel(profile_, level_,
+                                          &current_sps_.level_idc,
+                                          &current_sps_.constraint_set3_flag);
+
   current_sps_.seq_parameter_set_id = 0;
   current_sps_.chroma_format_idc = kChromaFormatIDC;
 
diff --git a/media/gpu/vaapi/h264_encoder.h b/media/gpu/vaapi/h264_encoder.h
index 2d32081..3a834bb 100644
--- a/media/gpu/vaapi/h264_encoder.h
+++ b/media/gpu/vaapi/h264_encoder.h
@@ -95,10 +95,7 @@
   ~H264Encoder() override;
 
   // AcceleratedVideoEncoder implementation.
-  bool Initialize(const gfx::Size& visible_size,
-                  VideoCodecProfile profile,
-                  uint32_t initial_bitrate,
-                  uint32_t initial_framerate) override;
+  bool Initialize(const VideoEncodeAccelerator::Config& config) override;
   bool UpdateRates(const VideoBitrateAllocation& bitrate_allocation,
                    uint32_t framerate) override;
   gfx::Size GetCodedSize() const override;
@@ -115,6 +112,10 @@
   void GeneratePackedSPS();
   void GeneratePackedPPS();
 
+  // Check if |bitrate| and |framerate| and current coded size are supported by
+  // current profile and level.
+  bool CheckConfigValidity(uint32_t bitrate, uint32_t framerate);
+
   // Current SPS, PPS and their packed versions. Packed versions are NALUs
   // in AnnexB format *without* emulation prevention three-byte sequences
   // (those are expected to be added by the client as needed).
@@ -129,6 +130,9 @@
   // H264 profile currently used.
   media::VideoCodecProfile profile_ = VIDEO_CODEC_PROFILE_UNKNOWN;
 
+  // H264 level currently used.
+  uint8_t level_ = 0;
+
   // Current visible and coded sizes in pixels.
   gfx::Size visible_size_;
   gfx::Size coded_size_;
diff --git a/media/gpu/vaapi/vaapi_video_encode_accelerator.cc b/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
index 69c0856..70167c7 100644
--- a/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
@@ -55,8 +55,6 @@
 // reconstructed picture, which is later used for reference.
 constexpr size_t kNumSurfacesPerFrame = 2;
 
-constexpr int kDefaultFramerate = 30;
-
 // Percentage of bitrate set to be targeted by the HW encoder.
 constexpr unsigned int kTargetBitratePercentage = 90;
 
@@ -293,12 +291,7 @@
       return;
   }
 
-  // TODO(johnylin): pass |config.h264_output_level| to H264Encoder.
-  //                 https://crbug.com/863327
-  if (!encoder_->Initialize(
-          config.input_visible_size, config.output_profile,
-          config.initial_bitrate,
-          config.initial_framerate.value_or(kDefaultFramerate))) {
+  if (!encoder_->Initialize(config)) {
     NOTIFY_ERROR(kInvalidArgumentError, "Failed initializing encoder");
     return;
   }
diff --git a/media/gpu/vaapi/vp8_encoder.cc b/media/gpu/vaapi/vp8_encoder.cc
index b7df863c..b527c5fa 100644
--- a/media/gpu/vaapi/vp8_encoder.cc
+++ b/media/gpu/vaapi/vp8_encoder.cc
@@ -48,27 +48,36 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
 
-bool VP8Encoder::Initialize(const gfx::Size& visible_size,
-                            VideoCodecProfile profile,
-                            uint32_t initial_bitrate,
-                            uint32_t initial_framerate) {
+bool VP8Encoder::Initialize(const VideoEncodeAccelerator::Config& config) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(profile >= VP8PROFILE_MIN && profile <= VP8PROFILE_MAX);
+  if (VideoCodecProfileToVideoCodec(config.output_profile) != kCodecVP8) {
+    DVLOGF(1) << "Invalid profile: " << GetProfileName(config.output_profile);
+    return false;
+  }
 
-  DCHECK(!visible_size.IsEmpty());
+  if (config.input_visible_size.IsEmpty()) {
+    DVLOGF(1) << "Input visible size could not be empty";
+    return false;
+  }
   // 4:2:0 format has to be 2-aligned.
-  DCHECK_EQ(visible_size.width() % 2, 0);
-  DCHECK_EQ(visible_size.height() % 2, 0);
+  if ((config.input_visible_size.width() % 2 != 0) ||
+      (config.input_visible_size.height() % 2 != 0)) {
+    DVLOGF(1) << "The pixel sizes are not even: "
+              << config.input_visible_size.ToString();
+    return false;
+  }
 
-  visible_size_ = visible_size;
+  visible_size_ = config.input_visible_size;
   coded_size_ = gfx::Size(base::bits::Align(visible_size_.width(), 16),
                           base::bits::Align(visible_size_.height(), 16));
 
   Reset();
 
   VideoBitrateAllocation initial_bitrate_allocation;
-  initial_bitrate_allocation.SetBitrate(0, 0, initial_bitrate);
-  return UpdateRates(initial_bitrate_allocation, initial_framerate);
+  initial_bitrate_allocation.SetBitrate(0, 0, config.initial_bitrate);
+  return UpdateRates(initial_bitrate_allocation,
+                     config.initial_framerate.value_or(
+                         VideoEncodeAccelerator::kDefaultFramerate));
 }
 
 gfx::Size VP8Encoder::GetCodedSize() const {
diff --git a/media/gpu/vaapi/vp8_encoder.h b/media/gpu/vaapi/vp8_encoder.h
index 0b9b0e88..1ed75b59 100644
--- a/media/gpu/vaapi/vp8_encoder.h
+++ b/media/gpu/vaapi/vp8_encoder.h
@@ -72,10 +72,7 @@
   ~VP8Encoder() override;
 
   // AcceleratedVideoEncoder implementation.
-  bool Initialize(const gfx::Size& visible_size,
-                  VideoCodecProfile profile,
-                  uint32_t initial_bitrate,
-                  uint32_t initial_framerate) override;
+  bool Initialize(const VideoEncodeAccelerator::Config& config) override;
   bool UpdateRates(const VideoBitrateAllocation& bitrate_allocation,
                    uint32_t framerate) override;
   gfx::Size GetCodedSize() const override;
diff --git a/media/video/BUILD.gn b/media/video/BUILD.gn
index 1b677c1..23e5ace1 100644
--- a/media/video/BUILD.gn
+++ b/media/video/BUILD.gn
@@ -21,6 +21,8 @@
     "gpu_video_accelerator_factories.h",
     "h264_bit_reader.cc",
     "h264_bit_reader.h",
+    "h264_level_limits.cc",
+    "h264_level_limits.h",
     "h264_parser.cc",
     "h264_parser.h",
     "h264_poc.cc",
diff --git a/media/video/h264_level_limits.cc b/media/video/h264_level_limits.cc
new file mode 100644
index 0000000..5437e7b
--- /dev/null
+++ b/media/video/h264_level_limits.cc
@@ -0,0 +1,144 @@
+// 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 "media/video/h264_level_limits.h"
+
+#include "base/logging.h"
+#include "media/video/h264_parser.h"
+
+namespace media {
+namespace {
+struct LevelLimits {
+  // All names and abbreviations are as in table A-1 in spec.
+  // MaxMBPS, Max. macroblock processing rate (MB/s)
+  uint32_t max_mbps;
+  // MaxFS, Max. frame size (MBs)
+  uint32_t max_fs;
+  // MaxDpbMbs, Max. decoded picture buffer size (MBs)
+  uint32_t max_dpb_mbs;
+  // MaxBR, Max. video bitrate for Baseline and Main Profiles (kbit/s)
+  uint32_t max_main_br;
+};
+
+LevelLimits LevelToLevelLimits(uint8_t level) {
+  // See table A-1 in spec
+  // { MaxMBPS, MaxFS, MaxDpbMbs, MaxBR}
+  switch (level) {
+    case H264SPS::kLevelIDC1p0:
+      return {1485, 99, 396, 64};  // Level 1.0
+    case H264SPS::kLevelIDC1B:
+      return {1485, 99, 396, 128};  // Level 1b
+    case H264SPS::kLevelIDC1p1:
+      return {3000, 396, 900, 192};  // Level 1.1
+    case H264SPS::kLevelIDC1p2:
+      return {6000, 396, 2376, 384};  // Level 1.2
+    case H264SPS::kLevelIDC1p3:
+      return {11800, 396, 2376, 768};  // Level 1.3
+    case H264SPS::kLevelIDC2p0:
+      return {11880, 396, 2376, 2000};  // Level 2.0
+    case H264SPS::kLevelIDC2p1:
+      return {19800, 792, 4752, 4000};  // Level 2.1
+    case H264SPS::kLevelIDC2p2:
+      return {20250, 1620, 8100, 4000};  // Level 2.2
+    case H264SPS::kLevelIDC3p0:
+      return {40500, 1620, 8100, 10000};  // Level 3.0
+    case H264SPS::kLevelIDC3p1:
+      return {108000, 3600, 18000, 14000};  // Level 3.1
+    case H264SPS::kLevelIDC3p2:
+      return {216000, 5120, 20480, 20000};  // Level 3.2
+    case H264SPS::kLevelIDC4p0:
+      return {245760, 8192, 32768, 20000};  // Level 4.0
+    case H264SPS::kLevelIDC4p1:
+      return {245760, 8192, 32768, 50000};  // Level 4.1
+    case H264SPS::kLevelIDC4p2:
+      return {522240, 8704, 34816, 50000};  // Level 4.2
+    case H264SPS::kLevelIDC5p0:
+      return {589824, 22080, 110400, 135000};  // Level 5.0
+    case H264SPS::kLevelIDC5p1:
+      return {983040, 36864, 184320, 240000};  // Level 5.1
+    case H264SPS::kLevelIDC5p2:
+      return {2073600, 36864, 184320, 240000};  // Level 5.2
+    case H264SPS::kLevelIDC6p0:
+      return {4177920, 139264, 696320, 240000};  // Level 6.0
+    case H264SPS::kLevelIDC6p1:
+      return {8355840, 139264, 696320, 480000};  // Level 6.1
+    case H264SPS::kLevelIDC6p2:
+      return {16711680, 139264, 696320, 800000};  // Level 6.2
+    default:
+      DVLOG(1) << "Invalid codec level (" << static_cast<int>(level) << ")";
+      return {0, 0, 0, 0};
+  }
+}
+}  // namespace
+
+uint32_t H264LevelToMaxMBPS(uint8_t level) {
+  return LevelToLevelLimits(level).max_mbps;
+}
+
+uint32_t H264LevelToMaxFS(uint8_t level) {
+  return LevelToLevelLimits(level).max_fs;
+}
+
+uint32_t H264LevelToMaxDpbMbs(uint8_t level) {
+  return LevelToLevelLimits(level).max_dpb_mbs;
+}
+
+uint32_t H264ProfileLevelToMaxBR(VideoCodecProfile profile, uint8_t level) {
+  uint32_t max_main_br = LevelToLevelLimits(level).max_main_br;
+
+  // See table A-2 in spec
+  // The maximum bit rate for High Profile is 1.25 times that of the
+  // Base/Extended/Main Profiles, 3 times for Hi10P, and 4 times for
+  // Hi422P/Hi444PP.
+  switch (profile) {
+    case H264PROFILE_BASELINE:
+    case H264PROFILE_MAIN:
+    case H264PROFILE_EXTENDED:
+      return max_main_br;
+    case H264PROFILE_HIGH:
+      return max_main_br * 5 / 4;
+    case H264PROFILE_HIGH10PROFILE:
+      return max_main_br * 3;
+    case H264PROFILE_HIGH422PROFILE:
+    case H264PROFILE_HIGH444PREDICTIVEPROFILE:
+      return max_main_br * 4;
+    default:
+      DVLOG(1) << "Failed to query MaxBR for profile: "
+               << GetProfileName(profile);
+      return 0;
+  }
+}
+
+bool CheckH264LevelLimits(VideoCodecProfile profile,
+                          uint8_t level,
+                          uint32_t bitrate,
+                          uint32_t framerate,
+                          uint32_t framesize_in_mbs) {
+  uint32_t max_bitrate_kbs = H264ProfileLevelToMaxBR(profile, level);
+  DCHECK(base::IsValueInRangeForNumericType<uint32_t>(max_bitrate_kbs * 1000));
+
+  uint32_t max_bitrate = max_bitrate_kbs * 1000;
+  if (bitrate > max_bitrate) {
+    DVLOG(1) << "Target bitrate: " << bitrate << " exceeds Max: " << max_bitrate
+             << " bit/s";
+    return false;
+  }
+
+  if (framesize_in_mbs > H264LevelToMaxFS(level)) {
+    DVLOG(1) << "Target frame size: " << framesize_in_mbs
+             << " exceeds Max: " << H264LevelToMaxFS(level) << " Macroblocks";
+    return false;
+  }
+
+  uint32_t mbps = framesize_in_mbs * framerate;
+  if (mbps > H264LevelToMaxMBPS(level)) {
+    DVLOG(1) << "Target macroblock processing rate: " << mbps
+             << " exceeds Max: " << H264LevelToMaxMBPS(level) << "Macroblock/s";
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace media
diff --git a/media/video/h264_level_limits.h b/media/video/h264_level_limits.h
new file mode 100644
index 0000000..0772dd2
--- /dev/null
+++ b/media/video/h264_level_limits.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 MEDIA_VIDEO_H264_LEVEL_LIMITS_H_
+#define MEDIA_VIDEO_H264_LEVEL_LIMITS_H_
+
+#include <stddef.h>
+
+#include "media/base/media_export.h"
+#include "media/base/video_codecs.h"
+
+namespace media {
+
+// Get max macroblock processing rate in macroblocks per second (MaxMBPS) from
+// level. The abbreviation is as per spec table A-1.
+uint32_t MEDIA_EXPORT H264LevelToMaxMBPS(uint8_t level);
+
+// Get max frame size in macroblocks (MaxFS) from level. The abbreviation is as
+// per spec table A-1.
+uint32_t MEDIA_EXPORT H264LevelToMaxFS(uint8_t level);
+
+// Get max decoded picture buffer size in macroblocks (MaxDpbMbs) from level.
+// The abbreviation is as per spec table A-1.
+uint32_t MEDIA_EXPORT H264LevelToMaxDpbMbs(uint8_t level);
+
+// Get max video bitrate in kbit per second (MaxBR) from profile and level. The
+// abbreviation is as per spec table A-1.
+uint32_t MEDIA_EXPORT H264ProfileLevelToMaxBR(VideoCodecProfile profile,
+                                              uint8_t level);
+
+// Check whether |bitrate|, |framerate|, and |framesize_in_mbs| are valid from
+// the limits of |profile| and |level| from Table A-1 in spec.
+bool MEDIA_EXPORT CheckH264LevelLimits(VideoCodecProfile profile,
+                                       uint8_t level,
+                                       uint32_t bitrate,
+                                       uint32_t framerate,
+                                       uint32_t framesize_in_mbs);
+
+}  // namespace media
+
+#endif  // MEDIA_VIDEO_H264_LEVEL_LIMITS_H_
diff --git a/media/video/h264_parser.cc b/media/video/h264_parser.cc
index dc1c43f..f786ee3 100644
--- a/media/video/h264_parser.cc
+++ b/media/video/h264_parser.cc
@@ -79,6 +79,27 @@
   memset(this, 0, sizeof(*this));
 }
 
+// static
+void H264SPS::GetLevelConfigFromProfileLevel(VideoCodecProfile profile,
+                                             uint8_t level,
+                                             int* level_idc,
+                                             bool* constraint_set3_flag) {
+  // Spec A.3.1.
+  // Note: we always use h264_output_level = 9 to indicate Level 1b in
+  //       VideoEncodeAccelerator::Config, in order to tell apart from Level 1.1
+  //       which level IDC is also 11.
+  // For Baseline and Main profile, if requested level is Level 1b, set
+  // level_idc to 11 and constraint_set3_flag to true. Otherwise, set level_idc
+  // to 9 for Level 1b, and ten times level number for others.
+  if ((profile == H264PROFILE_BASELINE || profile == H264PROFILE_MAIN) &&
+      level == kLevelIDC1B) {
+    *level_idc = 11;
+    *constraint_set3_flag = true;
+  } else {
+    *level_idc = level;
+  }
+}
+
 H264SPS::H264SPS() {
   memset(this, 0, sizeof(*this));
 }
@@ -173,6 +194,33 @@
   }
 }
 
+uint8_t H264SPS::GetIndicatedLevel() const {
+  // Spec A.3.1 and A.3.2
+  // For Baseline, Constrained Baseline and Main profile, the indicated level is
+  // Level 1b if level_idc is equal to 11 and constraint_set3_flag is true.
+  if ((profile_idc == H264SPS::kProfileIDCBaseline ||
+       profile_idc == H264SPS::kProfileIDCConstrainedBaseline ||
+       profile_idc == H264SPS::kProfileIDCMain) &&
+      level_idc == 11 && constraint_set3_flag) {
+    return kLevelIDC1B;  // Level 1b
+  }
+
+  // Otherwise, the level_idc is equal to 9 for Level 1b, and others are equal
+  // to values of ten times the level numbers.
+  return base::checked_cast<uint8_t>(level_idc);
+}
+
+bool H264SPS::CheckIndicatedLevelWithinTarget(uint8_t target_level) const {
+  // See table A-1 in spec.
+  // Level 1.0 < 1b < 1.1 < 1.2 .... (in numeric order).
+  uint8_t level = GetIndicatedLevel();
+  if (target_level == kLevelIDC1p0)
+    return level == kLevelIDC1p0;
+  if (target_level == kLevelIDC1B)
+    return level == kLevelIDC1p0 || level == kLevelIDC1B;
+  return level <= target_level;
+}
+
 H264PPS::H264PPS() {
   memset(this, 0, sizeof(*this));
 }
diff --git a/media/video/h264_parser.h b/media/video/h264_parser.h
index 141a0c18..4a701fe6 100644
--- a/media/video/h264_parser.h
+++ b/media/video/h264_parser.h
@@ -92,6 +92,29 @@
     kProfileIDHigh444Predictive = 244,
   };
 
+  enum H264LevelIDC : uint8_t {
+    kLevelIDC1p0 = 10,
+    kLevelIDC1B = 9,
+    kLevelIDC1p1 = 11,
+    kLevelIDC1p2 = 12,
+    kLevelIDC1p3 = 13,
+    kLevelIDC2p0 = 20,
+    kLevelIDC2p1 = 21,
+    kLevelIDC2p2 = 22,
+    kLevelIDC3p0 = 30,
+    kLevelIDC3p1 = 31,
+    kLevelIDC3p2 = 32,
+    kLevelIDC4p0 = 40,
+    kLevelIDC4p1 = 41,
+    kLevelIDC4p2 = 42,
+    kLevelIDC5p0 = 50,
+    kLevelIDC5p1 = 51,
+    kLevelIDC5p2 = 52,
+    kLevelIDC6p0 = 60,
+    kLevelIDC6p1 = 61,
+    kLevelIDC6p2 = 62,
+  };
+
   enum AspectRatioIdc {
     kExtendedSar = 255,
   };
@@ -183,12 +206,27 @@
 
   int chroma_array_type;
 
+  // Get corresponding SPS |level_idc| and |constraint_set3_flag| value from
+  // requested |profile| and |level| (see Spec A.3.1).
+  static void GetLevelConfigFromProfileLevel(VideoCodecProfile profile,
+                                             uint8_t level,
+                                             int* level_idc,
+                                             bool* constraint_set3_flag);
+
   // Helpers to compute frequently-used values. These methods return
   // base::nullopt if they encounter integer overflow. They do not verify that
   // the results are in-spec for the given profile or level.
   base::Optional<gfx::Size> GetCodedSize() const;
   base::Optional<gfx::Rect> GetVisibleRect() const;
   VideoColorSpace GetColorSpace() const;
+
+  // Helper to compute indicated level from parsed SPS data. The value of
+  // indicated level would be included in H264LevelIDC enum representing the
+  // level as in name.
+  uint8_t GetIndicatedLevel() const;
+  // Helper to check if indicated level is lower than or equal to
+  // |target_level|.
+  bool CheckIndicatedLevelWithinTarget(uint8_t target_level) const;
 };
 
 struct MEDIA_EXPORT H264PPS {
diff --git a/media/video/video_encode_accelerator.cc b/media/video/video_encode_accelerator.cc
index e351e188..bf05cb72 100644
--- a/media/video/video_encode_accelerator.cc
+++ b/media/video/video_encode_accelerator.cc
@@ -47,8 +47,10 @@
       input_visible_size(input_visible_size),
       output_profile(output_profile),
       initial_bitrate(initial_bitrate),
-      initial_framerate(initial_framerate),
-      h264_output_level(h264_output_level),
+      initial_framerate(initial_framerate.value_or(
+          VideoEncodeAccelerator::kDefaultFramerate)),
+      h264_output_level(h264_output_level.value_or(
+          VideoEncodeAccelerator::kDefaultH264Level)),
       content_type(content_type) {}
 
 VideoEncodeAccelerator::Config::~Config() = default;
@@ -64,7 +66,8 @@
     str += base::StringPrintf(", initial_framerate: %u",
                               initial_framerate.value());
   }
-  if (h264_output_level) {
+  if (h264_output_level &&
+      VideoCodecProfileToVideoCodec(output_profile) == kCodecH264) {
     str += base::StringPrintf(", h264_output_level: %u",
                               h264_output_level.value());
   }
diff --git a/media/video/video_encode_accelerator.h b/media/video/video_encode_accelerator.h
index 2ea66e2..15b3f2e 100644
--- a/media/video/video_encode_accelerator.h
+++ b/media/video/video_encode_accelerator.h
@@ -21,6 +21,7 @@
 #include "media/base/video_bitrate_allocation.h"
 #include "media/base/video_decoder_config.h"
 #include "media/base/video_frame.h"
+#include "media/video/h264_parser.h"
 
 namespace media {
 
@@ -93,6 +94,12 @@
     kErrorMax = kPlatformFailureError
   };
 
+  // Unified default values for all VEA implementations.
+  enum {
+    kDefaultFramerate = 30,
+    kDefaultH264Level = H264SPS::kLevelIDC4p0,
+  };
+
   // Parameters required for VEA initialization.
   struct MEDIA_EXPORT Config {
     // Indicates if video content should be treated as a "normal" camera feed
@@ -129,14 +136,15 @@
     uint32_t initial_bitrate;
 
     // Initial encoding framerate in frames per second. This is optional and
-    // VideoEncodeAccelerator should use default framerate if not given.
+    // VideoEncodeAccelerator should use |kDefaultFramerate| if not given.
     base::Optional<uint32_t> initial_framerate;
 
     // Codec level of encoded output stream for H264 only. This value should
     // be aligned to the H264 standard definition of SPS.level_idc. The only
     // exception is in Main and Baseline profile we still use
     // |h264_output_level|=9 for Level 1b, which should set level_idc to 11 and
-    // constraint_set3_flag to 1. (Spec A.3.1 and A.3.2)
+    // constraint_set3_flag to 1 (Spec A.3.1 and A.3.2). This is optional and
+    // use |kDefaultH264Level| if not given.
     base::Optional<uint8_t> h264_output_level;
 
     // Indicates captured video (from a camera) or generated (screen grabber).
diff --git a/net/base/filename_util_internal.cc b/net/base/filename_util_internal.cc
index bf7a6a9..38ecd17 100644
--- a/net/base/filename_util_internal.cc
+++ b/net/base/filename_util_internal.cc
@@ -125,10 +125,9 @@
   if (!url.is_valid() || url.SchemeIs("about") || url.SchemeIs("data"))
     return std::string();
 
-  const std::string unescaped_url_filename = UnescapeURLComponent(
-      url.ExtractFileName(),
-      UnescapeRule::SPACES |
-          UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS);
+  std::string unescaped_url_filename;
+  UnescapeBinaryURLComponent(url.ExtractFileName(), UnescapeRule::NORMAL,
+                             &unescaped_url_filename);
 
   // The URL's path should be escaped UTF-8, but may not be.
   std::string decoded_filename = unescaped_url_filename;
diff --git a/net/base/filename_util_unittest.cc b/net/base/filename_util_unittest.cc
index 17fb283..7f150af 100644
--- a/net/base/filename_util_unittest.cc
+++ b/net/base/filename_util_unittest.cc
@@ -573,9 +573,14 @@
     {// A normal avi should get .avi and not .avi.avi
      __LINE__, "https://example.com/misc/2.avi", "", "", "", "video/x-msvideo",
      L"download", L"2.avi"},
-    {// Shouldn't unescape slashes.
+    {// Slashes are illegal, and should be replaced with underscores.
      __LINE__, "http://example.com/foo%2f..%2fbar.jpg", "", "", "",
-     "text/plain", L"download", L"foo%2f..%2fbar.jpg"},
+     "text/plain", L"download", L"foo_.._bar.jpg"},
+    {// "%00" decodes to the NUL byte, which is illegal and should be replaced
+     // with an underscore. (Note: This can't be tested with a URL, since "%00"
+     // is illegal in a URL. Only applies to Content-Disposition.)
+     __LINE__, "http://example.com/download.py", "filename=foo%00bar.jpg", "",
+     "", "text/plain", L"download", L"foo_bar.jpg"},
     {// Extension generation for C-D derived filenames.
      __LINE__, "", "filename=my-cat", "", "", "image/jpeg", L"download",
      L"my-cat"},
@@ -744,6 +749,15 @@
      __LINE__, "http://www.example.com/fooa%cc%88.txt", "", "", "",
      "image/jpeg", L"foo\xe4", L"foo\xe4.txt"},
 #endif
+
+    // U+3000 IDEOGRAPHIC SPACE (http://crbug.com/849794): In URL file name.
+    {__LINE__, "http://www.example.com/%E5%B2%A1%E3%80%80%E5%B2%A1.txt", "", "",
+     "", "text/plain", L"", L"\u5ca1\u3000\u5ca1.txt"},
+    // U+3000 IDEOGRAPHIC SPACE (http://crbug.com/849794): In
+    // Content-Disposition filename.
+    {__LINE__, "http://www.example.com/download.py",
+     "filename=%E5%B2%A1%E3%80%80%E5%B2%A1.txt", "utf-8", "", "text/plain", L"",
+     L"\u5ca1\u3000\u5ca1.txt"},
   };
 
   for (const auto& selection_test : selection_tests)
diff --git a/net/http/http_content_disposition.cc b/net/http/http_content_disposition.cc
index da72593..70d45f9 100644
--- a/net/http/http_content_disposition.cc
+++ b/net/http/http_content_disposition.cc
@@ -189,7 +189,7 @@
   // web browser.
 
   // What IE6/7 does: %-escaped UTF-8.
-  decoded_word = UnescapeURLComponent(encoded_word, UnescapeRule::SPACES);
+  UnescapeBinaryURLComponent(encoded_word, UnescapeRule::NORMAL, &decoded_word);
   if (decoded_word != encoded_word)
     *parse_result_flags |= HttpContentDisposition::HAS_PERCENT_ENCODED_STRINGS;
   if (base::IsStringUTF8(decoded_word)) {
diff --git a/remoting/protocol/ice_config.cc b/remoting/protocol/ice_config.cc
index 87bf7e8..32fd3a0 100644
--- a/remoting/protocol/ice_config.cc
+++ b/remoting/protocol/ice_config.cc
@@ -4,6 +4,8 @@
 
 #include "remoting/protocol/ice_config.h"
 
+#include <algorithm>
+
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
 #include "base/strings/string_number_conversions.h"
@@ -86,6 +88,21 @@
   return true;
 }
 
+// Returns the smallest specified value, or 0 if neither is specified.
+// A value is "specified" if it is greater than 0.
+int MinimumSpecified(int value1, int value2) {
+  if (value1 <= 0) {
+    // value1 is not specified, so return value2 (or 0).
+    return std::max(0, value2);
+  }
+  if (value2 <= 0) {
+    // value1 is specified, so return it directly.
+    return value1;
+  }
+  // Both values are specified, so return the minimum.
+  return std::min(value1, value2);
+}
+
 }  // namespace
 
 IceConfig::IceConfig() = default;
@@ -117,6 +134,7 @@
 
   // Parse iceServers list and store them in |ice_config|.
   bool errors_found = false;
+  ice_config.max_bitrate_kbps = 0;
   for (const auto& server : *ice_servers_list) {
     const base::DictionaryValue* server_dict;
     if (!server.GetAsDictionary(&server_dict)) {
@@ -136,6 +154,16 @@
     std::string password;
     server_dict->GetString("credential", &password);
 
+    // Compute the lowest specified bitrate of all the ICE servers.
+    // Ideally the bitrate would be stored per ICE server, but it is not
+    // possible (at the application level) to look up which particular
+    // ICE server was used for the P2P connection.
+    double new_bitrate_double;
+    if (server_dict->GetDouble("maxRateKbps", &new_bitrate_double)) {
+      ice_config.max_bitrate_kbps = MinimumSpecified(
+          ice_config.max_bitrate_kbps, static_cast<int>(new_bitrate_double));
+    }
+
     for (const auto& url : *urls_list) {
       std::string url_str;
       if (!url.GetAsString(&url_str)) {
diff --git a/remoting/protocol/ice_config.h b/remoting/protocol/ice_config.h
index 5831a5a9..38b9ef81 100644
--- a/remoting/protocol/ice_config.h
+++ b/remoting/protocol/ice_config.h
@@ -42,6 +42,10 @@
 
   // Standard TURN servers
   std::vector<cricket::RelayServerConfig> turn_servers;
+
+  // If greater than 0, the max bandwidth used for relayed connections should
+  // be set to this value.
+  int max_bitrate_kbps = 0;
 };
 
 }  // namespace protocol
diff --git a/remoting/protocol/ice_config_unittest.cc b/remoting/protocol/ice_config_unittest.cc
index ab7aa7c5..b315c44 100644
--- a/remoting/protocol/ice_config_unittest.cc
+++ b/remoting/protocol/ice_config_unittest.cc
@@ -26,7 +26,8 @@
       "        \"turns:the_server.com?transport=udp\""
       "      ],"
       "      \"username\": \"123\","
-      "      \"credential\": \"abc\""
+      "      \"credential\": \"abc\","
+      "      \"maxRateKbps\": 8000.0"
       "    },"
       "    {"
       "      \"urls\": ["
@@ -71,6 +72,7 @@
   EXPECT_EQ(rtc::SocketAddress("stun_server.com", 18344),
             config.stun_servers[0]);
   EXPECT_EQ(rtc::SocketAddress("1.2.3.4", 3478), config.stun_servers[1]);
+  EXPECT_EQ(8000.0, config.max_bitrate_kbps);
 }
 
 TEST(IceConfigTest, ParseDataEnvelope) {
@@ -126,5 +128,63 @@
   EXPECT_TRUE(config.is_null());
 }
 
+TEST(IceConfigTest, UnspecifiedMaxRate_IsZero) {
+  const char kTestConfigJson[] =
+      "{"
+      "  \"iceServers\": ["
+      "    {"
+      "      \"urls\": ["
+      "        \"stun:1.2.3.4\""
+      "      ]"
+      "    }"
+      "  ]"
+      "}";
+
+  IceConfig config = IceConfig::Parse(kTestConfigJson);
+  EXPECT_EQ(0, config.max_bitrate_kbps);
+}
+
+TEST(IceConfigTest, OneSpecifiedMaxRate_IsUsed) {
+  const char kTestConfigJson1[] =
+      "{"
+      "  \"iceServers\": ["
+      "    {"
+      "      \"urls\": ["
+      "        \"stun:1.2.3.4\""
+      "      ],"
+      "      \"maxRateKbps\": 1000.0"
+      "    },"
+      "    {"
+      "      \"urls\": ["
+      "        \"stun:1.2.3.4\""
+      "      ]"
+      "    }"
+      "  ]"
+      "}";
+
+  IceConfig config1 = IceConfig::Parse(kTestConfigJson1);
+  EXPECT_EQ(1000, config1.max_bitrate_kbps);
+
+  const char kTestConfigJson2[] =
+      "{"
+      "  \"iceServers\": ["
+      "    {"
+      "      \"urls\": ["
+      "        \"stun:1.2.3.4\""
+      "      ]"
+      "    },"
+      "    {"
+      "      \"urls\": ["
+      "        \"stun:1.2.3.4\""
+      "      ],"
+      "      \"maxRateKbps\": 2000.0"
+      "    }"
+      "  ]"
+      "}";
+
+  IceConfig config2 = IceConfig::Parse(kTestConfigJson2);
+  EXPECT_EQ(2000, config2.max_bitrate_kbps);
+}
+
 }  // namespace protocol
 }  // namespace remoting
diff --git a/remoting/protocol/transport_context.cc b/remoting/protocol/transport_context.cc
index 4ae8a88..6bae822 100644
--- a/remoting/protocol/transport_context.cc
+++ b/remoting/protocol/transport_context.cc
@@ -122,5 +122,10 @@
   }
 }
 
+int TransportContext::GetTurnMaxRateKbps() const {
+  DCHECK_EQ(relay_mode_, RelayMode::TURN);
+  return ice_config_[RelayMode::TURN].max_bitrate_kbps;
+}
+
 }  // namespace protocol
 }  // namespace remoting
diff --git a/remoting/protocol/transport_context.h b/remoting/protocol/transport_context.h
index 4df7a678..e93f4f9 100644
--- a/remoting/protocol/transport_context.h
+++ b/remoting/protocol/transport_context.h
@@ -101,6 +101,11 @@
   TransportRole role() const { return role_; }
   rtc::NetworkManager* network_manager() const { return network_manager_; }
 
+  // Returns the suggested bandwidth cap for TURN relay connections, or 0 if
+  // no rate-limit is set in the IceConfig. Currently this is not used for
+  // legacy GTURN connections.
+  int GetTurnMaxRateKbps() const;
+
  private:
   friend class base::RefCountedThreadSafe<TransportContext>;
 
diff --git a/remoting/protocol/webrtc_transport.cc b/remoting/protocol/webrtc_transport.cc
index 7f8df70..c3bf652 100644
--- a/remoting/protocol/webrtc_transport.cc
+++ b/remoting/protocol/webrtc_transport.cc
@@ -53,13 +53,6 @@
 // XML namespace for the transport elements.
 const char kTransportNamespace[] = "google:remoting:webrtc";
 
-// Bitrate cap applied to relay connections. This is done to prevent
-// large amounts of packet loss, since the Google TURN/relay server drops
-// packets to limit the connection to ~10Mbps. The rate-limiting behavior works
-// badly with WebRTC's bandwidth-estimation, which results in the host process
-// trying to send frames too rapidly over the connection.
-constexpr int kMaxBitrateOnRelayKbps = 8000;
-
 #if !defined(NDEBUG)
 // Command line switch used to disable signature verification.
 // TODO(sergeyu): Remove this flag.
@@ -757,6 +750,16 @@
   VLOG(0) << "Relay connection: " << (is_relay ? "true" : "false");
 
   if (is_relay) {
+    int max_bitrate_kbps = transport_context_->GetTurnMaxRateKbps();
+    if (max_bitrate_kbps <= 0) {
+      VLOG(0) << "No bitrate cap set.";
+      return;
+    }
+    VLOG(0) << "Capping bitrate to " << max_bitrate_kbps << "kbps.";
+
+    // Apply the suggested bitrate cap to prevent large amounts of packet loss.
+    // The Google TURN/relay server limits the connection speed by dropping
+    // packets, which may interact badly with WebRTC's bandwidth-estimation.
     auto senders = peer_connection()->GetSenders();
     for (rtc::scoped_refptr<webrtc::RtpSenderInterface> sender : senders) {
       // x-google-max-bitrate is only set for video codecs in the SDP exchange.
@@ -777,7 +780,7 @@
                    << sender->id();
       }
 
-      parameters.encodings[0].max_bitrate_bps = kMaxBitrateOnRelayKbps * 1000;
+      parameters.encodings[0].max_bitrate_bps = max_bitrate_kbps * 1000;
       sender->SetParameters(parameters);
     }
   }
diff --git a/services/catalog/entry.cc b/services/catalog/entry.cc
index b79dbfa0..b5ef481 100644
--- a/services/catalog/entry.cc
+++ b/services/catalog/entry.cc
@@ -189,10 +189,12 @@
       options_struct.can_connect_to_other_services_as_any_user =
           can_connect_to_other_services_as_any_user_value->GetBool();
 
-    if (const base::Value* allow_other_instance_names_value =
-            options->FindKey("allow_other_instance_names"))
-      options_struct.allow_other_instance_names =
-          allow_other_instance_names_value->GetBool();
+    if (const base::Value*
+            can_connect_to_other_services_with_any_instance_name_value =
+                options->FindKey(
+                    "can_connect_to_other_services_with_any_instance_name"))
+      options_struct.can_connect_to_other_services_with_any_instance_name =
+          can_connect_to_other_services_with_any_instance_name_value->GetBool();
 
     if (const base::Value* instance_for_client_process_value =
             options->FindKey("instance_for_client_process"))
diff --git a/services/catalog/entry_unittest.cc b/services/catalog/entry_unittest.cc
index ec360c6..78fe700f 100644
--- a/services/catalog/entry_unittest.cc
+++ b/services/catalog/entry_unittest.cc
@@ -76,7 +76,8 @@
   EXPECT_EQ(ServiceOptions::InstanceSharingType::SINGLETON,
             entry->options().instance_sharing);
   EXPECT_TRUE(entry->options().can_connect_to_other_services_as_any_user);
-  EXPECT_TRUE(entry->options().allow_other_instance_names);
+  EXPECT_TRUE(
+      entry->options().can_connect_to_other_services_with_any_instance_name);
   EXPECT_TRUE(entry->options().instance_for_client_process);
 
   EXPECT_EQ("", entry->sandbox_type());
diff --git a/services/catalog/service_options.h b/services/catalog/service_options.h
index b5f14fe..9c73e420 100644
--- a/services/catalog/service_options.h
+++ b/services/catalog/service_options.h
@@ -16,7 +16,7 @@
 
   InstanceSharingType instance_sharing = InstanceSharingType::NONE;
   bool can_connect_to_other_services_as_any_user = false;
-  bool allow_other_instance_names = false;
+  bool can_connect_to_other_services_with_any_instance_name = false;
   bool instance_for_client_process = false;
 };
 
diff --git a/services/catalog/test_data/options b/services/catalog/test_data/options
index 1917633..e3698b5 100644
--- a/services/catalog/test_data/options
+++ b/services/catalog/test_data/options
@@ -4,7 +4,7 @@
   "options": {
     "instance_sharing": "singleton",
     "can_connect_to_other_services_as_any_user": true,
-    "allow_other_instance_names": true,
+    "can_connect_to_other_services_with_any_instance_name": true,
     "instance_for_client_process": true
   },
   "interface_provider_specs": { }
diff --git a/services/network/cors/cors_url_loader.cc b/services/network/cors/cors_url_loader.cc
index 0bd18b7..8d95257d 100644
--- a/services/network/cors/cors_url_loader.cc
+++ b/services/network/cors/cors_url_loader.cc
@@ -27,7 +27,7 @@
 }
 
 bool NeedsPreflight(const ResourceRequest& request) {
-  if (!cors::IsCORSEnabledRequestMode(request.fetch_request_mode))
+  if (!IsCORSEnabledRequestMode(request.fetch_request_mode))
     return false;
 
   if (request.is_external_request)
@@ -46,13 +46,9 @@
   if (!IsCORSSafelistedMethod(request.method))
     return true;
 
-  for (const auto& header : request.headers.GetHeaderVector()) {
-    if (!IsCORSSafelistedHeader(header.key, header.value) &&
-        !IsForbiddenHeader(header.key)) {
-      return true;
-    }
-  }
-  return false;
+  return !CORSUnsafeNotForbiddenRequestHeaderNames(
+              request.headers.GetHeaderVector())
+              .empty();
 }
 
 }  // namespace
@@ -92,7 +88,7 @@
 
 void CORSURLLoader::Start() {
   if (fetch_cors_flag_ &&
-      cors::IsCORSEnabledRequestMode(request_.fetch_request_mode)) {
+      IsCORSEnabledRequestMode(request_.fetch_request_mode)) {
     // Username and password should be stripped in a CORS-enabled request.
     if (request_.url.has_username() || request_.url.has_password()) {
       GURL::Replacements replacements;
diff --git a/services/network/cors/preflight_controller.cc b/services/network/cors/preflight_controller.cc
index 629fd22..8828fa4 100644
--- a/services/network/cors/preflight_controller.cc
+++ b/services/network/cors/preflight_controller.cc
@@ -41,18 +41,11 @@
 //  - byte-lowercased
 std::string CreateAccessControlRequestHeadersHeader(
     const net::HttpRequestHeaders& headers) {
-  std::vector<std::string> filtered_headers;
-  for (const auto& header : headers.GetHeaderVector()) {
-    // Exclude CORS-safelisted headers.
-    if (cors::IsCORSSafelistedHeader(header.key, header.value))
-      continue;
-    // Exclude the forbidden headers because they may be added by the user
-    // agent. They must be checked separately and rejected for
-    // JavaScript-initiated requests.
-    if (cors::IsForbiddenHeader(header.key))
-      continue;
-    filtered_headers.push_back(base::ToLowerASCII(header.key));
-  }
+  // Exclude the forbidden headers because they may be added by the user
+  // agent. They must be checked separately and rejected for
+  // JavaScript-initiated requests.
+  std::vector<std::string> filtered_headers =
+      CORSUnsafeNotForbiddenRequestHeaderNames(headers.GetHeaderVector());
   if (filtered_headers.empty())
     return std::string();
 
@@ -88,18 +81,18 @@
   preflight_request->load_flags |= net::LOAD_DO_NOT_SEND_AUTH_DATA;
 
   preflight_request->headers.SetHeader(
-      cors::header_names::kAccessControlRequestMethod, request.method);
+      header_names::kAccessControlRequestMethod, request.method);
 
   std::string request_headers =
       CreateAccessControlRequestHeadersHeader(request.headers);
   if (!request_headers.empty()) {
     preflight_request->headers.SetHeader(
-        cors::header_names::kAccessControlRequestHeaders, request_headers);
+        header_names::kAccessControlRequestHeaders, request_headers);
   }
 
   if (request.is_external_request) {
     preflight_request->headers.SetHeader(
-        cors::header_names::kAccessControlRequestExternal, "true");
+        header_names::kAccessControlRequestExternal, "true");
   }
 
   DCHECK(request.request_initiator);
@@ -130,10 +123,9 @@
   // TODO(toyoshim): Reflect --allow-file-access-from-files flag.
   *detected_error_status = CheckPreflightAccess(
       final_url, head.headers->response_code(),
+      GetHeaderString(head.headers, header_names::kAccessControlAllowOrigin),
       GetHeaderString(head.headers,
-                      cors::header_names::kAccessControlAllowOrigin),
-      GetHeaderString(head.headers,
-                      cors::header_names::kAccessControlAllowCredentials),
+                      header_names::kAccessControlAllowCredentials),
       original_request.fetch_credentials_mode,
       tainted ? url::Origin() : *original_request.request_initiator,
       false /* allow_file_origin */);
diff --git a/services/network/cors/preflight_controller_unittest.cc b/services/network/cors/preflight_controller_unittest.cc
index a5cb266..e2f2e85 100644
--- a/services/network/cors/preflight_controller_unittest.cc
+++ b/services/network/cors/preflight_controller_unittest.cc
@@ -50,7 +50,7 @@
   EXPECT_EQ("null", header);
 
   EXPECT_TRUE(preflight->headers.GetHeader(
-      cors::header_names::kAccessControlRequestHeaders, &header));
+      header_names::kAccessControlRequestHeaders, &header));
   EXPECT_EQ("apple,content-type,kiwifruit,orange,strawberry", header);
 }
 
@@ -73,7 +73,7 @@
   // left out in the preflight request.
   std::string header;
   EXPECT_FALSE(preflight->headers.GetHeader(
-      cors::header_names::kAccessControlRequestHeaders, &header));
+      header_names::kAccessControlRequestHeaders, &header));
 }
 
 TEST(PreflightControllerCreatePreflightRequestTest, Credentials) {
@@ -108,7 +108,7 @@
   // Empty list also; see comment in test above.
   std::string header;
   EXPECT_FALSE(preflight->headers.GetHeader(
-      cors::header_names::kAccessControlRequestHeaders, &header));
+      header_names::kAccessControlRequestHeaders, &header));
 }
 
 TEST(PreflightControllerCreatePreflightRequestTest, IncludeNonSimpleHeader) {
@@ -123,7 +123,7 @@
 
   std::string header;
   EXPECT_TRUE(preflight->headers.GetHeader(
-      cors::header_names::kAccessControlRequestHeaders, &header));
+      header_names::kAccessControlRequestHeaders, &header));
   EXPECT_EQ("x-custom-header", header);
 }
 
@@ -141,7 +141,7 @@
 
   std::string header;
   EXPECT_TRUE(preflight->headers.GetHeader(
-      cors::header_names::kAccessControlRequestHeaders, &header));
+      header_names::kAccessControlRequestHeaders, &header));
   EXPECT_EQ("content-type", header);
 }
 
@@ -157,7 +157,7 @@
 
   std::string header;
   EXPECT_FALSE(preflight->headers.GetHeader(
-      cors::header_names::kAccessControlRequestHeaders, &header));
+      header_names::kAccessControlRequestHeaders, &header));
 }
 
 TEST(PreflightControllerCreatePreflightRequestTest, Tainted) {
@@ -256,7 +256,7 @@
           net::test_server::ShouldHandle(request, "/tainted")
               ? url::Origin()
               : url::Origin::Create(test_server_.base_url());
-      response->AddCustomHeader(cors::header_names::kAccessControlAllowOrigin,
+      response->AddCustomHeader(header_names::kAccessControlAllowOrigin,
                                 origin.Serialize());
       response->AddCustomHeader(header_names::kAccessControlAllowMethods,
                                 "GET, OPTIONS");
diff --git a/services/network/public/cpp/cors/cors.cc b/services/network/public/cpp/cors/cors.cc
index 0076d1b..b117c22 100644
--- a/services/network/public/cpp/cors/cors.cc
+++ b/services/network/public/cpp/cors/cors.cc
@@ -354,6 +354,10 @@
 }
 
 bool IsCORSSafelistedHeader(const std::string& name, const std::string& value) {
+  // If |value|’s length is greater than 128, then return false.
+  if (value.size() > 128)
+    return false;
+
   // https://fetch.spec.whatwg.org/#cors-safelisted-request-header
   // "A CORS-safelisted header is a header whose name is either one of `Accept`,
   // `Accept-Language`, and `Content-Language`, or whose name is
@@ -395,12 +399,89 @@
   if (lower_name == "save-data")
     return lower_value == "on";
 
+  if (lower_name == "accept") {
+    return (value.end() == std::find_if(value.begin(), value.end(), [](char c) {
+              return (c < 0x20 && c != 0x09) || c == 0x22 || c == 0x28 ||
+                     c == 0x29 || c == 0x3a || c == 0x3c || c == 0x3e ||
+                     c == 0x3f || c == 0x40 || c == 0x5b || c == 0x5c ||
+                     c == 0x5d || c == 0x7b || c == 0x7d || c >= 0x7f;
+            }));
+  }
+
+  if (lower_name == "accept-language" || lower_name == "content-language") {
+    return (value.end() == std::find_if(value.begin(), value.end(), [](char c) {
+              return !isalnum(c) && c != 0x20 && c != 0x2a && c != 0x2c &&
+                     c != 0x2d && c != 0x2e && c != 0x3b && c != 0x3d;
+            }));
+  }
+
   if (lower_name == "content-type")
     return IsCORSSafelistedLowerCaseContentType(lower_value);
 
   return true;
 }
 
+bool IsNoCORSSafelistedHeader(const std::string& name,
+                              const std::string& value) {
+  const std::string lower_name = base::ToLowerASCII(name);
+
+  if (lower_name != "accept" && lower_name != "accept-language" &&
+      lower_name != "content-language" && lower_name != "content-type") {
+    return false;
+  }
+
+  return IsCORSSafelistedHeader(lower_name, value);
+}
+
+std::vector<std::string> CORSUnsafeRequestHeaderNames(
+    const net::HttpRequestHeaders::HeaderVector& headers) {
+  std::vector<std::string> potentially_unsafe_names;
+  std::vector<std::string> header_names;
+
+  constexpr size_t kSafeListValueSizeMax = 1024;
+  size_t safe_list_value_size = 0;
+
+  for (const auto& header : headers) {
+    if (!IsCORSSafelistedHeader(header.key, header.value)) {
+      header_names.push_back(base::ToLowerASCII(header.key));
+    } else {
+      potentially_unsafe_names.push_back(base::ToLowerASCII(header.key));
+      safe_list_value_size += header.value.size();
+    }
+  }
+  if (safe_list_value_size > kSafeListValueSizeMax) {
+    header_names.insert(header_names.end(), potentially_unsafe_names.begin(),
+                        potentially_unsafe_names.end());
+  }
+  return header_names;
+}
+
+std::vector<std::string> CORSUnsafeNotForbiddenRequestHeaderNames(
+    const net::HttpRequestHeaders::HeaderVector& headers) {
+  std::vector<std::string> header_names;
+  std::vector<std::string> potentially_unsafe_names;
+
+  constexpr size_t kSafeListValueSizeMax = 1024;
+  size_t safe_list_value_size = 0;
+
+  for (const auto& header : headers) {
+    if (IsForbiddenHeader(header.key))
+      continue;
+
+    if (!IsCORSSafelistedHeader(header.key, header.value)) {
+      header_names.push_back(base::ToLowerASCII(header.key));
+    } else {
+      potentially_unsafe_names.push_back(base::ToLowerASCII(header.key));
+      safe_list_value_size += header.value.size();
+    }
+  }
+  if (safe_list_value_size > kSafeListValueSizeMax) {
+    header_names.insert(header_names.end(), potentially_unsafe_names.begin(),
+                        potentially_unsafe_names.end());
+  }
+  return header_names;
+}
+
 bool IsForbiddenMethod(const std::string& method) {
   static const std::vector<std::string> forbidden_methods = {"trace", "track",
                                                              "connect"};
diff --git a/services/network/public/cpp/cors/cors.h b/services/network/public/cpp/cors/cors.h
index ec91457..cbe6219 100644
--- a/services/network/public/cpp/cors/cors.h
+++ b/services/network/public/cpp/cors/cors.h
@@ -6,9 +6,11 @@
 #define SERVICES_NETWORK_PUBLIC_CPP_CORS_CORS_H_
 
 #include <string>
+#include <vector>
 
 #include "base/component_export.h"
 #include "base/optional.h"
+#include "net/http/http_request_headers.h"
 #include "services/network/public/cpp/cors/cors_error_status.h"
 #include "services/network/public/mojom/cors.mojom-shared.h"
 #include "services/network/public/mojom/fetch_api.mojom-shared.h"
@@ -117,6 +119,26 @@
 bool IsCORSSafelistedContentType(const std::string& name);
 COMPONENT_EXPORT(NETWORK_CPP)
 bool IsCORSSafelistedHeader(const std::string& name, const std::string& value);
+COMPONENT_EXPORT(NETWORK_CPP)
+bool IsNoCORSSafelistedHeader(const std::string& name,
+                              const std::string& value);
+
+// https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names
+// |headers| must not contain multiple headers for the same name.
+// The returned list is NOT sorted.
+// The returned list consists of lower-cased names.
+COMPONENT_EXPORT(NETWORK_CPP)
+std::vector<std::string> CORSUnsafeRequestHeaderNames(
+    const net::HttpRequestHeaders::HeaderVector& headers);
+
+// https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names
+// Returns header names which are not CORS-safelisted AND not forbidden.
+// |headers| must not contain multiple headers for the same name.
+// The returned list is NOT sorted.
+// The returned list consists of lower-cased names.
+COMPONENT_EXPORT(NETWORK_CPP)
+std::vector<std::string> CORSUnsafeNotForbiddenRequestHeaderNames(
+    const net::HttpRequestHeaders::HeaderVector& headers);
 
 // Checks forbidden method in the fetch spec.
 // See https://fetch.spec.whatwg.org/#forbidden-method.
diff --git a/services/network/public/cpp/cors/cors_unittest.cc b/services/network/public/cpp/cors/cors_unittest.cc
index 695b04fd..1bd3c49 100644
--- a/services/network/public/cpp/cors/cors_unittest.cc
+++ b/services/network/public/cpp/cors/cors_unittest.cc
@@ -4,27 +4,29 @@
 
 #include "services/network/public/cpp/cors/cors.h"
 
+#include <limits.h>
+
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
 namespace network {
-
+namespace cors {
 namespace {
 
 using CORSTest = testing::Test;
 
 TEST_F(CORSTest, CheckAccessDetectsInvalidResponse) {
-  base::Optional<CORSErrorStatus> error_status = cors::CheckAccess(
-      GURL(), 0 /* response_status_code */,
-      base::nullopt /* allow_origin_header */,
-      base::nullopt /* allow_credentials_header */,
-      network::mojom::FetchCredentialsMode::kOmit, url::Origin());
+  base::Optional<CORSErrorStatus> error_status =
+      CheckAccess(GURL(), 0 /* response_status_code */,
+                  base::nullopt /* allow_origin_header */,
+                  base::nullopt /* allow_credentials_header */,
+                  network::mojom::FetchCredentialsMode::kOmit, url::Origin());
   ASSERT_TRUE(error_status);
   EXPECT_EQ(mojom::CORSError::kInvalidResponse, error_status->cors_error);
 }
 
-// Tests if cors::CheckAccess detects kWildcardOriginNotAllowed error correctly.
+// Tests if CheckAccess detects kWildcardOriginNotAllowed error correctly.
 TEST_F(CORSTest, CheckAccessDetectsWildcardOriginNotAllowed) {
   const GURL response_url("http://example.com/data");
   const url::Origin origin = url::Origin::Create(GURL("http://google.com"));
@@ -33,24 +35,24 @@
 
   // Access-Control-Allow-Origin '*' works.
   base::Optional<CORSErrorStatus> error1 =
-      cors::CheckAccess(response_url, response_status_code,
-                        allow_all_header /* allow_origin_header */,
-                        base::nullopt /* allow_credentials_header */,
-                        network::mojom::FetchCredentialsMode::kOmit, origin);
+      CheckAccess(response_url, response_status_code,
+                  allow_all_header /* allow_origin_header */,
+                  base::nullopt /* allow_credentials_header */,
+                  network::mojom::FetchCredentialsMode::kOmit, origin);
   EXPECT_FALSE(error1);
 
   // Access-Control-Allow-Origin '*' should not be allowed if credentials mode
   // is kInclude.
   base::Optional<CORSErrorStatus> error2 =
-      cors::CheckAccess(response_url, response_status_code,
-                        allow_all_header /* allow_origin_header */,
-                        base::nullopt /* allow_credentials_header */,
-                        network::mojom::FetchCredentialsMode::kInclude, origin);
+      CheckAccess(response_url, response_status_code,
+                  allow_all_header /* allow_origin_header */,
+                  base::nullopt /* allow_credentials_header */,
+                  network::mojom::FetchCredentialsMode::kInclude, origin);
   ASSERT_TRUE(error2);
   EXPECT_EQ(mojom::CORSError::kWildcardOriginNotAllowed, error2->cors_error);
 }
 
-// Tests if cors::CheckAccess detects kMissingAllowOriginHeader error correctly.
+// Tests if CheckAccess detects kMissingAllowOriginHeader error correctly.
 TEST_F(CORSTest, CheckAccessDetectsMissingAllowOriginHeader) {
   const GURL response_url("http://example.com/data");
   const url::Origin origin = url::Origin::Create(GURL("http://google.com"));
@@ -58,15 +60,15 @@
 
   // Access-Control-Allow-Origin is missed.
   base::Optional<CORSErrorStatus> error =
-      cors::CheckAccess(response_url, response_status_code,
-                        base::nullopt /* allow_origin_header */,
-                        base::nullopt /* allow_credentials_header */,
-                        network::mojom::FetchCredentialsMode::kOmit, origin);
+      CheckAccess(response_url, response_status_code,
+                  base::nullopt /* allow_origin_header */,
+                  base::nullopt /* allow_credentials_header */,
+                  network::mojom::FetchCredentialsMode::kOmit, origin);
   ASSERT_TRUE(error);
   EXPECT_EQ(mojom::CORSError::kMissingAllowOriginHeader, error->cors_error);
 }
 
-// Tests if cors::CheckAccess detects kMultipleAllowOriginValues error
+// Tests if CheckAccess detects kMultipleAllowOriginValues error
 // correctly.
 TEST_F(CORSTest, CheckAccessDetectsMultipleAllowOriginValues) {
   const GURL response_url("http://example.com/data");
@@ -75,55 +77,55 @@
 
   const std::string space_separated_multiple_origins(
       "http://example.com http://another.example.com");
-  base::Optional<CORSErrorStatus> error1 = cors::CheckAccess(
-      response_url, response_status_code,
-      space_separated_multiple_origins /* allow_origin_header */,
-      base::nullopt /* allow_credentials_header */,
-      network::mojom::FetchCredentialsMode::kOmit, origin);
+  base::Optional<CORSErrorStatus> error1 =
+      CheckAccess(response_url, response_status_code,
+                  space_separated_multiple_origins /* allow_origin_header */,
+                  base::nullopt /* allow_credentials_header */,
+                  network::mojom::FetchCredentialsMode::kOmit, origin);
   ASSERT_TRUE(error1);
   EXPECT_EQ(mojom::CORSError::kMultipleAllowOriginValues, error1->cors_error);
 
   const std::string comma_separated_multiple_origins(
       "http://example.com,http://another.example.com");
-  base::Optional<CORSErrorStatus> error2 = cors::CheckAccess(
-      response_url, response_status_code,
-      comma_separated_multiple_origins /* allow_origin_header */,
-      base::nullopt /* allow_credentials_header */,
-      network::mojom::FetchCredentialsMode::kOmit, origin);
+  base::Optional<CORSErrorStatus> error2 =
+      CheckAccess(response_url, response_status_code,
+                  comma_separated_multiple_origins /* allow_origin_header */,
+                  base::nullopt /* allow_credentials_header */,
+                  network::mojom::FetchCredentialsMode::kOmit, origin);
   ASSERT_TRUE(error2);
   EXPECT_EQ(mojom::CORSError::kMultipleAllowOriginValues, error2->cors_error);
 }
 
-// Tests if cors::CheckAccess detects kInvalidAllowOriginValue error correctly.
+// Tests if CheckAccess detects kInvalidAllowOriginValue error correctly.
 TEST_F(CORSTest, CheckAccessDetectsInvalidAllowOriginValue) {
   const GURL response_url("http://example.com/data");
   const url::Origin origin = url::Origin::Create(GURL("http://google.com"));
   const int response_status_code = 200;
 
   base::Optional<CORSErrorStatus> error =
-      cors::CheckAccess(response_url, response_status_code,
-                        std::string("invalid.origin") /* allow_origin_header */,
-                        base::nullopt /* allow_credentials_header */,
-                        network::mojom::FetchCredentialsMode::kOmit, origin);
+      CheckAccess(response_url, response_status_code,
+                  std::string("invalid.origin") /* allow_origin_header */,
+                  base::nullopt /* allow_credentials_header */,
+                  network::mojom::FetchCredentialsMode::kOmit, origin);
   ASSERT_TRUE(error);
   EXPECT_EQ(mojom::CORSError::kInvalidAllowOriginValue, error->cors_error);
   EXPECT_EQ("invalid.origin", error->failed_parameter);
 }
 
-// Tests if cors::CheckAccess detects kAllowOriginMismatch error correctly.
+// Tests if CheckAccess detects kAllowOriginMismatch error correctly.
 TEST_F(CORSTest, CheckAccessDetectsAllowOriginMismatch) {
   const GURL response_url("http://example.com/data");
   const url::Origin origin = url::Origin::Create(GURL("http://google.com"));
   const int response_status_code = 200;
 
   base::Optional<CORSErrorStatus> error1 =
-      cors::CheckAccess(response_url, response_status_code,
-                        origin.Serialize() /* allow_origin_header */,
-                        base::nullopt /* allow_credentials_header */,
-                        network::mojom::FetchCredentialsMode::kOmit, origin);
+      CheckAccess(response_url, response_status_code,
+                  origin.Serialize() /* allow_origin_header */,
+                  base::nullopt /* allow_credentials_header */,
+                  network::mojom::FetchCredentialsMode::kOmit, origin);
   ASSERT_FALSE(error1);
 
-  base::Optional<CORSErrorStatus> error2 = cors::CheckAccess(
+  base::Optional<CORSErrorStatus> error2 = CheckAccess(
       response_url, response_status_code,
       std::string("http://not.google.com") /* allow_origin_header */,
       base::nullopt /* allow_credentials_header */,
@@ -137,37 +139,37 @@
   const url::Origin null_origin;
   EXPECT_EQ(null_string, null_origin.Serialize());
 
-  base::Optional<CORSErrorStatus> error3 = cors::CheckAccess(
+  base::Optional<CORSErrorStatus> error3 = CheckAccess(
       response_url, response_status_code, null_string /* allow_origin_header */,
       base::nullopt /* allow_credentials_header */,
       network::mojom::FetchCredentialsMode::kOmit, null_origin);
   EXPECT_FALSE(error3);
 }
 
-// Tests if cors::CheckAccess detects kInvalidAllowCredentials error correctly.
+// Tests if CheckAccess detects kInvalidAllowCredentials error correctly.
 TEST_F(CORSTest, CheckAccessDetectsInvalidAllowCredential) {
   const GURL response_url("http://example.com/data");
   const url::Origin origin = url::Origin::Create(GURL("http://google.com"));
   const int response_status_code = 200;
 
   base::Optional<CORSErrorStatus> error1 =
-      cors::CheckAccess(response_url, response_status_code,
-                        origin.Serialize() /* allow_origin_header */,
-                        std::string("true") /* allow_credentials_header */,
-                        network::mojom::FetchCredentialsMode::kInclude, origin);
+      CheckAccess(response_url, response_status_code,
+                  origin.Serialize() /* allow_origin_header */,
+                  std::string("true") /* allow_credentials_header */,
+                  network::mojom::FetchCredentialsMode::kInclude, origin);
   ASSERT_FALSE(error1);
 
   base::Optional<CORSErrorStatus> error2 =
-      cors::CheckAccess(response_url, response_status_code,
-                        origin.Serialize() /* allow_origin_header */,
-                        std::string("fuga") /* allow_credentials_header */,
-                        network::mojom::FetchCredentialsMode::kInclude, origin);
+      CheckAccess(response_url, response_status_code,
+                  origin.Serialize() /* allow_origin_header */,
+                  std::string("fuga") /* allow_credentials_header */,
+                  network::mojom::FetchCredentialsMode::kInclude, origin);
   ASSERT_TRUE(error2);
   EXPECT_EQ(mojom::CORSError::kInvalidAllowCredentials, error2->cors_error);
   EXPECT_EQ("fuga", error2->failed_parameter);
 }
 
-// Tests if cors::CheckRedirectLocation detects kCORSDisabledScheme and
+// Tests if CheckRedirectLocation detects kCORSDisabledScheme and
 // kRedirectContainsCredentials errors correctly.
 TEST_F(CORSTest, CheckRedirectLocation) {
   struct TestCase {
@@ -277,30 +279,30 @@
                  << ", tainted: " << test.tainted);
 
     EXPECT_EQ(test.expectation,
-              cors::CheckRedirectLocation(test.url, test.request_mode, origin,
-                                          test.cors_flag, test.tainted));
+              CheckRedirectLocation(test.url, test.request_mode, origin,
+                                    test.cors_flag, test.tainted));
   }
 }
 
 TEST_F(CORSTest, CheckPreflightDetectsErrors) {
-  EXPECT_FALSE(cors::CheckPreflight(200));
-  EXPECT_FALSE(cors::CheckPreflight(299));
+  EXPECT_FALSE(CheckPreflight(200));
+  EXPECT_FALSE(CheckPreflight(299));
 
-  base::Optional<mojom::CORSError> error1 = cors::CheckPreflight(300);
+  base::Optional<mojom::CORSError> error1 = CheckPreflight(300);
   ASSERT_TRUE(error1);
   EXPECT_EQ(mojom::CORSError::kPreflightInvalidStatus, *error1);
 
-  EXPECT_FALSE(cors::CheckExternalPreflight(std::string("true")));
+  EXPECT_FALSE(CheckExternalPreflight(std::string("true")));
 
   base::Optional<CORSErrorStatus> error2 =
-      cors::CheckExternalPreflight(base::nullopt);
+      CheckExternalPreflight(base::nullopt);
   ASSERT_TRUE(error2);
   EXPECT_EQ(mojom::CORSError::kPreflightMissingAllowExternal,
             error2->cors_error);
   EXPECT_EQ("", error2->failed_parameter);
 
   base::Optional<CORSErrorStatus> error3 =
-      cors::CheckExternalPreflight(std::string("TRUE"));
+      CheckExternalPreflight(std::string("TRUE"));
   ASSERT_TRUE(error3);
   EXPECT_EQ(mojom::CORSError::kPreflightInvalidAllowExternal,
             error3->cors_error);
@@ -318,148 +320,386 @@
 
   // CORS flag is false, same-origin request
   EXPECT_EQ(FetchResponseType::kBasic,
-            cors::CalculateResponseTainting(
+            CalculateResponseTainting(
                 same_origin_url, FetchRequestMode::kSameOrigin, origin, false));
   EXPECT_EQ(FetchResponseType::kBasic,
-            cors::CalculateResponseTainting(
+            CalculateResponseTainting(
                 same_origin_url, FetchRequestMode::kNoCORS, origin, false));
   EXPECT_EQ(FetchResponseType::kBasic,
-            cors::CalculateResponseTainting(
-                same_origin_url, FetchRequestMode::kCORS, origin, false));
+            CalculateResponseTainting(same_origin_url, FetchRequestMode::kCORS,
+                                      origin, false));
   EXPECT_EQ(FetchResponseType::kBasic,
-            cors::CalculateResponseTainting(
+            CalculateResponseTainting(
                 same_origin_url, FetchRequestMode::kCORSWithForcedPreflight,
                 origin, false));
   EXPECT_EQ(FetchResponseType::kBasic,
-            cors::CalculateResponseTainting(
+            CalculateResponseTainting(
                 same_origin_url, FetchRequestMode::kNavigate, origin, false));
 
   // CORS flag is false, cross-origin request
   EXPECT_EQ(FetchResponseType::kOpaque,
-            cors::CalculateResponseTainting(
+            CalculateResponseTainting(
                 cross_origin_url, FetchRequestMode::kNoCORS, origin, false));
   EXPECT_EQ(FetchResponseType::kBasic,
-            cors::CalculateResponseTainting(
+            CalculateResponseTainting(
                 cross_origin_url, FetchRequestMode::kNavigate, origin, false));
 
   // CORS flag is true, same-origin request
   EXPECT_EQ(FetchResponseType::kCORS,
-            cors::CalculateResponseTainting(
-                same_origin_url, FetchRequestMode::kCORS, origin, true));
+            CalculateResponseTainting(same_origin_url, FetchRequestMode::kCORS,
+                                      origin, true));
   EXPECT_EQ(FetchResponseType::kCORS,
-            cors::CalculateResponseTainting(
+            CalculateResponseTainting(
                 same_origin_url, FetchRequestMode::kCORSWithForcedPreflight,
                 origin, true));
 
   // CORS flag is true, cross-origin request
   EXPECT_EQ(FetchResponseType::kCORS,
-            cors::CalculateResponseTainting(
-                cross_origin_url, FetchRequestMode::kCORS, origin, true));
+            CalculateResponseTainting(cross_origin_url, FetchRequestMode::kCORS,
+                                      origin, true));
   EXPECT_EQ(FetchResponseType::kCORS,
-            cors::CalculateResponseTainting(
+            CalculateResponseTainting(
                 cross_origin_url, FetchRequestMode::kCORSWithForcedPreflight,
                 origin, true));
 
   // Origin is not provided.
   EXPECT_EQ(FetchResponseType::kBasic,
-            cors::CalculateResponseTainting(
+            CalculateResponseTainting(
                 same_origin_url, FetchRequestMode::kNoCORS, no_origin, false));
   EXPECT_EQ(
       FetchResponseType::kBasic,
-      cors::CalculateResponseTainting(
-          same_origin_url, FetchRequestMode::kNavigate, no_origin, false));
+      CalculateResponseTainting(same_origin_url, FetchRequestMode::kNavigate,
+                                no_origin, false));
   EXPECT_EQ(FetchResponseType::kBasic,
-            cors::CalculateResponseTainting(
+            CalculateResponseTainting(
                 cross_origin_url, FetchRequestMode::kNoCORS, no_origin, false));
   EXPECT_EQ(
       FetchResponseType::kBasic,
-      cors::CalculateResponseTainting(
-          cross_origin_url, FetchRequestMode::kNavigate, no_origin, false));
+      CalculateResponseTainting(cross_origin_url, FetchRequestMode::kNavigate,
+                                no_origin, false));
 }
 
-TEST_F(CORSTest, CheckCORSSafelist) {
+TEST_F(CORSTest, SafelistedMethod) {
   // Method check should be case-insensitive.
-  EXPECT_TRUE(cors::IsCORSSafelistedMethod("get"));
-  EXPECT_TRUE(cors::IsCORSSafelistedMethod("Get"));
-  EXPECT_TRUE(cors::IsCORSSafelistedMethod("GET"));
-  EXPECT_TRUE(cors::IsCORSSafelistedMethod("HEAD"));
-  EXPECT_TRUE(cors::IsCORSSafelistedMethod("POST"));
-  EXPECT_FALSE(cors::IsCORSSafelistedMethod("OPTIONS"));
+  EXPECT_TRUE(IsCORSSafelistedMethod("get"));
+  EXPECT_TRUE(IsCORSSafelistedMethod("Get"));
+  EXPECT_TRUE(IsCORSSafelistedMethod("GET"));
+  EXPECT_TRUE(IsCORSSafelistedMethod("HEAD"));
+  EXPECT_TRUE(IsCORSSafelistedMethod("POST"));
+  EXPECT_FALSE(IsCORSSafelistedMethod("OPTIONS"));
+}
 
-  // Content-Type check should be case-insensitive, and should ignore spaces and
-  // parameters such as charset after a semicolon.
+TEST_F(CORSTest, SafelistedHeader) {
+  // See SafelistedAccept/AcceptLanguage/ContentLanguage/ContentType also.
+
+  EXPECT_TRUE(IsCORSSafelistedHeader("accept", "foo"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("foo", "bar"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("user-agent", "foo"));
+}
+
+TEST_F(CORSTest, SafelistedAccept) {
+  EXPECT_TRUE(IsCORSSafelistedHeader("accept", "text/html"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("AccepT", "text/html"));
+
+  constexpr char kAllowed[] =
+      "\t !#$%&'*+,-./0123456789;="
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~";
+  for (int i = CHAR_MIN; i <= CHAR_MAX; ++i) {
+    SCOPED_TRACE(testing::Message() << "c = static_cast<char>(" << i << ")");
+    char c = static_cast<char>(i);
+    // 1 for the trailing null character.
+    auto* end = kAllowed + base::size(kAllowed) - 1;
+    EXPECT_EQ(std::find(kAllowed, end, c) != end,
+              IsCORSSafelistedHeader("accept", std::string(1, c)));
+    EXPECT_EQ(std::find(kAllowed, end, c) != end,
+              IsCORSSafelistedHeader("AccepT", std::string(1, c)));
+  }
+
+  EXPECT_TRUE(IsCORSSafelistedHeader("accept", std::string(128, 'a')));
+  EXPECT_FALSE(IsCORSSafelistedHeader("accept", std::string(129, 'a')));
+  EXPECT_TRUE(IsCORSSafelistedHeader("AccepT", std::string(128, 'a')));
+  EXPECT_FALSE(IsCORSSafelistedHeader("AccepT", std::string(129, 'a')));
+}
+
+TEST_F(CORSTest, SafelistedAcceptLanguage) {
+  EXPECT_TRUE(IsCORSSafelistedHeader("accept-language", "en,ja"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("aCcEPT-lAngUAge", "en,ja"));
+
+  constexpr char kAllowed[] =
+      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz *,-.;=";
+  for (int i = CHAR_MIN; i <= CHAR_MAX; ++i) {
+    SCOPED_TRACE(testing::Message() << "c = static_cast<char>(" << i << ")");
+    char c = static_cast<char>(i);
+    // 1 for the trailing null character.
+    auto* end = kAllowed + base::size(kAllowed) - 1;
+    EXPECT_EQ(std::find(kAllowed, end, c) != end,
+              IsCORSSafelistedHeader("aCcEPT-lAngUAge", std::string(1, c)));
+  }
+  EXPECT_TRUE(IsCORSSafelistedHeader("accept-language", std::string(128, 'a')));
+  EXPECT_FALSE(
+      IsCORSSafelistedHeader("accept-language", std::string(129, 'a')));
+  EXPECT_TRUE(IsCORSSafelistedHeader("aCcEPT-lAngUAge", std::string(128, 'a')));
+  EXPECT_FALSE(
+      IsCORSSafelistedHeader("aCcEPT-lAngUAge", std::string(129, 'a')));
+}
+
+TEST_F(CORSTest, SafelistedContentLanguage) {
+  EXPECT_TRUE(IsCORSSafelistedHeader("content-language", "en,ja"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("cONTent-LANguaGe", "en,ja"));
+
+  constexpr char kAllowed[] =
+      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz *,-.;=";
+  for (int i = CHAR_MIN; i <= CHAR_MAX; ++i) {
+    SCOPED_TRACE(testing::Message() << "c = static_cast<char>(" << i << ")");
+    char c = static_cast<char>(i);
+    // 1 for the trailing null character.
+    auto* end = kAllowed + base::size(kAllowed) - 1;
+    EXPECT_EQ(std::find(kAllowed, end, c) != end,
+              IsCORSSafelistedHeader("content-language", std::string(1, c)));
+    EXPECT_EQ(std::find(kAllowed, end, c) != end,
+              IsCORSSafelistedHeader("cONTent-LANguaGe", std::string(1, c)));
+  }
   EXPECT_TRUE(
-      cors::IsCORSSafelistedContentType("application/x-www-form-urlencoded"));
-  EXPECT_TRUE(cors::IsCORSSafelistedContentType("multipart/form-data"));
-  EXPECT_TRUE(cors::IsCORSSafelistedContentType("text/plain"));
-  EXPECT_TRUE(cors::IsCORSSafelistedContentType("TEXT/PLAIN"));
-  EXPECT_TRUE(cors::IsCORSSafelistedContentType("text/plain;charset=utf-8"));
-  EXPECT_TRUE(cors::IsCORSSafelistedContentType(" text/plain ;charset=utf-8"));
-  EXPECT_FALSE(cors::IsCORSSafelistedContentType("text/html"));
+      IsCORSSafelistedHeader("content-language", std::string(128, 'a')));
+  EXPECT_FALSE(
+      IsCORSSafelistedHeader("content-language", std::string(129, 'a')));
+  EXPECT_TRUE(
+      IsCORSSafelistedHeader("cONTent-LANguaGe", std::string(128, 'a')));
+  EXPECT_FALSE(
+      IsCORSSafelistedHeader("cONTent-LANguaGe", std::string(129, 'a')));
+}
 
-  // Header check should be case-insensitive. Value must be considered only for
-  // Content-Type.
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("accept", "text/html"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("Accept-Language", "en"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("Content-Language", "ja"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("SAVE-DATA", "on"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("Intervention", ""));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("Cache-Control", ""));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("Content-Type", "text/plain"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("Content-Type", "image/png"));
+TEST_F(CORSTest, SafelistedContentType) {
+  EXPECT_TRUE(IsCORSSafelistedHeader("content-type", "text/plain"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("CoNtEnt-TyPE", "text/plain"));
+  EXPECT_TRUE(
+      IsCORSSafelistedHeader("content-type", "text/plain; charset=utf-8"));
+  EXPECT_TRUE(
+      IsCORSSafelistedHeader("content-type", "  text/plain ; charset=UTF-8"));
+  EXPECT_TRUE(
+      IsCORSSafelistedHeader("content-type", "text/plain; param=BOGUS"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("content-type",
+                                     "application/x-www-form-urlencoded"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("content-type", "multipart/form-data"));
+
+  EXPECT_TRUE(IsCORSSafelistedHeader("content-type", "Text/plain"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("content-type", "tEXT/PLAIN"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("content-type", "text/html"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("CoNtEnt-TyPE", "text/html"));
+
+  EXPECT_FALSE(IsCORSSafelistedHeader("content-type", "image/png"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("CoNtEnt-TyPE", "image/png"));
+  EXPECT_TRUE(IsCORSSafelistedHeader(
+      "content-type", "text/plain; charset=" + std::string(108, 'a')));
+  EXPECT_TRUE(IsCORSSafelistedHeader(
+      "cONTent-tYPE", "text/plain; charset=" + std::string(108, 'a')));
+  EXPECT_FALSE(IsCORSSafelistedHeader(
+      "content-type", "text/plain; charset=" + std::string(109, 'a')));
+  EXPECT_FALSE(IsCORSSafelistedHeader(
+      "cONTent-tYPE", "text/plain; charset=" + std::string(109, 'a')));
 }
 
 TEST_F(CORSTest, CheckCORSClientHintsSafelist) {
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("device-memory", ""));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("device-memory", "abc"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("device-memory", "1.25"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("DEVICE-memory", "1.25"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("device-memory", "1.25-2.5"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("device-memory", "-1.25"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("device-memory", "1e2"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("device-memory", "inf"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("device-memory", "-2.3"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("device-memory", "NaN"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("DEVICE-memory", "1.25.3"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("DEVICE-memory", "1."));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("DEVICE-memory", ".1"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("DEVICE-memory", "."));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("DEVICE-memory", "1"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", ""));
+  EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "abc"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("device-memory", "1.25"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("DEVICE-memory", "1.25"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "1.25-2.5"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "-1.25"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "1e2"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "inf"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "-2.3"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "NaN"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("DEVICE-memory", "1.25.3"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("DEVICE-memory", "1."));
+  EXPECT_FALSE(IsCORSSafelistedHeader("DEVICE-memory", ".1"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("DEVICE-memory", "."));
+  EXPECT_TRUE(IsCORSSafelistedHeader("DEVICE-memory", "1"));
 
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("dpr", ""));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("dpr", "abc"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("dpr", "1.25"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("Dpr", "1.25"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("dpr", "1.25-2.5"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("dpr", "-1.25"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("dpr", "1e2"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("dpr", "inf"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("dpr", "-2.3"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("dpr", "NaN"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("dpr", "1.25.3"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("dpr", "1."));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("dpr", ".1"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("dpr", "."));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("dpr", "1"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("dpr", ""));
+  EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "abc"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("dpr", "1.25"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("Dpr", "1.25"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "1.25-2.5"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "-1.25"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "1e2"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "inf"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "-2.3"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "NaN"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "1.25.3"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "1."));
+  EXPECT_FALSE(IsCORSSafelistedHeader("dpr", ".1"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "."));
+  EXPECT_TRUE(IsCORSSafelistedHeader("dpr", "1"));
 
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("width", ""));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("width", "abc"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("width", "125"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("width", "1"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("WIDTH", "125"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("width", "125.2"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("width", "-125"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("width", "2147483648"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("width", ""));
+  EXPECT_FALSE(IsCORSSafelistedHeader("width", "abc"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("width", "125"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("width", "1"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("WIDTH", "125"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("width", "125.2"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("width", "-125"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("width", "2147483648"));
 
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("viewport-width", ""));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("viewport-width", "abc"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("viewport-width", "125"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("viewport-width", "1"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("viewport-Width", "125"));
-  EXPECT_FALSE(cors::IsCORSSafelistedHeader("viewport-width", "125.2"));
-  EXPECT_TRUE(cors::IsCORSSafelistedHeader("viewport-width", "2147483648"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("viewport-width", ""));
+  EXPECT_FALSE(IsCORSSafelistedHeader("viewport-width", "abc"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("viewport-width", "125"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("viewport-width", "1"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("viewport-Width", "125"));
+  EXPECT_FALSE(IsCORSSafelistedHeader("viewport-width", "125.2"));
+  EXPECT_TRUE(IsCORSSafelistedHeader("viewport-width", "2147483648"));
+}
+
+TEST_F(CORSTest, CORSUnsafeRequestHeaderNames) {
+  // Needed because initializer list is not allowed for a macro argument.
+  using List = std::vector<std::string>;
+
+  // Empty => Empty
+  EXPECT_EQ(CORSUnsafeRequestHeaderNames({}), List({}));
+
+  // Some headers are safelisted.
+  EXPECT_EQ(CORSUnsafeRequestHeaderNames({{"content-type", "text/plain"},
+                                          {"dpr", "12345"},
+                                          {"aCCept", "en,ja"},
+                                          {"accept-charset", "utf-8"},
+                                          {"uSer-Agent", "foo"},
+                                          {"hogE", "fuga"}}),
+            List({"accept-charset", "user-agent", "hoge"}));
+
+  // All headers are not safelisted.
+  EXPECT_EQ(
+      CORSUnsafeRequestHeaderNames({{"content-type", "text/html"},
+                                    {"dpr", "123-45"},
+                                    {"aCCept", "en,ja"},
+                                    {"accept-charset", "utf-8"},
+                                    {"uSer-Agent", "foo"},
+                                    {"hogE", "fuga"}}),
+      List({"content-type", "dpr", "accept-charset", "user-agent", "hoge"}));
+
+  // |safelistValueSize| is 1024.
+  EXPECT_EQ(
+      CORSUnsafeRequestHeaderNames(
+          {{"content-type", "text/plain; charset=" + std::string(108, '1')},
+           {"accept", std::string(128, '1')},
+           {"accept-language", std::string(128, '1')},
+           {"content-language", std::string(128, '1')},
+           {"dpr", std::string(128, '1')},
+           {"device-memory", std::string(128, '1')},
+           {"save-data", "on"},
+           {"viewport-width", std::string(128, '1')},
+           {"width", std::string(126, '1')},
+           {"hogE", "fuga"}}),
+      List({"hoge"}));
+
+  // |safelistValueSize| is 1025.
+  EXPECT_EQ(
+      CORSUnsafeRequestHeaderNames(
+          {{"content-type", "text/plain; charset=" + std::string(108, '1')},
+           {"accept", std::string(128, '1')},
+           {"accept-language", std::string(128, '1')},
+           {"content-language", std::string(128, '1')},
+           {"dpr", std::string(128, '1')},
+           {"device-memory", std::string(128, '1')},
+           {"save-data", "on"},
+           {"viewport-width", std::string(128, '1')},
+           {"width", std::string(127, '1')},
+           {"hogE", "fuga"}}),
+      List({"hoge", "content-type", "accept", "accept-language",
+            "content-language", "dpr", "device-memory", "save-data",
+            "viewport-width", "width"}));
+
+  // |safelistValueSize| is 897 because "content-type" is not safelisted.
+  EXPECT_EQ(
+      CORSUnsafeRequestHeaderNames(
+          {{"content-type", "text/plain; charset=" + std::string(128, '1')},
+           {"accept", std::string(128, '1')},
+           {"accept-language", std::string(128, '1')},
+           {"content-language", std::string(128, '1')},
+           {"dpr", std::string(128, '1')},
+           {"device-memory", std::string(128, '1')},
+           {"save-data", "on"},
+           {"viewport-width", std::string(128, '1')},
+           {"width", std::string(127, '1')},
+           {"hogE", "fuga"}}),
+      List({"content-type", "hoge"}));
+}
+
+TEST_F(CORSTest, CORSUnsafeNotForbiddenRequestHeaderNames) {
+  // Needed because initializer list is not allowed for a macro argument.
+  using List = std::vector<std::string>;
+
+  // Empty => Empty
+  EXPECT_EQ(CORSUnsafeNotForbiddenRequestHeaderNames({}), List({}));
+
+  // "user-agent" is NOT forbidden per spec, but forbidden in Chromium.
+  EXPECT_EQ(
+      CORSUnsafeNotForbiddenRequestHeaderNames({{"content-type", "text/plain"},
+                                                {"dpr", "12345"},
+                                                {"aCCept", "en,ja"},
+                                                {"accept-charset", "utf-8"},
+                                                {"uSer-Agent", "foo"},
+                                                {"hogE", "fuga"}}),
+      List({"hoge"}));
+
+  EXPECT_EQ(
+      CORSUnsafeNotForbiddenRequestHeaderNames({{"content-type", "text/html"},
+                                                {"dpr", "123-45"},
+                                                {"aCCept", "en,ja"},
+                                                {"accept-charset", "utf-8"},
+                                                {"hogE", "fuga"}}),
+      List({"content-type", "dpr", "hoge"}));
+
+  // |safelistValueSize| is 1024.
+  EXPECT_EQ(
+      CORSUnsafeNotForbiddenRequestHeaderNames(
+          {{"content-type", "text/plain; charset=" + std::string(108, '1')},
+           {"accept", std::string(128, '1')},
+           {"accept-language", std::string(128, '1')},
+           {"content-language", std::string(128, '1')},
+           {"dpr", std::string(128, '1')},
+           {"device-memory", std::string(128, '1')},
+           {"save-data", "on"},
+           {"viewport-width", std::string(128, '1')},
+           {"width", std::string(126, '1')},
+           {"accept-charset", "utf-8"},
+           {"hogE", "fuga"}}),
+      List({"hoge"}));
+
+  // |safelistValueSize| is 1025.
+  EXPECT_EQ(
+      CORSUnsafeNotForbiddenRequestHeaderNames(
+          {{"content-type", "text/plain; charset=" + std::string(108, '1')},
+           {"accept", std::string(128, '1')},
+           {"accept-language", std::string(128, '1')},
+           {"content-language", std::string(128, '1')},
+           {"dpr", std::string(128, '1')},
+           {"device-memory", std::string(128, '1')},
+           {"save-data", "on"},
+           {"viewport-width", std::string(128, '1')},
+           {"width", std::string(127, '1')},
+           {"accept-charset", "utf-8"},
+           {"hogE", "fuga"}}),
+      List({"hoge", "content-type", "accept", "accept-language",
+            "content-language", "dpr", "device-memory", "save-data",
+            "viewport-width", "width"}));
+
+  // |safelistValueSize| is 897 because "content-type" is not safelisted.
+  EXPECT_EQ(
+      CORSUnsafeNotForbiddenRequestHeaderNames(
+          {{"content-type", "text/plain; charset=" + std::string(128, '1')},
+           {"accept", std::string(128, '1')},
+           {"accept-language", std::string(128, '1')},
+           {"content-language", std::string(128, '1')},
+           {"dpr", std::string(128, '1')},
+           {"device-memory", std::string(128, '1')},
+           {"save-data", "on"},
+           {"viewport-width", std::string(128, '1')},
+           {"width", std::string(127, '1')},
+           {"accept-charset", "utf-8"},
+           {"hogE", "fuga"}}),
+      List({"content-type", "hoge"}));
 }
 
 }  // namespace
-
+}  // namespace cors
 }  // namespace network
diff --git a/services/network/public/cpp/cors/preflight_result.cc b/services/network/public/cpp/cors/preflight_result.cc
index 3cf0f25..516e7ef7 100644
--- a/services/network/public/cpp/cors/preflight_result.cc
+++ b/services/network/public/cpp/cors/preflight_result.cc
@@ -136,20 +136,17 @@
   if (!credentials_ && headers_.find("*") != headers_.end())
     return base::nullopt;
 
-  for (const auto& header : headers.GetHeaderVector()) {
+  // Forbidden headers are forbidden to be used by JavaScript, and checked
+  // beforehand. But user-agents may add these headers internally, and it's
+  // fine.
+  for (const auto& name :
+       CORSUnsafeNotForbiddenRequestHeaderNames(headers.GetHeaderVector())) {
     // Header list check is performed in case-insensitive way. Here, we have a
     // parsed header list set in lower case, and search each header in lower
     // case.
-    const std::string key = base::ToLowerASCII(header.key);
-    if (headers_.find(key) == headers_.end() &&
-        !IsCORSSafelistedHeader(key, header.value)) {
-      // Forbidden headers are forbidden to be used by JavaScript, and checked
-      // beforehand. But user-agents may add these headers internally, and it's
-      // fine.
-      if (IsForbiddenHeader(key))
-        continue;
+    if (headers_.find(name) == headers_.end()) {
       return CORSErrorStatus(
-          mojom::CORSError::kHeaderDisallowedByPreflightResponse, header.key);
+          mojom::CORSError::kHeaderDisallowedByPreflightResponse, name);
     }
   }
   return base::nullopt;
diff --git a/services/network/public/cpp/cors/preflight_result_unittest.cc b/services/network/public/cpp/cors/preflight_result_unittest.cc
index 2a83e34af..b7084eb 100644
--- a/services/network/public/cpp/cors/preflight_result_unittest.cc
+++ b/services/network/public/cpp/cors/preflight_result_unittest.cc
@@ -135,15 +135,15 @@
     {"GET", "", mojom::FetchCredentialsMode::kOmit, "GET", "X-MY-HEADER:t",
      mojom::FetchCredentialsMode::kOmit,
      CORSErrorStatus(mojom::CORSError::kHeaderDisallowedByPreflightResponse,
-                     "X-MY-HEADER")},
+                     "x-my-header")},
     {"GET", "X-SOME-OTHER-HEADER", mojom::FetchCredentialsMode::kOmit, "GET",
      "X-MY-HEADER:t", mojom::FetchCredentialsMode::kOmit,
      CORSErrorStatus(mojom::CORSError::kHeaderDisallowedByPreflightResponse,
-                     "X-MY-HEADER")},
+                     "x-my-header")},
     {"GET", "X-MY-HEADER", mojom::FetchCredentialsMode::kOmit, "GET",
      "X-MY-HEADER:t\r\nY-MY-HEADER:t", mojom::FetchCredentialsMode::kOmit,
      CORSErrorStatus(mojom::CORSError::kHeaderDisallowedByPreflightResponse,
-                     "Y-MY-HEADER")},
+                     "y-my-header")},
 };
 
 TEST_F(PreflightResultTest, MaxAge) {
diff --git a/services/service_manager/manifest.json b/services/service_manager/manifest.json
index 15feb7b..20150a85 100644
--- a/services/service_manager/manifest.json
+++ b/services/service_manager/manifest.json
@@ -13,9 +13,6 @@
         // Clients requesting this class are allowed to register clients for
         // processes they launch themselves.
         "service_manager:client_process": [ ],
-        // Clients requesting this class are allowed to connect to other clients
-        // in specific process instance groups.
-        "service_manager:instance_name": [ ],
         "service_manager:block_wildcard": [ ],
 
         "service_manager:service_manager": [
diff --git a/services/service_manager/service_manager.cc b/services/service_manager/service_manager.cc
index 90e9538..52a1d0f8 100644
--- a/services/service_manager/service_manager.cc
+++ b/services/service_manager/service_manager.cc
@@ -48,7 +48,6 @@
 namespace {
 
 const char kCapability_ClientProcess[] = "service_manager:client_process";
-const char kCapability_InstanceName[] = "service_manager:instance_name";
 const char kCapability_ServiceManager[] = "service_manager:service_manager";
 
 bool Succeeded(mojom::ConnectResult result) {
@@ -621,17 +620,16 @@
                  << " running as: " << identity_.user_id()
                  << " attempting to connect to: " << target.name()
                  << " as: " << target.user_id() << " without "
-                 << " the service:service_manager{user_id} capability.";
+                 << " the 'can_connect_to_other_services_as_any_user' option.";
       return mojom::ConnectResult::ACCESS_DENIED;
     }
-    if (!target.instance().empty() &&
-        target.instance() != target.name() &&
-        !HasCapability(connection_spec, kCapability_InstanceName)) {
-      LOG(ERROR) << "Instance: " << identity_.name() << " attempting to "
-                 << "connect to " << target.name()
-                 << " using Instance name: " << target.instance()
-                 << " without the "
-                 << "service_manager{instance_name} capability.";
+    if (!target.instance().empty() && target.instance() != target.name() &&
+        !options_.can_connect_to_other_services_with_any_instance_name) {
+      LOG(ERROR)
+          << "Instance: " << identity_.name() << " attempting to "
+          << "connect to " << target.name()
+          << " using Instance name: " << target.instance() << " without the "
+          << " 'can_connect_to_other_services_with_any_instance_name' option.";
       return mojom::ConnectResult::ACCESS_DENIED;
     }
 
diff --git a/services/service_manager/tests/connect/connect_unittests_manifest.json b/services/service_manager/tests/connect/connect_unittests_manifest.json
index 5434489..a84bee3 100644
--- a/services/service_manager/tests/connect/connect_unittests_manifest.json
+++ b/services/service_manager/tests/connect/connect_unittests_manifest.json
@@ -1,7 +1,10 @@
 {
   "name": "connect_unittests",
   "display_name": "Connect Unittests",
-  "options": { "can_connect_to_other_services_as_any_user": true },
+  "options": {
+    "can_connect_to_other_services_as_any_user": true,
+    "can_connect_to_other_services_with_any_instance_name": true
+  },
   "interface_provider_specs": {
     "service_manager:connector": {
       "provides": {
@@ -26,10 +29,7 @@
           "connect_unittests:standalone_app",
           "connect_unittests:user_id_test"
         ],
-        "connect_test_singleton_app": [],
-        "service_manager": [
-          "service_manager:instance_name"
-        ]
+        "connect_test_singleton_app": []
       }
     }
   }
diff --git a/services/service_manager/tests/lifecycle/lifecycle_unittest_manifest.json b/services/service_manager/tests/lifecycle/lifecycle_unittest_manifest.json
index 251344d..5497ca8 100644
--- a/services/service_manager/tests/lifecycle/lifecycle_unittest_manifest.json
+++ b/services/service_manager/tests/lifecycle/lifecycle_unittest_manifest.json
@@ -8,7 +8,6 @@
         "lifecycle_unittest_parent": [ "lifecycle_unittest:parent" ],
         "service_manager": [
           "service_manager:service_manager",
-          "service_manager:instance_name",
           "service_manager:client_process"
         ]
       }
diff --git a/services/service_manager/tests/service_manager/service_manager_unittest_manifest.json b/services/service_manager/tests/service_manager/service_manager_unittest_manifest.json
index 21a15312..7e8d1ce1 100644
--- a/services/service_manager/tests/service_manager/service_manager_unittest_manifest.json
+++ b/services/service_manager/tests/service_manager/service_manager_unittest_manifest.json
@@ -2,7 +2,8 @@
   "name": "service_manager_unittest",
   "display_name": "Service Manager Unittest",
   "options": {
-    "instance_sharing" : "singleton"
+    "instance_sharing": "singleton",
+    "can_connect_to_other_services_with_any_instance_name": true
   },
   "interface_provider_specs": {
     "service_manager:connector": {
@@ -14,8 +15,7 @@
       "requires": {
         "service_manager": [
           "service_manager:service_manager",
-          "service_manager:client_process",
-          "service_manager:instance_name"
+          "service_manager:client_process"
         ],
         "service_manager_unittest_embedder": [
           "service_manager_unittest:embedder"
diff --git a/services/viz/privileged/interfaces/gl/gpu_host.mojom b/services/viz/privileged/interfaces/gl/gpu_host.mojom
index 11849c9..2e081bb 100644
--- a/services/viz/privileged/interfaces/gl/gpu_host.mojom
+++ b/services/viz/privileged/interfaces/gl/gpu_host.mojom
@@ -35,8 +35,10 @@
   // track of this decision in case the GPU process crashes.
   DisableGpuCompositing();
 
+  [EnableIf=is_win]
   SetChildSurface(gpu.mojom.SurfaceHandle parent,
                   gpu.mojom.SurfaceHandle child);
+
   StoreShaderToDisk(int32 client_id, string key, string shader);
 
   RecordLogMessage(int32 severity, string header, string message);
diff --git a/services/ws/gpu_host/gpu_host.cc b/services/ws/gpu_host/gpu_host.cc
index 24c3515..4606e3e 100644
--- a/services/ws/gpu_host/gpu_host.cc
+++ b/services/ws/gpu_host/gpu_host.cc
@@ -193,9 +193,9 @@
 
 void DefaultGpuHost::DisableGpuCompositing() {}
 
+#if defined(OS_WIN)
 void DefaultGpuHost::SetChildSurface(gpu::SurfaceHandle parent,
                                      gpu::SurfaceHandle child) {
-#if defined(OS_WIN)
   // Verify that |parent| was created by the window server.
   DWORD process_id = 0;
   DWORD thread_id = GetWindowThreadProcessId(parent, &process_id);
@@ -210,10 +210,8 @@
                                                                  child)) {
     OnBadMessageFromGpu();
   }
-#else
-  NOTREACHED();
-#endif
 }
+#endif  // defined(OS_WIN)
 
 void DefaultGpuHost::StoreShaderToDisk(int32_t client_id,
                                        const std::string& key,
diff --git a/services/ws/gpu_host/gpu_host.h b/services/ws/gpu_host/gpu_host.h
index 0ff47fe..d81ca99 100644
--- a/services/ws/gpu_host/gpu_host.h
+++ b/services/ws/gpu_host/gpu_host.h
@@ -110,8 +110,10 @@
                       gpu::error::ContextLostReason reason,
                       const GURL& active_url) override;
   void DisableGpuCompositing() override;
+#if defined(OS_WIN)
   void SetChildSurface(gpu::SurfaceHandle parent,
                        gpu::SurfaceHandle child) override;
+#endif
   void StoreShaderToDisk(int32_t client_id,
                          const std::string& key,
                          const std::string& shader) override;
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 9844f8cb..b51074b 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2916,9 +2916,11 @@
             ],
             "experiments": [
                 {
-                    "name": "Intervention_R3",
+                    "name": "Intervention_R7",
                     "params": {
-                        "pause_renderer": "true"
+                        "navigate_ads": "true",
+                        "pause_renderer": "true",
+                        "renderer_workload_threshold_percentage": "16"
                     },
                     "enable_features": [
                         "OomIntervention"
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
index 60ae0771..ab34111 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
@@ -81,6 +81,7 @@
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-nested-002.xht [ Pass ]
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-remove-006.xht [ Pass ]
 crbug.com/591099 external/wpt/css/CSS2/text/white-space-mixed-003.xht [ Pass ]
+crbug.com/591099 external/wpt/css/css-contain/contain-size-scrollbars-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-display/display-contents-fieldset-nested-legend.html [ Pass ]
 crbug.com/714962 external/wpt/css/css-fonts/font-features-across-space-1.html [ Pass ]
 crbug.com/714962 external/wpt/css/css-fonts/font-features-across-space-3.html [ Pass ]
@@ -205,6 +206,9 @@
 crbug.com/591099 external/wpt/css/selectors/selector-placeholder-shown-type-change-001.html [ Pass ]
 crbug.com/591099 external/wpt/css/selectors/selector-read-write-type-change-002.html [ Pass ]
 crbug.com/591099 external/wpt/css/selectors/selector-required-type-change-002.html [ Pass ]
+crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-003.html [ Failure ]
+crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-004.html [ Failure ]
+crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-005.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-002.xhtml [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-011.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-012.html [ Pass ]
@@ -234,20 +238,13 @@
 crbug.com/591099 external/wpt/editing/run/bold.html [ Pass ]
 crbug.com/591099 external/wpt/editing/run/fontname.html [ Pass ]
 crbug.com/591099 external/wpt/editing/run/formatblock.html [ Pass ]
-crbug.com/591099 external/wpt/editing/run/inserthorizontalrule.html [ Pass ]
-crbug.com/591099 external/wpt/editing/run/inserthtml.html [ Pass ]
-crbug.com/591099 external/wpt/editing/run/insertimage.html [ Pass ]
-crbug.com/591099 external/wpt/editing/run/insertlinebreak.html [ Pass ]
 crbug.com/591099 external/wpt/editing/run/insertparagraph.html [ Pass ]
 crbug.com/591099 external/wpt/editing/run/justifycenter.html [ Pass ]
 crbug.com/591099 external/wpt/editing/run/justifyfull.html [ Pass ]
 crbug.com/591099 external/wpt/editing/run/justifyleft.html [ Pass ]
 crbug.com/591099 external/wpt/editing/run/justifyright.html [ Pass ]
-crbug.com/591099 external/wpt/editing/run/misc.html [ Pass ]
 crbug.com/591099 external/wpt/editing/run/multitest.html [ Pass ]
 crbug.com/591099 external/wpt/editing/run/strikethrough.html [ Pass ]
-crbug.com/591099 external/wpt/editing/run/subscript.html [ Pass ]
-crbug.com/591099 external/wpt/editing/run/unlink.html [ Pass ]
 crbug.com/591099 external/wpt/encoding/eof-utf-8-three.html [ Failure ]
 crbug.com/591099 external/wpt/encoding/eof-utf-8-two.html [ Failure ]
 crbug.com/591099 external/wpt/encoding/legacy-mb-korean/euc-kr/euckr-decode-ksc_5601.html [ Timeout ]
@@ -255,7 +252,7 @@
 crbug.com/591099 external/wpt/fetch/api/redirect/redirect-count.any.worker.html [ Pass ]
 crbug.com/591099 external/wpt/fetch/api/request/request-keepalive-quota.html?include=slow-2 [ Pass ]
 crbug.com/591099 external/wpt/fetch/http-cache/basic-auth-cache-test.html [ Timeout ]
-crbug.com/591099 external/wpt/geolocation-API/PositionOptions.https.html [ Failure Pass ]
+crbug.com/591099 external/wpt/geolocation-API/PositionOptions.https.html [ Failure ]
 crbug.com/591099 external/wpt/html-media-capture/capture_audio_cancel-manual.html [ Failure ]
 crbug.com/591099 external/wpt/html-media-capture/capture_image_cancel-manual.html [ Failure ]
 crbug.com/591099 external/wpt/html-media-capture/capture_video_cancel-manual.html [ Failure ]
@@ -290,7 +287,7 @@
 crbug.com/591099 external/wpt/offscreen-canvas/convert-to-blob/offscreencanvas.convert.to.blob.html [ Pass ]
 crbug.com/591099 external/wpt/payment-handler/idlharness.https.any.serviceworker.html [ Pass ]
 crbug.com/591099 external/wpt/performance-timeline/po-observe.html [ Timeout ]
-crbug.com/591099 external/wpt/picture-in-picture/request-picture-in-picture-twice.html [ Pass Timeout ]
+crbug.com/591099 external/wpt/picture-in-picture/request-picture-in-picture-twice.html [ Pass ]
 crbug.com/591099 external/wpt/pointerevents/pointerevent_click_during_capture-manual.html [ Crash Timeout ]
 crbug.com/591099 external/wpt/push-api/idlharness.https.any.serviceworker.html [ Pass ]
 crbug.com/591099 external/wpt/quirks/line-height-calculation.html [ Failure ]
@@ -394,6 +391,7 @@
 crbug.com/835484 fast/inline/outline-offset.html [ Failure ]
 crbug.com/591099 fast/overflow/overflow-update-transform.html [ Failure ]
 crbug.com/591099 fast/overflow/recompute-overflow-of-layout-root-container.html [ Failure ]
+crbug.com/591099 fast/reflections/opacity-reflection-transform.html [ Failure ]
 crbug.com/591099 fast/replaced/table-replaced-element.html [ Failure ]
 crbug.com/591099 fast/scrolling/content-box-smaller-than-scrollbar.html [ Failure ]
 crbug.com/591099 fast/scrolling/jquery-rtl-scroll-type.html [ Failure ]
@@ -445,21 +443,18 @@
 crbug.com/591099 http/tests/security/cors-rfc1918/addressspace-document-appcache.https.html [ Crash Failure ]
 crbug.com/591099 http/tests/security/cors-rfc1918/addressspace-document-csp-appcache.https.html [ Crash Failure Pass ]
 crbug.com/591099 http/tests/security/setDomainRelaxationForbiddenForURLScheme.html [ Crash ]
-crbug.com/591099 virtual/outofblink-cors/http/tests/security/cors-rfc1918/addressspace-document-appcache.https.html [ Crash Failure ]
-crbug.com/591099 virtual/outofblink-cors/http/tests/security/cors-rfc1918/addressspace-document-csp-appcache.https.html [ Crash Failure Pass ]
-crbug.com/591099 virtual/outofblink-cors/http/tests/security/setDomainRelaxationForbiddenForURLScheme.html [ Crash ]
-crbug.com/591099 virtual/outofblink-cors-ns/http/tests/security/cors-rfc1918/addressspace-document-appcache.https.html [ Crash Failure ]
-crbug.com/591099 virtual/outofblink-cors-ns/http/tests/security/cors-rfc1918/addressspace-document-csp-appcache.https.html [ Crash Failure Pass ]
-crbug.com/591099 virtual/outofblink-cors-ns/http/tests/security/setDomainRelaxationForbiddenForURLScheme.html [ Crash ]
-crbug.com/591099 idle-callback/test-runner-run-idle-tasks.html [ Crash Pass ]
+crbug.com/591099 idle-callback/test-runner-run-idle-tasks.html [ Crash Pass Timeout ]
 crbug.com/591099 images/color-profile-image-filter-all.html [ Failure ]
 crbug.com/591099 images/rendering-broken-block-flow-images.html [ Failure ]
 crbug.com/591099 images/rendering-broken-images.html [ Failure ]
 crbug.com/714962 inspector-protocol/css/css-get-platform-fonts.js [ Failure ]
 crbug.com/591099 inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-pseudo-element.js [ Failure ]
+crbug.com/591099 inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-scroll-offset.js [ Failure ]
+crbug.com/591099 inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-viewport.js [ Failure ]
 crbug.com/591099 inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot.js [ Failure ]
 crbug.com/714962 inspector-protocol/layout-fonts/languages-emoji-rare-glyphs.js [ Failure ]
 crbug.com/591099 inspector-protocol/timeline/page-frames.js [ Failure ]
+crbug.com/591099 media/video-aspect-ratio.html [ Failure ]
 crbug.com/591099 paint/background/scrolling-background-with-negative-z-child.html [ Failure ]
 crbug.com/591099 paint/float/float-under-inline-self-painting-change.html [ Failure ]
 crbug.com/835484 paint/inline/focus-ring-under-absolute-with-relative-continuation.html [ Failure ]
@@ -508,6 +503,7 @@
 crbug.com/591099 storage/indexeddb/objectstore-cursor.html [ Pass ]
 crbug.com/591099 svg/custom/object-sizing-no-width-height.xhtml [ Failure ]
 crbug.com/591099 svg/filters/feTurbulence-bad-seeds.html [ Failure ]
+crbug.com/591099 svg/hixie/error/013.xml [ Failure ]
 crbug.com/591099 svg/in-html/sizing/svg-inline.html [ Failure ]
 crbug.com/591099 svg/transforms/text-with-pattern-inside-transformed-html.xhtml [ Failure ]
 crbug.com/591099 svg/zoom/page/zoom-img-preserveAspectRatio-support-1.html [ Failure ]
@@ -549,10 +545,15 @@
 crbug.com/591099 virtual/outofblink-cors-ns/external/wpt/fetch/api/redirect/redirect-count.any.html [ Pass ]
 crbug.com/591099 virtual/outofblink-cors-ns/external/wpt/fetch/api/redirect/redirect-count.any.worker.html [ Pass ]
 crbug.com/591099 virtual/outofblink-cors-ns/external/wpt/fetch/api/request/request-keepalive-quota.html?include=slow-2 [ Pass ]
-crbug.com/591099 virtual/outofblink-cors-ns/external/wpt/xhr/send-content-type-string.htm [ Pass ]
-crbug.com/591099 virtual/outofblink-cors-ns/external/wpt/xhr/send-entity-body-document.htm [ Pass ]
-crbug.com/591099 virtual/outofblink-cors-ns/external/wpt/xhr/send-redirect-bogus-sync.htm [ Pass ]
+crbug.com/591099 virtual/outofblink-cors-ns/http/tests/security/cors-rfc1918/addressspace-document-appcache.https.html [ Crash Failure ]
+crbug.com/591099 virtual/outofblink-cors-ns/http/tests/security/cors-rfc1918/addressspace-document-csp-appcache.https.html [ Crash Failure Pass ]
+crbug.com/591099 virtual/outofblink-cors-ns/http/tests/security/document-domain-canonicalizes.html [ Pass ]
+crbug.com/591099 virtual/outofblink-cors-ns/http/tests/security/setDomainRelaxationForbiddenForURLScheme.html [ Crash ]
+crbug.com/591099 virtual/outofblink-cors-ns/http/tests/security/video-poster-cross-origin-crash2.html [ Pass ]
 crbug.com/591099 virtual/outofblink-cors/ [ Skip ]
+crbug.com/591099 virtual/outofblink-cors/http/tests/security/cors-rfc1918/addressspace-document-appcache.https.html [ Crash Failure ]
+crbug.com/591099 virtual/outofblink-cors/http/tests/security/cors-rfc1918/addressspace-document-csp-appcache.https.html [ Crash Failure Pass ]
+crbug.com/591099 virtual/outofblink-cors/http/tests/security/setDomainRelaxationForbiddenForURLScheme.html [ Pass ]
 crbug.com/591099 virtual/paint-timing/external/wpt/paint-timing/sibling-painting-first-image.html [ Failure ]
 crbug.com/591099 virtual/paint-touchaction-rects/fast/events/touch/compositor-touch-hit-rects-continuation.html [ Failure ]
 crbug.com/591099 virtual/paint-touchaction-rects/fast/events/touch/compositor-touch-hit-rects-list-translate.html [ Failure ]
@@ -571,4 +572,5 @@
 crbug.com/591099 virtual/threaded/ [ Skip ]
 crbug.com/591099 virtual/user-activation-v2/fast/events/mouse-cursor.html [ Failure ]
 crbug.com/591099 virtual/user-activation-v2/fast/events/touch/compositor-touch-hit-rects.html [ Failure ]
-crbug.com/591099 virtual/webrtc-wpt-unified-plan/external/wpt/webrtc/RTCDTMFSender-ontonechange.https.html [ Failure Pass ]
+crbug.com/591099 virtual/video-surface-layer/media/video-aspect-ratio.html [ Failure ]
+crbug.com/591099 virtual/webrtc-wpt-unified-plan/external/wpt/webrtc/RTCDTMFSender-ontonechange.https.html [ Pass ]
diff --git a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json
index ea0b307..84b851715 100644
--- a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json
+++ b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json
@@ -35789,6 +35789,18 @@
      {}
     ]
    ],
+   "css/css-contain/contain-animation-001.html": [
+    [
+     "/css/css-contain/contain-animation-001.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-contain/contain-layout-001.html": [
     [
      "/css/css-contain/contain-layout-001.html",
@@ -97841,6 +97853,18 @@
      {}
     ]
    ],
+   "html/rendering/the-details-element/details-display-property-is-ignored.html": [
+    [
+     "/html/rendering/the-details-element/details-display-property-is-ignored.html",
+     [
+      [
+       "/html/rendering/the-details-element/details-display-property-is-ignored-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "html/semantics/document-metadata/the-link-element/stylesheet-change-href.html": [
     [
      "/html/semantics/document-metadata/the-link-element/stylesheet-change-href.html",
@@ -147980,6 +148004,16 @@
      {}
     ]
    ],
+   "fetch/api/cors/cors-preflight-not-cors-safelisted.any-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "fetch/api/cors/cors-preflight-not-cors-safelisted.any.worker-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "fetch/api/cors/cors-preflight-redirect.any-expected.txt": [
     [
      {}
@@ -148000,6 +148034,11 @@
      {}
     ]
    ],
+   "fetch/api/headers/headers-no-cors.window-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "fetch/api/headers/headers-record-expected.txt": [
     [
      {}
@@ -158065,6 +158104,11 @@
      {}
     ]
    ],
+   "html/rendering/the-details-element/details-display-property-is-ignored-ref.html": [
+    [
+     {}
+    ]
+   ],
    "html/rendering/the-details-element/single-summary.html": [
     [
      {}
@@ -263225,6 +263269,12 @@
      {}
     ]
    ],
+   "webaudio/the-audio-api/the-oscillatornode-interface/detune-limiting.html": [
+    [
+     "/webaudio/the-audio-api/the-oscillatornode-interface/detune-limiting.html",
+     {}
+    ]
+   ],
    "webaudio/the-audio-api/the-pannernode-interface/ctor-panner.html": [
     [
      "/webaudio/the-audio-api/the-pannernode-interface/ctor-panner.html",
@@ -311778,6 +311828,10 @@
    "259c00b2a587c9aa2d07de97fb547b32f9772b92",
    "support"
   ],
+  "css/css-contain/contain-animation-001.html": [
+   "449221428c3d76d31ff84a5792c7578c36cbebed",
+   "reftest"
+  ],
   "css/css-contain/contain-layout-001.html": [
    "85b959da2b9a151c13be3dc83485646341752915",
    "reftest"
@@ -368462,10 +368516,18 @@
    "ce6a169d8146750b183c9210d1b2041fac879248",
    "testharness"
   ],
+  "fetch/api/cors/cors-preflight-not-cors-safelisted.any-expected.txt": [
+   "56141bc0d4147b276e2ab2fc795fb4032ed70e59",
+   "support"
+  ],
   "fetch/api/cors/cors-preflight-not-cors-safelisted.any.js": [
    "b2747ccd5bc09e4174aa4c59244e386c80527b51",
    "testharness"
   ],
+  "fetch/api/cors/cors-preflight-not-cors-safelisted.any.worker-expected.txt": [
+   "56141bc0d4147b276e2ab2fc795fb4032ed70e59",
+   "support"
+  ],
   "fetch/api/cors/cors-preflight-redirect.any-expected.txt": [
    "8a420164e1b94e02f9d86d41790b44adc4f87cd5",
    "support"
@@ -368550,6 +368612,10 @@
    "194ff32f1559f2dd9b5903eb3738c17c061c7172",
    "testharness"
   ],
+  "fetch/api/headers/headers-no-cors.window-expected.txt": [
+   "2241e22e63412b03a454626bfb48d65376b2428b",
+   "support"
+  ],
   "fetch/api/headers/headers-no-cors.window.js": [
    "aa6562b7d377f4ad74456a87d7e37bf0bd18cb2b",
    "testharness"
@@ -380962,6 +381028,14 @@
    "3dd95e311aed7fc642e9370dad7cfee5a1eeac1c",
    "reftest"
   ],
+  "html/rendering/the-details-element/details-display-property-is-ignored-ref.html": [
+   "6ebed6075de1e8cf62db7bee756b05d3e425e0ab",
+   "support"
+  ],
+  "html/rendering/the-details-element/details-display-property-is-ignored.html": [
+   "445b4e483d00c8aabfa76a946d8cb0871e479c7d",
+   "reftest"
+  ],
   "html/rendering/the-details-element/single-summary.html": [
    "1f09e7e75f9126982a07902ae0693f9ea2fd5823",
    "support"
@@ -390531,7 +390605,7 @@
    "testharness"
   ],
   "html/webappapis/dynamic-markup-insertion/opening-the-input-stream/reload.window.js": [
-   "d6ff9dc7a45425cb688ed4b6c9ea2ab5c1c3ae5c",
+   "279020f64da8421e118a447bae1373da2e98d9c1",
    "testharness"
   ],
   "html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-async-frame.html": [
@@ -423198,6 +423272,10 @@
    "36bf604b296c63b213d99408ab38937c62a755dc",
    "testharness"
   ],
+  "webaudio/the-audio-api/the-oscillatornode-interface/detune-limiting.html": [
+   "81a1293d0355ed448e60c0e31ad4435ea708e224",
+   "testharness"
+  ],
   "webaudio/the-audio-api/the-pannernode-interface/.gitkeep": [
    "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
    "support"
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-contain/contain-animation-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-contain/contain-animation-001.html
new file mode 100644
index 0000000..44922142
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-contain/contain-animation-001.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Containment Test: contain is not animatable</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://drafts.csswg.org/css-contain/#contain-property">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<meta name=assert content="the contain property is not animatable">
+<style>
+div {
+  border: 50px solid green;
+  background: red;
+  position: absolute; /* for shrinkwrap */
+  contain: strict;
+
+  animation-duration: 1s;
+  animation-name: bad;
+  animation-play-state: paused;
+
+  font-size: 100px;
+}
+
+@keyframes bad {
+  from {
+    contain: none;
+  }
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div>&nbsp;</div>
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-paint-api/registered-properties-in-custom-paint.https.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-paint-api/registered-properties-in-custom-paint.https.html
deleted file mode 100644
index d9a63da..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-paint-api/registered-properties-in-custom-paint.https.html
+++ /dev/null
@@ -1,67 +0,0 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-<link rel="match" href="parse-input-arguments-ref.html">
-<style>
-.container {
-  width: 100px;
-  height: 100px;
-  --length: 10px;
-  --number: 10;
-}
-
-#canvas-geometry {
-  background-image: paint(geometry);
-}
-</style>
-<script src="/common/reftest-wait.js"></script>
-<script src="/common/worklet-reftest.js"></script>
-<body>
-<div id="canvas-geometry" class="container"></div>
-
-<script id="code" type="text/worklet">
-registerPaint('geometry', class {
-    static get inputProperties() {
-        return [
-            '--length',
-            '--length-initial',
-            '--number',
-        ];
-    }
-    paint(ctx, geom, styleMap) {
-        const properties = [...styleMap.keys()].sort();
-        var serializedStrings = [];
-        for (let i = 0; i < properties.length; i++) {
-            const value = styleMap.get(properties[i]);
-            let serialized;
-            if (value)
-                serialized = properties[i].toString() + ': [' + value.constructor.name + '=' + value.toString() + ']';
-            else
-                serialized = properties[i].toString() + ': [null]';
-            serializedStrings.push(serialized);
-        }
-        ctx.strokeStyle = 'green';
-        if (serializedStrings[0] != "--length: [CSSUnitValue=10px]")
-            ctx.strokeStyle = 'red';
-        if (serializedStrings[1] != "--length-initial: [CSSUnitValue=20px]")
-            ctx.strokeStyle = 'blue';
-        if (serializedStrings[2] != "--number: [CSSUnitValue=10]")
-            ctx.strokeStyle = 'yellow';
-        ctx.lineWidth = 4;
-        ctx.strokeRect(0, 0, geom.width, geom.height);
-    }
-});
-</script>
-
-<script>
-    try {
-      CSS.registerProperty({name: '--length', syntax: '<length>', initialValue: '0px', inherits: false});
-      CSS.registerProperty({name: '--length-initial', syntax: '<length>', initialValue: '20px', inherits: false});
-      CSS.registerProperty({name: '--number', syntax: '<number>', initialValue: '0', inherits: false});
-      importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
-    } catch(e) {
-      document.body.textContent = e;
-      takeScreenshot();
-    }
-</script>
-</body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-paint-api/registered-property-type.https.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-paint-api/registered-property-type.https.html
new file mode 100644
index 0000000..6ff7ce4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-paint-api/registered-property-type.https.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="help" href="https://www.w3.org/TR/css-paint-api-1/#examples">
+<link rel="match" href="parse-input-arguments-ref.html">
+<style>
+.container {
+  width: 100px;
+  height: 100px;
+}
+
+#canvas-geometry {
+  background-image: paint(geometry);
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+<body>
+<div id="canvas-geometry" class="container"></div>
+<script id="code" type="text/worklet">
+  // Globals that must be prepended to this script:
+  // - debugLog: A function that logs errors.
+  // - props: Test data.
+
+  registerPaint('geometry', class {
+    static get inputProperties() { return props.map(p => p.name); }
+
+    paint(ctx, geom, styleMap) {
+      ctx.strokeStyle = 'green';
+      for (let prop of props) {
+        let first = styleMap.get(prop.name);
+        let all = styleMap.getAll(prop.name);
+        let serialize = v => v.constructor.name + '=' + v.toString()
+        let actual = all.map(serialize).join(',');
+        let expected = prop.expected.join(',');
+        let pass = actual === expected
+            && serialize(first) === prop.expected[0];
+        if (!pass)
+          ctx.strokeStyle = 'red';
+        debugLog(pass ? 'PASS' : 'FAIL', prop.syntax, actual, expected);
+      }
+      ctx.lineWidth = 4;
+      ctx.strokeRect(0, 0, geom.width, geom.height);
+    }
+  });
+</script>
+<script>
+    // A copy of this array (automatically enriched with 'name' and 'expected')
+    // is also available in the worklet.
+    let props = [
+      // Initial values.
+      { syntax: '*', initialValue: 'if(){}' },
+      { syntax: '<angle>', initialValue: '42deg' },
+      { syntax: '<color>', initialValue: '#fefefe' },
+      { syntax: '<custom-ident>', initialValue: 'none' },
+      { syntax: '<image>', initialValue: 'linear-gradient(red, red)' },
+      { syntax: '<image>', initialValue: 'url(http://a.com/a)' },
+      { syntax: '<integer>', initialValue: '42' },
+      { syntax: '<length-percentage>', initialValue: '10%' },
+      { syntax: '<length-percentage>', initialValue: '10px' },
+      { syntax: '<length-percentage>', initialValue: 'calc(10px + 10%)' },
+      { syntax: '<length>', initialValue: '1337px' },
+      { syntax: '<number>', initialValue: '42.5' },
+      { syntax: '<percentage>', initialValue: '42%' },
+      { syntax: '<resolution>', initialValue: '300dpi' },
+      { syntax: '<time>', initialValue: '3600s' },
+      { syntax: '<url>', initialValue: 'url(http://a.com/a)' },
+      { syntax: 'thing', initialValue: 'thing' },
+      { syntax: '<length> | <angle>', initialValue: '1337px' },
+      { syntax: '<angle> | <image>', initialValue: '1turn' },
+      { syntax: '<length>+', initialValue: '1337px' },
+      { syntax: '<length>+', initialValue: '1337px 1338px', count: 2 },
+      { syntax: '<length>#', initialValue: '1337px' },
+      { syntax: '<length>#', initialValue: '1337px, 1338px', count: 2 },
+
+      // Non-initial values:
+      { syntax: '*', initialValue: 'fail', value: 'if(){}' },
+      { syntax: '<angle> | fail', initialValue: 'fail', value: '42deg' },
+      { syntax: '<color> | fail', initialValue: 'fail', value: '#fefefe' },
+      { syntax: '<custom-ident> | fail', initialValue: 'fail', value: 'none' },
+      { syntax: '<image> | fail', initialValue: 'fail', value: 'linear-gradient(red, red)' },
+      { syntax: '<image> | fail', initialValue: 'fail', value: 'url(http://a.com/a)' },
+      { syntax: '<integer> | fail', initialValue: 'fail', value: '42' },
+      { syntax: '<length-percentage> | fail', initialValue: 'fail', value: '10%' },
+      { syntax: '<length-percentage> | fail', initialValue: 'fail', value: '10px' },
+      { syntax: '<length-percentage> | fail', initialValue: 'fail', value: 'calc(10px + 10%)' },
+      { syntax: '<length> | fail', initialValue: 'fail', value: '1337px' },
+      { syntax: '<number> | fail', initialValue: 'fail', value: '42.5' },
+      { syntax: '<percentage> | fail', initialValue: 'fail', value: '42%' },
+      { syntax: '<resolution> | fail', initialValue: 'fail', value: '300dpi' },
+      { syntax: '<time> | fail', initialValue: 'fail', value: '3600s' },
+      { syntax: '<url> | fail', initialValue: 'fail', value: 'url(http://a.com/a)' },
+      { syntax: 'thing | fail', initialValue: 'fail', value: 'thing' },
+      { syntax: '<length>+ | fail', initialValue: 'fail', value: '1337px' },
+      { syntax: '<length>+ | fail', initialValue: 'fail', value: '1337px 1338px', count: 2 },
+      { syntax: '<length># | fail', initialValue: 'fail', value: '1337px' },
+      { syntax: '<length># | fail', initialValue: 'fail', value: '1337px, 1338px', count: 2 },
+    ];
+
+    try {
+      let target = document.getElementById('canvas-geometry');
+      let pid = 1;
+
+      for (let p of props) {
+        p.name = `--prop-${++pid}`;
+
+        CSS.registerProperty({
+          name: p.name,
+          syntax: p.syntax,
+          initialValue: p.initialValue,
+          inherits: (typeof p.inherits !== 'undefined') ? p.inherits : false
+        });
+
+        if (typeof p.value !== 'undefined')
+          target.style.setProperty(p.name, p.value);
+        if (typeof p.count === 'undefined')
+          p.count = 1;
+
+        let getValue = p => (typeof p.value !== 'undefined') ? p.value : p.initialValue;
+        let serialize = v => v.constructor.name + '=' + v.toString();
+
+        let parse = function (p) {
+          if (p.count == 1)
+            return [CSSStyleValue.parse(p.name, getValue(p))];
+          return CSSStyleValue.parseAll(p.name, getValue(p));
+        };
+
+        // Generate expected value. We assume that CSSStyleValue.parse/All
+        // returns the correct CSSStyleValue subclass and value.
+        p.expected = parse(p).map(serialize);
+      }
+
+      // Adding '?debug' to the URL will cause this test to emit
+      // test results to console.log.
+      let debugMode = document.location.href.endsWith('?debug');
+      let code = [
+        `const props = ${JSON.stringify(props)};`,
+        `const debugLog = ${debugMode ? 'console.log' : 'function(){}'};`,
+        document.getElementById('code').textContent
+      ].join('\n');
+
+      importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, code);
+    } catch(e) {
+      document.body.textContent = e;
+      takeScreenshot();
+    }
+</script>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/dom/events/event-global-extra.window-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/dom/events/event-global-extra.window-expected.txt
index beb08255..0c086b2 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/dom/events/event-global-extra.window-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/dom/events/event-global-extra.window-expected.txt
@@ -1,9 +1,9 @@
 This is a testharness.js-based test.
-PASS window.event for constructors from another global: EventTarget
-PASS window.event for constructors from another global: XMLHttpRequest
+FAIL window.event for constructors from another global: EventTarget assert_equals: expected (undefined) undefined but got (object) object "[object Event]"
+FAIL window.event for constructors from another global: XMLHttpRequest assert_equals: expected (undefined) undefined but got (object) object "[object Event]"
 PASS window.event and element from another document
-PASS window.event and moving an element post-dispatch
+FAIL window.event and moving an element post-dispatch assert_equals: expected (object) object "[object Event]" but got (undefined) undefined
 FAIL window.event should not be affected by nodes moving post-dispatch assert_equals: expected (undefined) undefined but got (object) object "[object Event]"
-PASS Listener from a different global
+FAIL Listener from a different global assert_equals: expected (object) object "[object Event]" but got (undefined) undefined
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/api/cors/cors-preflight-not-cors-safelisted.any-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/fetch/api/cors/cors-preflight-not-cors-safelisted.any-expected.txt
deleted file mode 100644
index 56141bc..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/fetch/api/cors/cors-preflight-not-cors-safelisted.any-expected.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-This is a testharness.js-based test.
-PASS Loading data…
-FAIL Need CORS-preflight for accept/" header assert_equals: Preflight request has been made expected "1" but got "0"
-FAIL Need CORS-preflight for accept/012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 header assert_equals: Preflight request has been made expected "1" but got "0"
-FAIL Need CORS-preflight for accept-language/ header assert_equals: Preflight request has been made expected "1" but got "0"
-FAIL Need CORS-preflight for accept-language/@ header assert_equals: Preflight request has been made expected "1" but got "0"
-FAIL Need CORS-preflight for content-language/ header assert_equals: Preflight request has been made expected "1" but got "0"
-FAIL Need CORS-preflight for content-language/@ header assert_equals: Preflight request has been made expected "1" but got "0"
-PASS Need CORS-preflight for content-type/text/html header
-FAIL Need CORS-preflight for content-type/text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 header assert_equals: Preflight request has been made expected "1" but got "0"
-PASS Need CORS-preflight for test/hi header
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/api/cors/cors-preflight-not-cors-safelisted.any.worker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/fetch/api/cors/cors-preflight-not-cors-safelisted.any.worker-expected.txt
deleted file mode 100644
index 56141bc..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/fetch/api/cors/cors-preflight-not-cors-safelisted.any.worker-expected.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-This is a testharness.js-based test.
-PASS Loading data…
-FAIL Need CORS-preflight for accept/" header assert_equals: Preflight request has been made expected "1" but got "0"
-FAIL Need CORS-preflight for accept/012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 header assert_equals: Preflight request has been made expected "1" but got "0"
-FAIL Need CORS-preflight for accept-language/ header assert_equals: Preflight request has been made expected "1" but got "0"
-FAIL Need CORS-preflight for accept-language/@ header assert_equals: Preflight request has been made expected "1" but got "0"
-FAIL Need CORS-preflight for content-language/ header assert_equals: Preflight request has been made expected "1" but got "0"
-FAIL Need CORS-preflight for content-language/@ header assert_equals: Preflight request has been made expected "1" but got "0"
-PASS Need CORS-preflight for content-type/text/html header
-FAIL Need CORS-preflight for content-type/text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 header assert_equals: Preflight request has been made expected "1" but got "0"
-PASS Need CORS-preflight for test/hi header
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/api/headers/headers-no-cors.window-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/fetch/api/headers/headers-no-cors.window-expected.txt
deleted file mode 100644
index 2241e22..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/fetch/api/headers/headers-no-cors.window-expected.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-This is a testharness.js-based test.
-PASS Loading data…
-FAIL "no-cors" Headers object cannot have accept/" as header assert_false: expected false got true
-FAIL "no-cors" Headers object cannot have accept/012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 as header assert_false: expected false got true
-FAIL "no-cors" Headers object cannot have accept-language/ as header assert_false: expected false got true
-FAIL "no-cors" Headers object cannot have accept-language/@ as header assert_false: expected false got true
-FAIL "no-cors" Headers object cannot have content-language/ as header assert_false: expected false got true
-FAIL "no-cors" Headers object cannot have content-language/@ as header assert_false: expected false got true
-PASS "no-cors" Headers object cannot have content-type/text/html as header
-FAIL "no-cors" Headers object cannot have content-type/text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 as header assert_false: expected false got true
-PASS "no-cors" Headers object cannot have test/hi as header
-FAIL "no-cors" Headers object cannot have dpr/2 as header assert_false: expected false got true
-PASS "no-cors" Headers object cannot have downlink/1 as header
-FAIL "no-cors" Headers object cannot have save-data/on as header assert_false: expected false got true
-FAIL "no-cors" Headers object cannot have viewport-width/100 as header assert_false: expected false got true
-FAIL "no-cors" Headers object cannot have width/100 as header assert_false: expected false got true
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/the-details-element/details-display-property-is-ignored-ref.html b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/the-details-element/details-display-property-is-ignored-ref.html
new file mode 100644
index 0000000..6ebed60
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/the-details-element/details-display-property-is-ignored-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+From <a href="https://html.spec.whatwg.org/multipage/rendering.html#the-details-and-summary-elements">html.spec.whatwg.org</a>:
+
+<blockquote>
+The details element is expected to render as a block box. The element's shadow
+tree is expected to take the element's first summary element child, if any, and
+place it in a first block box container, and then take the element's remaining
+descendants, if any, and place them in a second block box container.
+</blockquote>
+
+&lt;details display:flex> should be ignored. Otherwise details would render as
+something other than a block box.
+<hr>
+
+<details open>
+  <summary>This is the summary.</summary>
+  <div>thing 1</div>
+  thing 2
+</details>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/the-details-element/details-display-property-is-ignored.html b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/the-details-element/details-display-property-is-ignored.html
new file mode 100644
index 0000000..445b4e4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/the-details-element/details-display-property-is-ignored.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/rendering.html#the-details-and-summary-elements">
+<link rel="match" href="details-display-property-is-ignored-ref.html">
+<link rel="bookmark" href="https://bugs.chromium.org/p/chromium/issues/detail?id=635282" />
+<meta name="assert" content="The display property is ignored on details elements and is instead always rendered as a block box." />
+
+From <a href="https://html.spec.whatwg.org/multipage/rendering.html#the-details-and-summary-elements">html.spec.whatwg.org</a>:
+
+<blockquote>
+The details element is expected to render as a block box. The element's shadow
+tree is expected to take the element's first summary element child, if any, and
+place it in a first block box container, and then take the element's remaining
+descendants, if any, and place them in a second block box container.
+</blockquote>
+
+&lt;details display:flex> should be ignored. Otherwise details would render as
+something other than a block box.
+<hr>
+
+<details open style="display:flex;">
+  <summary>This is the summary.</summary>
+  <div>thing 1</div>
+  thing 2
+</details>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/083-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/083-expected.txt
index 7d8abb8..5f6926c 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/083-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/083-expected.txt
@@ -1,4 +1,5 @@
 This is a testharness.js-based test.
+Harness Error. harness_status.status = 1 , harness_status.message = Uncaught TypeError: Cannot read property 'log' of null
 FAIL  scheduler: event listener defined by script in a document in history assert_array_equals: lengths differ, expected 5 got 6
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/084-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/084-expected.txt
new file mode 100644
index 0000000..bfce201
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/084-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL  scheduler: event listener defined by script in a removed IFRAME Uncaught TypeError: Cannot read property 'log' of null
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/reload.window.js b/third_party/WebKit/LayoutTests/external/wpt/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/reload.window.js
index d6ff9dc7a..279020f 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/reload.window.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/reload.window.js
@@ -12,16 +12,16 @@
 // that restriction.
 //
 // In any case, this test as the caller of `document.open()` would be used both
-// as the test file and as part of the test file. The `if (!opener)` condition
-// controls what role this file plays.
+// as the test file and as part of the test file. The `if (window.name !==
+// "opened-dummy-window")` condition controls what role this file plays.
 
-if (!opener) {
+if (window.name !== "opened-dummy-window") {
   async_test(t => {
     const testURL = document.URL;
     const dummyURL = new URL("resources/dummy.html", document.URL).href;
 
     // 1. Open an auxiliary window.
-    const win = window.open("resources/dummy.html");
+    const win = window.open("resources/dummy.html", "opened-dummy-window");
     t.add_cleanup(() => { win.close(); });
 
     win.addEventListener("load", t.step_func(() => {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-1-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-1-expected.txt
new file mode 100644
index 0000000..e65e176
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-1-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL The error event from an event listener should fire on that listener's global assert_true: expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-2-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-2-expected.txt
new file mode 100644
index 0000000..e65e176
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-2-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL The error event from an event listener should fire on that listener's global assert_true: expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/fast/dom/margin-height-guarded-crash-expected.txt b/third_party/WebKit/LayoutTests/fast/dom/margin-height-guarded-crash-expected.txt
index 5bb876c..6401436 100644
--- a/third_party/WebKit/LayoutTests/fast/dom/margin-height-guarded-crash-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/dom/margin-height-guarded-crash-expected.txt
@@ -1,4 +1,4 @@
-CONSOLE ERROR: line 24: Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
+CONSOLE ERROR: line 22: Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
 This is a testharness.js-based test.
 PASS try-trigger-crash
 Harness: the test ran to completion.
diff --git a/third_party/WebKit/LayoutTests/fast/dom/margin-height-guarded-crash.html b/third_party/WebKit/LayoutTests/fast/dom/margin-height-guarded-crash.html
index 706e309..b0546e4a 100644
--- a/third_party/WebKit/LayoutTests/fast/dom/margin-height-guarded-crash.html
+++ b/third_party/WebKit/LayoutTests/fast/dom/margin-height-guarded-crash.html
@@ -17,8 +17,6 @@
 <iframe id=html_iframe></iframe>
 <div id=html_div></div>
 <script>
-setup({ allow_uncaught_exception: true });
-
 function reactToWidthChange() {
   // null the body
   html_div.appendChild(html_iframe.contentDocument.body);
diff --git a/third_party/WebKit/LayoutTests/fast/dom/ready-state-change-crash-expected.txt b/third_party/WebKit/LayoutTests/fast/dom/ready-state-change-crash-expected.txt
index c559db5..1042c767 100644
--- a/third_party/WebKit/LayoutTests/fast/dom/ready-state-change-crash-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/dom/ready-state-change-crash-expected.txt
@@ -1,3 +1,2 @@
-CONSOLE ERROR: line 27: Uncaught NotFoundError: Failed to execute 'appendChild' on 'Node': The node to be removed is no longer a child of this node. Perhaps it was moved in response to a mutation?
 Test passes if it does not crash.
 
diff --git a/third_party/WebKit/LayoutTests/fast/events/touch/gesture/gesture-tap-frame-removed-expected.txt b/third_party/WebKit/LayoutTests/fast/events/touch/gesture/gesture-tap-frame-removed-expected.txt
index d1fe6f4..aa4c6432 100644
--- a/third_party/WebKit/LayoutTests/fast/events/touch/gesture/gesture-tap-frame-removed-expected.txt
+++ b/third_party/WebKit/LayoutTests/fast/events/touch/gesture/gesture-tap-frame-removed-expected.txt
@@ -13,12 +13,19 @@
 
 Test case: Remove during mousedown
 Sending GestureTap
-FAIL document.getElementById('target') should be null. Was [object HTMLIFrameElement].
+Received mousemove in child frame
+Received mousedown in child frame
+Removing iframe
+PASS document.getElementById('target') is null
 iframe loaded
 
 Test case: Remove during mouseup
 Sending GestureTap
-FAIL document.getElementById('target') should be null. Was [object HTMLIFrameElement].
+Received mousemove in child frame
+Received mousedown in child frame
+Received mouseup in child frame
+Removing iframe
+PASS document.getElementById('target') is null
 iframe loaded
 
 PASS successfullyParsed is true
diff --git a/third_party/WebKit/LayoutTests/http/tests/wasm/wasm_worker_termination_while_compiling.html b/third_party/WebKit/LayoutTests/http/tests/wasm/wasm_worker_termination_while_compiling.html
new file mode 100644
index 0000000..8dd42d6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/wasm/wasm_worker_termination_while_compiling.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="resources/wasm-constants.js"></script>
+<script src="resources/wasm-module-builder.js"></script>
+<script>
+
+const test = async_test("TestWasmWorkerTerminationWhileCompiling");
+
+const kNumWorkers = 8;
+const kNumFunctions = 5000;
+
+// This function is executed by each worker.
+function workerFunction() {
+  onmessage = function(event) {
+    // Start asynchronous compilation, then notify the main thread about that.
+    WebAssembly.compile(event.data)
+        .catch(e => postMessage("compilation error: " + e));
+    postMessage("compiling");
+  };
+}
+
+// Build a wasm module with a number of function, such that the workers need
+// some time compiling it.
+const builder = new WasmModuleBuilder();
+for (var i = 0; i < kNumFunctions; ++i) {
+  builder.addFunction('func' + i, kSig_v_v).addBody([kExprCallFunction, 0]);
+}
+const module_bytes = builder.toBuffer();
+
+const blobURL =
+    URL.createObjectURL(new Blob(['(' + workerFunction.toString() + ')()']));
+
+// Counter to wait for all workers to start compilation.
+var outstanding_worker_events = kNumWorkers;
+const workers = [];
+
+function workerEvent(event) {
+  // If the event data is not "compiling", this is an error in the worker.
+  assert_equals(event.data, "compiling");
+  // Decrement counter of workers that did not start compiling yet.
+  // Do nothing if there are still outstanding workers.
+  assert_greater_than(outstanding_worker_events, 0);
+  if (--outstanding_worker_events > 0) return;
+  // All workers started compiling! Now terminate them all. This should not
+  // crash.
+  for (let i = 0; i < kNumWorkers; ++i) {
+    workers[i].terminate();
+  }
+  test.done();
+}
+
+for (let i = 0; i < kNumWorkers; ++i) {
+  const worker = new Worker(blobURL);
+  worker.onmessage = test.step_func(workerEvent);
+  worker.postMessage(module_bytes);
+  workers.push(worker);
+}
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-merged-nodes-expected.txt b/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-merged-nodes-expected.txt
index a9c29cd..9276a699 100644
--- a/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-merged-nodes-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-merged-nodes-expected.txt
@@ -2,5 +2,5 @@
 Took heap snapshot
 Parsed snapshot
 SUCCESS: found leaking
-SUCCESS: retaining path = [Detached V8EventListener, Detached EventListener, Detached InternalNode, Detached InternalNode, Detached HTMLDivElement, Window / file://, ]
+SUCCESS: retaining path = [Detached EventListener, Detached InternalNode, Detached InternalNode, Detached HTMLDivElement, Window / file://, ]
 
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-with-event-listener-expected.txt b/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-with-event-listener-expected.txt
index e53326f..10c73030 100644
--- a/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-with-event-listener-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-with-event-listener-expected.txt
@@ -2,5 +2,5 @@
 Took heap snapshot
 Parsed snapshot
 SUCCESS: found myEventListener
-SUCCESS: retaining path = [V8EventListener, EventListener, InternalNode, InternalNode, HTMLBodyElement, HTMLHtmlElement, HTMLDocument, Window / file://, ]
+SUCCESS: retaining path = [EventListener, InternalNode, InternalNode, HTMLBodyElement, HTMLHtmlElement, HTMLDocument, Window / file://, ]
 
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-with-multiple-retainers-expected.txt b/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-with-multiple-retainers-expected.txt
index 473e2fc..2c57bee 100644
--- a/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-with-multiple-retainers-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-with-multiple-retainers-expected.txt
@@ -2,9 +2,7 @@
 Took heap snapshot
 Parsed snapshot
 SUCCESS: found leaking
-SUCCESS: immediate retainer is V8EventListener.
 SUCCESS: immediate retainer is EventListener.
-SUCCESS: found single retaining path for v8EventListener.
 SUCCESS: found multiple retaining paths.
 SUCCESS: path1 = [InternalNode, HTMLBodyElement, HTMLHtmlElement, HTMLDocument, Window / file://, ]
 SUCCESS: path2 = [InternalNode, HTMLDivElement, HTMLBodyElement, HTMLHtmlElement, HTMLDocument, Window / file://, ]
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-with-multiple-retainers.js b/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-with-multiple-retainers.js
index 94f3d82..a2d852e 100644
--- a/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-with-multiple-retainers.js
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/heap-profiler/heap-snapshot-with-multiple-retainers.js
@@ -31,14 +31,7 @@
   else
     return testRunner.fail('cannot find the leaking node');
 
-  var v8EventListener = helper.firstRetainingPath(node)[0];
-  var eventListener = helper.firstRetainingPath(node)[1];
-
-  if (v8EventListener.name() == 'V8EventListener') {
-    testRunner.log('SUCCESS: immediate retainer is V8EventListener.');
-  } else {
-    return testRunner.fail('cannot find the V8EventListener.');
-  }
+  var eventListener = helper.firstRetainingPath(node)[0];
 
   if (eventListener.name() == 'EventListener') {
     testRunner.log('SUCCESS: immediate retainer is EventListener.');
@@ -46,12 +39,6 @@
     return testRunner.fail('cannot find the EventListener.');
   }
 
-  if (v8EventListener.retainersCount() === 1) {
-    testRunner.log('SUCCESS: found single retaining path for v8EventListener.');
-  } else {
-    return testRunner.fail('cannot find single retaining path for v8EventListener.');
-  }
-
   var retainingPaths = [];
   for (var iter = eventListener.retainers(); iter.hasNext(); iter.next()) {
     var path = helper.firstRetainingPath(iter.retainer.node());
diff --git a/third_party/WebKit/LayoutTests/invisible_dom/invisible-attribute.html b/third_party/WebKit/LayoutTests/invisible_dom/invisible-attribute.html
index ab7000d..d41ea29d 100644
--- a/third_party/WebKit/LayoutTests/invisible_dom/invisible-attribute.html
+++ b/third_party/WebKit/LayoutTests/invisible_dom/invisible-attribute.html
@@ -50,15 +50,24 @@
 
 test(() => {
   setUp();
-  // TODO(rakina): modify this test when we have levels ("invisible"/"static")
   outerDiv.setAttribute("invisible", "x");
   assert_equals(outerDiv.getAttribute("invisible"), "x");
+  assert_equals(outerDiv.invisible, "invisible");
   assert_true(outerDiv.hasAttribute("invisible"));
 
   outerDiv.setAttribute("invisible", "");
   assert_equals(outerDiv.getAttribute("invisible"), "");
+  assert_equals(outerDiv.invisible, "invisible");
   assert_true(outerDiv.hasAttribute("invisible"));
-}, "Setting/removing invisible attribute preserves original value.");
+
+  outerDiv.removeAttribute("invisible");
+  assert_equals(outerDiv.invisible, "");
+
+  outerDiv.setAttribute("invisible", "static");
+  assert_equals(outerDiv.getAttribute("invisible"), "static");
+  assert_equals(outerDiv.invisible, "static");
+  assert_true(outerDiv.hasAttribute("invisible"));
+}, "Setting/removing invisible attribute preserves original value, but property returns only invisible/static");
 
 test(() => {
   setUp();
diff --git a/third_party/WebKit/LayoutTests/invisible_dom/invisible-static.html b/third_party/WebKit/LayoutTests/invisible_dom/invisible-static.html
new file mode 100644
index 0000000..5570405
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/invisible_dom/invisible-static.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<title>Invisible-static level</title>
+<body>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+
+<div>
+  <div id="normal"></div>
+  <div id="basicInvisible" invisible></div>
+  <div id="staticInvisible" invisible="static"></div>
+</div>
+
+<script>
+'use strict';
+
+class TestElement extends HTMLElement {
+  constructor() {
+    super();
+    this.innerHTML = "upgraded";
+  }
+}
+
+customElements.define("test-element", TestElement);
+
+const testElementString =  "<test-element></test-element>";
+const testElementUpgradedString = "<test-element>upgraded</test-element>";
+
+function setUp() {
+  normal.innerHTML = basicInvisible.innerHTML = staticInvisible.innerHTML = "";
+  basicInvisible.invisible = "invisible";
+  staticInvisible.invisible = "static";
+}
+
+test(() => {
+  setUp();
+  normal.innerHTML = testElementString;
+  basicInvisible.innerHTML = testElementString;
+  staticInvisible.innerHTML = testElementString;
+  assert_equals(normal.innerHTML, testElementUpgradedString);
+  assert_equals(basicInvisible.innerHTML, testElementUpgradedString);
+  assert_equals(staticInvisible.innerHTML, testElementString);
+}, "Custom elements inside invisible-static subtree is not upgraded.");
+
+test(() => {
+  setUp();
+  staticInvisible.removeAttribute("invisible");
+  staticInvisible.innerHTML = testElementString;
+  assert_equals(staticInvisible.innerHTML, testElementUpgradedString);
+}, "Previously-static subtree should not block custom element upgrade");
+
+test(() => {
+  setUp();
+  staticInvisible.innerHTML = testElementString;
+  staticInvisible.invisible = "invisible";
+  assert_equals(staticInvisible.innerHTML, testElementUpgradedString);
+}, "Making an element not invisible='static' upgrades the custom elements inside");
+
+test(() => {
+  setUp();
+  staticInvisible.innerHTML = testElementString;
+  assert_equals(staticInvisible.innerHTML, testElementString, "Normally not upgraded");
+  customElements.upgrade(staticInvisible);
+  assert_equals(staticInvisible.innerHTML, testElementUpgradedString, "After forcing got upgraded");
+}, "Upgrade by customElements.upgrade is not deferred");
+
+test(() => {
+  setUp();
+  staticInvisible.innerHTML = "<another-element></another-element>";
+  customElements.define("another-element", class extends HTMLElement {
+    constructor() {
+      super();
+      this.innerHTML = "upgraded";
+    }
+  });
+  assert_equals(staticInvisible.innerHTML, "<another-element></another-element>");
+  staticInvisible.invisible = "invisible";
+  assert_equals(staticInvisible.innerHTML, "<another-element>upgraded</another-element>");
+}, "Upgrade after defined is deferred");
+</script>
diff --git a/third_party/WebKit/LayoutTests/paint/background/background-clip-text-descendants-expected.html b/third_party/WebKit/LayoutTests/paint/background/background-clip-text-descendants-expected.html
deleted file mode 100644
index deee47e..0000000
--- a/third_party/WebKit/LayoutTests/paint/background/background-clip-text-descendants-expected.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<style>
-body {
-  font-size: 40px;
-}
-.transformed {
-  transform: translateX(0);
-}
-.clip-text {
-  background: blue;
-  -webkit-background-clip: text;
-  color: rgba(255,0,0,0.5);
-  clear: both;
-}
-</style>
-Passes if all texts are purple.
-<div class="clip-text">Block</div>
-<div class="clip-text">Block transformed</div>
-<div><div class="clip-text" style="float:left">Float</div><br></div>
-<div><div class="clip-text" style="float:left">Float transformed</div><br></div>
-<table><tr><td class="clip-text">Table</td></tr></table>
-<table><tr><td class="clip-text">Table transformed</td></tr></table>
-Except these (inline-block is not supported for text background clip yet):
-<div style="color: rgba(255,0,0,0.5"><div style="display: inline-block">Inline block</div></div>
-<div style="color: rgba(255,0,0,0.5"><div style="display: inline-block">Inline block transformed</div></div>
diff --git a/third_party/WebKit/LayoutTests/paint/background/background-clip-text-descendants.html b/third_party/WebKit/LayoutTests/paint/background/background-clip-text-descendants.html
deleted file mode 100644
index 4e06c95d..0000000
--- a/third_party/WebKit/LayoutTests/paint/background/background-clip-text-descendants.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<!DOCTYPE html>
-<style>
-body {
-  font-size: 40px;
-}
-.transformed {
-  transform: translateX(0);
-}
-.clip-text {
-  background: blue;
-  -webkit-background-clip: text;
-  color: rgba(255,0,0,0.5);
-}
-</style>
-Passes if all texts are purple.
-<div class="clip-text"><div>Block</div></div>
-<div class="clip-text transformed"><div>Block transformed</div></div>
-<div class="clip-text"><div style="float:left">Float</div><br></div>
-<div class="clip-text transformed"><div style="float:left">Float transformed</div><br></div>
-<table class="clip-text"><tr><td>Table</td></tr></table>
-<table class="clip-text transformed"><tr><td>Table transformed</td></tr></table>
-Except these (inline-block is not supported for text background clip yet):
-<div class="clip-text"><div style="display: inline-block">Inline block</div></div>
-<div class="clip-text transformed"><div style="display: inline-block">Inline block transformed</div></div>
diff --git a/third_party/WebKit/LayoutTests/paint/background/background-clip-text-inline-expected.html b/third_party/WebKit/LayoutTests/paint/background/background-clip-text-inline-expected.html
new file mode 100644
index 0000000..34c281e5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/paint/background/background-clip-text-inline-expected.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<style>
+body {
+  font-size: 40px;
+}
+.text-clip {
+  background: blue;
+  -webkit-background-clip: text;
+  color: rgba(255,0,0,0.5);
+}
+</style>
+Passes if all texts below are purple.
+<br>
+<div class="text-clip">&nbsp;Text1&nbsp;&nbsp;Text2&nbsp;</div>
+<div class="text-clip">&nbsp;Text3&nbsp;&nbsp;Text4&nbsp;</div>
diff --git a/third_party/WebKit/LayoutTests/paint/background/background-clip-text-inline.html b/third_party/WebKit/LayoutTests/paint/background/background-clip-text-inline.html
new file mode 100644
index 0000000..175a353
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/paint/background/background-clip-text-inline.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<style>
+body {
+  font-size: 40px;
+}
+.text-clip {
+  background: blue;
+  -webkit-background-clip: text;
+  color: rgba(255,0,0,0.5);
+}
+.relative {
+  position: relative;
+  left: 10px;
+}
+</style>
+Passes if all texts below are purple.
+<br>
+<!-- The &nbsp;s are to avoid glyph overflows. -->
+<span class="text-clip">&nbsp;Text1&nbsp;</span><span class="text-clip">&nbsp;Text2&nbsp;</span>
+<br>
+<span class="text-clip">&nbsp;Text3&nbsp;</span><span class="text-clip">&nbsp;Text4&nbsp;</span>
diff --git a/third_party/WebKit/LayoutTests/platform/mac/svg/text/text-selection-align-01-b-expected.png b/third_party/WebKit/LayoutTests/platform/mac/svg/text/text-selection-align-01-b-expected.png
index 768a297..d589f5b1 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/svg/text/text-selection-align-01-b-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/svg/text/text-selection-align-01-b-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/svg/text/text-selection-align-02-b-expected.png b/third_party/WebKit/LayoutTests/platform/mac/svg/text/text-selection-align-02-b-expected.png
index 72edaa1..1b8402a 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/svg/text/text-selection-align-02-b-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/mac/svg/text/text-selection-align-02-b-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/svg/text/text-selection-fonts-02-t-expected.png b/third_party/WebKit/LayoutTests/platform/win/svg/text/text-selection-fonts-02-t-expected.png
index 9f4e21ca..68c79078 100644
--- a/third_party/WebKit/LayoutTests/platform/win/svg/text/text-selection-fonts-02-t-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/win/svg/text/text-selection-fonts-02-t-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/shadow-dom/imperative-apis/custom-detail-summary.js b/third_party/WebKit/LayoutTests/shadow-dom/imperative-apis/custom-detail-summary.js
new file mode 100644
index 0000000..70d2af03
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/shadow-dom/imperative-apis/custom-detail-summary.js
@@ -0,0 +1,34 @@
+// We reuse the "backend" of the imperative Shadow DOM Distribution API for a new custom element, <my-detail>/<my-summary>.
+//TODO(crbug.com/869308):Emulate other <summary><details> features
+
+class MySummaryElement extends HTMLElement {
+  constructor() {
+    super();
+  }
+}
+customElements.define("my-summary", MySummaryElement);
+
+customElements.define("my-detail", class extends HTMLElement {
+  constructor() {
+    super();
+    this.attachShadow({ mode: "open", slotting: "manual" });
+  }
+  connectedCallback() {
+    const target = this;
+    if (!target.shadowRoot.querySelector(':scope > slot')) {
+      const slot1 = document.createElement("slot");
+      const slot2 = document.createElement("slot");
+      const shadowRoot = target.shadowRoot;
+      shadowRoot.appendChild(slot1);
+      shadowRoot.appendChild(slot2);
+      slot1.style.display = "block";
+      slot1.style.backgroundColor = "red";
+      const observer = new MutationObserver(function(mutations) {
+        //Get the first <my-summary> element from <my-detail>'s direct children
+        slot1.assign([target.querySelector(':scope > my-summary')]);
+        slot2.assign(target.childNodes);
+      });
+    observer.observe(this, {childList: true});
+    }
+  }
+});
diff --git a/third_party/WebKit/LayoutTests/shadow-dom/imperative-apis/test-custom-detail-summary-expected.html b/third_party/WebKit/LayoutTests/shadow-dom/imperative-apis/test-custom-detail-summary-expected.html
new file mode 100644
index 0000000..83138da
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/shadow-dom/imperative-apis/test-custom-detail-summary-expected.html
@@ -0,0 +1,7 @@
+<html>
+<body>
+  <p style="display: block; background-color: red;">added-summary</p>
+  <p>another test</p>
+  <p>summary first-summary</p>
+</body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/shadow-dom/imperative-apis/test-custom-detail-summary.html b/third_party/WebKit/LayoutTests/shadow-dom/imperative-apis/test-custom-detail-summary.html
new file mode 100644
index 0000000..47a34944
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/shadow-dom/imperative-apis/test-custom-detail-summary.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script src="custom-detail-summary.js"></script>
+
+<my-detail id="my-detail">
+  <p>another test</p>
+  <my-summary id="my-summary">summary</my-summary>
+  <my-summary>first-summary</my-summary>
+</my-detail>
+<my-summary id="my-summary1">added-summary</my-summary>
+<script>
+const host = document.querySelector("#my-detail");
+const sum = document.querySelector("#my-summary");
+const sum1 = document.querySelector("#my-summary1");
+host.insertBefore(sum1, sum);
+</script>
diff --git a/third_party/WebKit/LayoutTests/svg/dom/svganimatedinteger-initial-values.html b/third_party/WebKit/LayoutTests/svg/dom/svganimatedinteger-initial-values.html
new file mode 100644
index 0000000..b9d99071
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/svg/dom/svganimatedinteger-initial-values.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>SVGAnimatedInteger, initial values</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="resources/initial-value-helper.js"></script>
+<script>
+const propertyToContentAttribute = {
+  orderX: 'order',
+  orderY: 'order',
+};
+
+assert_initial_values([
+  { interface: 'SVGFEConvolveMatrixElement',
+    attributes: [ 'orderX', 'orderY', 'targetX', 'targetY' ],
+    orderX: { initial: 3 }, orderY: { initial: 3 } },
+  { interface: 'SVGFETurbulenceElement', attributes: [ 'numOctaves' ],
+    numOctaves: { initial: 1 } },
+], { initial: 0, valid: '42',
+     mapProperty: propertyToContentAttribute });
+</script>
diff --git a/third_party/WebKit/LayoutTests/svg/parser/whitespace-integer-expected.txt b/third_party/WebKit/LayoutTests/svg/parser/whitespace-integer-expected.txt
index 82d9a60..5bb52e7 100644
--- a/third_party/WebKit/LayoutTests/svg/parser/whitespace-integer-expected.txt
+++ b/third_party/WebKit/LayoutTests/svg/parser/whitespace-integer-expected.txt
@@ -444,125 +444,125 @@
 CONSOLE ERROR: line 63: Error: <feTurbulence> attribute numOctaves: Expected integer, "   1241245)90".
 This is a testharness.js-based test.
 PASS Test <integer> valid value: -47
-FAIL Test <integer> valid value: -47em assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47ex assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47px assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47in assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47cm assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47mm assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47pt assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47pc assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47% assert_equals: expected -47 but got 0
+FAIL Test <integer> valid value: -47em assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47ex assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47px assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47in assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47cm assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47mm assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47pt assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47pc assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47% assert_equals: expected -47 but got 1
 PASS Test <integer> valid value: 0
-PASS Test <integer> valid value: 0em
-PASS Test <integer> valid value: 0ex
-PASS Test <integer> valid value: 0px
-PASS Test <integer> valid value: 0in
-PASS Test <integer> valid value: 0cm
-PASS Test <integer> valid value: 0mm
-PASS Test <integer> valid value: 0pt
-PASS Test <integer> valid value: 0pc
-PASS Test <integer> valid value: 0%
+FAIL Test <integer> valid value: 0em assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0ex assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0px assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0in assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0cm assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0mm assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0pt assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0pc assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0% assert_equals: expected 0 but got 1
 PASS Test <integer> valid value: +32
-FAIL Test <integer> valid value: +32em assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32ex assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32px assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32in assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32cm assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32mm assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32pt assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32pc assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32% assert_equals: expected 32 but got 0
+FAIL Test <integer> valid value: +32em assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32ex assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32px assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32in assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32cm assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32mm assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32pt assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32pc assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32% assert_equals: expected 32 but got 1
 PASS Test <integer> valid value: 1241245
-FAIL Test <integer> valid value: 1241245em assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245ex assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245px assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245in assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245cm assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245mm assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245pt assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245pc assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245% assert_equals: expected 1241245 but got 0
+FAIL Test <integer> valid value: 1241245em assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245ex assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245px assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245in assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245cm assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245mm assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245pt assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245pc assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245% assert_equals: expected 1241245 but got 1
 PASS Test <integer> valid value: -47 
-FAIL Test <integer> valid value: -47em  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47ex  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47px  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47in  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47cm  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47mm  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47pt  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47pc  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47%  assert_equals: expected -47 but got 0
+FAIL Test <integer> valid value: -47em  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47ex  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47px  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47in  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47cm  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47mm  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47pt  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47pc  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47%  assert_equals: expected -47 but got 1
 PASS Test <integer> valid value: 0 
-PASS Test <integer> valid value: 0em 
-PASS Test <integer> valid value: 0ex 
-PASS Test <integer> valid value: 0px 
-PASS Test <integer> valid value: 0in 
-PASS Test <integer> valid value: 0cm 
-PASS Test <integer> valid value: 0mm 
-PASS Test <integer> valid value: 0pt 
-PASS Test <integer> valid value: 0pc 
-PASS Test <integer> valid value: 0% 
+FAIL Test <integer> valid value: 0em  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0ex  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0px  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0in  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0cm  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0mm  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0pt  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0pc  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0%  assert_equals: expected 0 but got 1
 PASS Test <integer> valid value: +32 
-FAIL Test <integer> valid value: +32em  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32ex  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32px  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32in  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32cm  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32mm  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32pt  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32pc  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32%  assert_equals: expected 32 but got 0
+FAIL Test <integer> valid value: +32em  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32ex  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32px  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32in  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32cm  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32mm  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32pt  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32pc  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32%  assert_equals: expected 32 but got 1
 PASS Test <integer> valid value: 1241245 
-FAIL Test <integer> valid value: 1241245em  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245ex  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245px  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245in  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245cm  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245mm  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245pt  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245pc  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245%  assert_equals: expected 1241245 but got 0
+FAIL Test <integer> valid value: 1241245em  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245ex  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245px  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245in  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245cm  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245mm  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245pt  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245pc  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245%  assert_equals: expected 1241245 but got 1
 PASS Test <integer> valid value: -47   
-FAIL Test <integer> valid value: -47em    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47ex    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47px    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47in    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47cm    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47mm    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47pt    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47pc    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value: -47%    assert_equals: expected -47 but got 0
+FAIL Test <integer> valid value: -47em    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47ex    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47px    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47in    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47cm    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47mm    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47pt    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47pc    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value: -47%    assert_equals: expected -47 but got 1
 PASS Test <integer> valid value: 0   
-PASS Test <integer> valid value: 0em   
-PASS Test <integer> valid value: 0ex   
-PASS Test <integer> valid value: 0px   
-PASS Test <integer> valid value: 0in   
-PASS Test <integer> valid value: 0cm   
-PASS Test <integer> valid value: 0mm   
-PASS Test <integer> valid value: 0pt   
-PASS Test <integer> valid value: 0pc   
-PASS Test <integer> valid value: 0%   
+FAIL Test <integer> valid value: 0em    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0ex    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0px    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0in    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0cm    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0mm    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0pt    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0pc    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value: 0%    assert_equals: expected 0 but got 1
 PASS Test <integer> valid value: +32   
-FAIL Test <integer> valid value: +32em    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32ex    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32px    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32in    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32cm    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32mm    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32pt    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32pc    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value: +32%    assert_equals: expected 32 but got 0
+FAIL Test <integer> valid value: +32em    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32ex    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32px    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32in    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32cm    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32mm    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32pt    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32pc    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value: +32%    assert_equals: expected 32 but got 1
 PASS Test <integer> valid value: 1241245   
-FAIL Test <integer> valid value: 1241245em    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245ex    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245px    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245in    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245cm    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245mm    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245pt    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245pc    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value: 1241245%    assert_equals: expected 1241245 but got 0
+FAIL Test <integer> valid value: 1241245em    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245ex    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245px    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245in    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245cm    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245mm    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245pt    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245pc    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value: 1241245%    assert_equals: expected 1241245 but got 1
 PASS Test <integer> trailing garbage, value: -47a
 PASS Test <integer> trailing garbage, value: 0a
 PASS Test <integer> trailing garbage, value: +32a
@@ -580,125 +580,125 @@
 PASS Test <integer> trailing garbage, value: +32)90
 PASS Test <integer> trailing garbage, value: 1241245)90
 PASS Test <integer> valid value:  -47
-FAIL Test <integer> valid value:  -47em assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47ex assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47px assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47in assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47cm assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47mm assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47pt assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47pc assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47% assert_equals: expected -47 but got 0
+FAIL Test <integer> valid value:  -47em assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47ex assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47px assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47in assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47cm assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47mm assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47pt assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47pc assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47% assert_equals: expected -47 but got 1
 PASS Test <integer> valid value:  0
-PASS Test <integer> valid value:  0em
-PASS Test <integer> valid value:  0ex
-PASS Test <integer> valid value:  0px
-PASS Test <integer> valid value:  0in
-PASS Test <integer> valid value:  0cm
-PASS Test <integer> valid value:  0mm
-PASS Test <integer> valid value:  0pt
-PASS Test <integer> valid value:  0pc
-PASS Test <integer> valid value:  0%
+FAIL Test <integer> valid value:  0em assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0ex assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0px assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0in assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0cm assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0mm assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0pt assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0pc assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0% assert_equals: expected 0 but got 1
 PASS Test <integer> valid value:  +32
-FAIL Test <integer> valid value:  +32em assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32ex assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32px assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32in assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32cm assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32mm assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32pt assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32pc assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32% assert_equals: expected 32 but got 0
+FAIL Test <integer> valid value:  +32em assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32ex assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32px assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32in assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32cm assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32mm assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32pt assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32pc assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32% assert_equals: expected 32 but got 1
 PASS Test <integer> valid value:  1241245
-FAIL Test <integer> valid value:  1241245em assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245ex assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245px assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245in assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245cm assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245mm assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245pt assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245pc assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245% assert_equals: expected 1241245 but got 0
+FAIL Test <integer> valid value:  1241245em assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245ex assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245px assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245in assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245cm assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245mm assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245pt assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245pc assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245% assert_equals: expected 1241245 but got 1
 PASS Test <integer> valid value:  -47 
-FAIL Test <integer> valid value:  -47em  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47ex  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47px  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47in  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47cm  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47mm  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47pt  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47pc  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47%  assert_equals: expected -47 but got 0
+FAIL Test <integer> valid value:  -47em  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47ex  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47px  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47in  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47cm  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47mm  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47pt  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47pc  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47%  assert_equals: expected -47 but got 1
 PASS Test <integer> valid value:  0 
-PASS Test <integer> valid value:  0em 
-PASS Test <integer> valid value:  0ex 
-PASS Test <integer> valid value:  0px 
-PASS Test <integer> valid value:  0in 
-PASS Test <integer> valid value:  0cm 
-PASS Test <integer> valid value:  0mm 
-PASS Test <integer> valid value:  0pt 
-PASS Test <integer> valid value:  0pc 
-PASS Test <integer> valid value:  0% 
+FAIL Test <integer> valid value:  0em  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0ex  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0px  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0in  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0cm  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0mm  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0pt  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0pc  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0%  assert_equals: expected 0 but got 1
 PASS Test <integer> valid value:  +32 
-FAIL Test <integer> valid value:  +32em  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32ex  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32px  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32in  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32cm  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32mm  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32pt  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32pc  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32%  assert_equals: expected 32 but got 0
+FAIL Test <integer> valid value:  +32em  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32ex  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32px  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32in  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32cm  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32mm  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32pt  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32pc  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32%  assert_equals: expected 32 but got 1
 PASS Test <integer> valid value:  1241245 
-FAIL Test <integer> valid value:  1241245em  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245ex  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245px  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245in  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245cm  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245mm  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245pt  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245pc  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245%  assert_equals: expected 1241245 but got 0
+FAIL Test <integer> valid value:  1241245em  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245ex  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245px  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245in  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245cm  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245mm  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245pt  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245pc  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245%  assert_equals: expected 1241245 but got 1
 PASS Test <integer> valid value:  -47   
-FAIL Test <integer> valid value:  -47em    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47ex    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47px    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47in    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47cm    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47mm    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47pt    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47pc    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:  -47%    assert_equals: expected -47 but got 0
+FAIL Test <integer> valid value:  -47em    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47ex    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47px    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47in    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47cm    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47mm    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47pt    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47pc    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:  -47%    assert_equals: expected -47 but got 1
 PASS Test <integer> valid value:  0   
-PASS Test <integer> valid value:  0em   
-PASS Test <integer> valid value:  0ex   
-PASS Test <integer> valid value:  0px   
-PASS Test <integer> valid value:  0in   
-PASS Test <integer> valid value:  0cm   
-PASS Test <integer> valid value:  0mm   
-PASS Test <integer> valid value:  0pt   
-PASS Test <integer> valid value:  0pc   
-PASS Test <integer> valid value:  0%   
+FAIL Test <integer> valid value:  0em    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0ex    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0px    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0in    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0cm    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0mm    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0pt    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0pc    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:  0%    assert_equals: expected 0 but got 1
 PASS Test <integer> valid value:  +32   
-FAIL Test <integer> valid value:  +32em    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32ex    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32px    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32in    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32cm    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32mm    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32pt    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32pc    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:  +32%    assert_equals: expected 32 but got 0
+FAIL Test <integer> valid value:  +32em    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32ex    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32px    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32in    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32cm    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32mm    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32pt    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32pc    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:  +32%    assert_equals: expected 32 but got 1
 PASS Test <integer> valid value:  1241245   
-FAIL Test <integer> valid value:  1241245em    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245ex    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245px    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245in    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245cm    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245mm    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245pt    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245pc    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:  1241245%    assert_equals: expected 1241245 but got 0
+FAIL Test <integer> valid value:  1241245em    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245ex    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245px    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245in    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245cm    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245mm    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245pt    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245pc    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:  1241245%    assert_equals: expected 1241245 but got 1
 PASS Test <integer> WS invalid value: -47 em
 PASS Test <integer> WS invalid value: 0 em
 PASS Test <integer> WS invalid value: +32 em
@@ -752,125 +752,125 @@
 PASS Test <integer> trailing garbage, value:  +32)90
 PASS Test <integer> trailing garbage, value:  1241245)90
 PASS Test <integer> valid value:    -47
-FAIL Test <integer> valid value:    -47em assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47ex assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47px assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47in assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47cm assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47mm assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47pt assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47pc assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47% assert_equals: expected -47 but got 0
+FAIL Test <integer> valid value:    -47em assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47ex assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47px assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47in assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47cm assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47mm assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47pt assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47pc assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47% assert_equals: expected -47 but got 1
 PASS Test <integer> valid value:    0
-PASS Test <integer> valid value:    0em
-PASS Test <integer> valid value:    0ex
-PASS Test <integer> valid value:    0px
-PASS Test <integer> valid value:    0in
-PASS Test <integer> valid value:    0cm
-PASS Test <integer> valid value:    0mm
-PASS Test <integer> valid value:    0pt
-PASS Test <integer> valid value:    0pc
-PASS Test <integer> valid value:    0%
+FAIL Test <integer> valid value:    0em assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0ex assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0px assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0in assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0cm assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0mm assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0pt assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0pc assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0% assert_equals: expected 0 but got 1
 PASS Test <integer> valid value:    +32
-FAIL Test <integer> valid value:    +32em assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32ex assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32px assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32in assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32cm assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32mm assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32pt assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32pc assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32% assert_equals: expected 32 but got 0
+FAIL Test <integer> valid value:    +32em assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32ex assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32px assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32in assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32cm assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32mm assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32pt assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32pc assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32% assert_equals: expected 32 but got 1
 PASS Test <integer> valid value:    1241245
-FAIL Test <integer> valid value:    1241245em assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245ex assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245px assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245in assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245cm assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245mm assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245pt assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245pc assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245% assert_equals: expected 1241245 but got 0
+FAIL Test <integer> valid value:    1241245em assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245ex assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245px assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245in assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245cm assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245mm assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245pt assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245pc assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245% assert_equals: expected 1241245 but got 1
 PASS Test <integer> valid value:    -47 
-FAIL Test <integer> valid value:    -47em  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47ex  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47px  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47in  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47cm  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47mm  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47pt  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47pc  assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47%  assert_equals: expected -47 but got 0
+FAIL Test <integer> valid value:    -47em  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47ex  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47px  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47in  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47cm  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47mm  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47pt  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47pc  assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47%  assert_equals: expected -47 but got 1
 PASS Test <integer> valid value:    0 
-PASS Test <integer> valid value:    0em 
-PASS Test <integer> valid value:    0ex 
-PASS Test <integer> valid value:    0px 
-PASS Test <integer> valid value:    0in 
-PASS Test <integer> valid value:    0cm 
-PASS Test <integer> valid value:    0mm 
-PASS Test <integer> valid value:    0pt 
-PASS Test <integer> valid value:    0pc 
-PASS Test <integer> valid value:    0% 
+FAIL Test <integer> valid value:    0em  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0ex  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0px  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0in  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0cm  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0mm  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0pt  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0pc  assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0%  assert_equals: expected 0 but got 1
 PASS Test <integer> valid value:    +32 
-FAIL Test <integer> valid value:    +32em  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32ex  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32px  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32in  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32cm  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32mm  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32pt  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32pc  assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32%  assert_equals: expected 32 but got 0
+FAIL Test <integer> valid value:    +32em  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32ex  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32px  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32in  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32cm  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32mm  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32pt  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32pc  assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32%  assert_equals: expected 32 but got 1
 PASS Test <integer> valid value:    1241245 
-FAIL Test <integer> valid value:    1241245em  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245ex  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245px  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245in  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245cm  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245mm  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245pt  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245pc  assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245%  assert_equals: expected 1241245 but got 0
+FAIL Test <integer> valid value:    1241245em  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245ex  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245px  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245in  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245cm  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245mm  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245pt  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245pc  assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245%  assert_equals: expected 1241245 but got 1
 PASS Test <integer> valid value:    -47   
-FAIL Test <integer> valid value:    -47em    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47ex    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47px    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47in    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47cm    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47mm    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47pt    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47pc    assert_equals: expected -47 but got 0
-FAIL Test <integer> valid value:    -47%    assert_equals: expected -47 but got 0
+FAIL Test <integer> valid value:    -47em    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47ex    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47px    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47in    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47cm    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47mm    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47pt    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47pc    assert_equals: expected -47 but got 1
+FAIL Test <integer> valid value:    -47%    assert_equals: expected -47 but got 1
 PASS Test <integer> valid value:    0   
-PASS Test <integer> valid value:    0em   
-PASS Test <integer> valid value:    0ex   
-PASS Test <integer> valid value:    0px   
-PASS Test <integer> valid value:    0in   
-PASS Test <integer> valid value:    0cm   
-PASS Test <integer> valid value:    0mm   
-PASS Test <integer> valid value:    0pt   
-PASS Test <integer> valid value:    0pc   
-PASS Test <integer> valid value:    0%   
+FAIL Test <integer> valid value:    0em    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0ex    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0px    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0in    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0cm    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0mm    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0pt    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0pc    assert_equals: expected 0 but got 1
+FAIL Test <integer> valid value:    0%    assert_equals: expected 0 but got 1
 PASS Test <integer> valid value:    +32   
-FAIL Test <integer> valid value:    +32em    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32ex    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32px    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32in    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32cm    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32mm    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32pt    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32pc    assert_equals: expected 32 but got 0
-FAIL Test <integer> valid value:    +32%    assert_equals: expected 32 but got 0
+FAIL Test <integer> valid value:    +32em    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32ex    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32px    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32in    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32cm    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32mm    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32pt    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32pc    assert_equals: expected 32 but got 1
+FAIL Test <integer> valid value:    +32%    assert_equals: expected 32 but got 1
 PASS Test <integer> valid value:    1241245   
-FAIL Test <integer> valid value:    1241245em    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245ex    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245px    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245in    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245cm    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245mm    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245pt    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245pc    assert_equals: expected 1241245 but got 0
-FAIL Test <integer> valid value:    1241245%    assert_equals: expected 1241245 but got 0
+FAIL Test <integer> valid value:    1241245em    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245ex    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245px    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245in    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245cm    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245mm    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245pt    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245pc    assert_equals: expected 1241245 but got 1
+FAIL Test <integer> valid value:    1241245%    assert_equals: expected 1241245 but got 1
 PASS Test <integer> WS invalid value: -47   em
 PASS Test <integer> WS invalid value: 0   em
 PASS Test <integer> WS invalid value: +32   em
diff --git a/third_party/WebKit/LayoutTests/svg/parser/whitespace-integer.html b/third_party/WebKit/LayoutTests/svg/parser/whitespace-integer.html
index 10b4a30..b503b22 100644
--- a/third_party/WebKit/LayoutTests/svg/parser/whitespace-integer.html
+++ b/third_party/WebKit/LayoutTests/svg/parser/whitespace-integer.html
@@ -25,7 +25,7 @@
 testType("<integer>",
          document.getElementsByTagName("feTurbulence")[0], // workaround for broken querySelector on camelcased elements
 		 "numOctaves",
-		 0, // expected default value (FIXME: should be 1)
+		 1, // expected default value
 		 whitespace,
 		 [ "-47", "0", "+32", "1241245" ],
 		 validunits,
diff --git a/third_party/blink/perf_tests/shadow_dom/custom-detail-summary.js b/third_party/blink/perf_tests/shadow_dom/custom-detail-summary.js
new file mode 100644
index 0000000..91a798a3
--- /dev/null
+++ b/third_party/blink/perf_tests/shadow_dom/custom-detail-summary.js
@@ -0,0 +1,34 @@
+// We reuse the "backend" of the imperative Shadow DOM Distribution API for a new custom element, <my-detail>/<my-summary>.
+//TODO(crbug.com/869308):Emulate other <summary><details> features
+
+class MySummaryElement extends HTMLElement {
+  constructor() {
+    super();
+  }
+}
+customElements.define("my-summary", MySummaryElement);
+
+customElements.define("my-detail", class extends HTMLElement {
+  constructor() {
+    super();
+    this.attachShadow({ mode: "open", slotting: "manual" });
+  }
+  connectedCallback() {
+    const target = this;
+    if (!target.shadowRoot.querySelector(':scope > slot')) {
+      const slot1 = document.createElement("slot");
+      const slot2 = document.createElement("slot");
+      const shadowRoot = target.shadowRoot;
+      shadowRoot.appendChild(slot1);
+      shadowRoot.appendChild(slot2);
+      slot1.style.display = "block";
+      slot1.style.backgroundColor = "red";
+      const observer = new MutationObserver(function(mutations) {
+        //Get the first <my-summary> element from <my-detail>'s direct children
+        slot1.assign(target.querySelector(':scope > my-summary'));
+        slot2.assign(target.childNodes);
+      });
+    observer.observe(this, {childList: true});
+    }
+  }
+});
diff --git a/third_party/blink/perf_tests/shadow_dom/imperative-api-custom-detail-summary.html b/third_party/blink/perf_tests/shadow_dom/imperative-api-custom-detail-summary.html
new file mode 100644
index 0000000..5305cc7
--- /dev/null
+++ b/third_party/blink/perf_tests/shadow_dom/imperative-api-custom-detail-summary.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<script src="../resources/runner.js"></script>
+<script src="custom-detail-summary.js"></script>
+<my-detail id="my-detail">
+  <my-summary id="my-summary">summary</my-summary>
+</my-detail>
+<my-summary id="my-summary1">added-summary1</my-summary>
+<my-summary id="my-summary2">added-summary2</my-summary>
+<script>
+  const host = document.querySelector("#my-detail");
+  const sum = document.querySelector("#my-summary");
+  const sum1 = document.querySelector("#my-summary1");
+  const sum2 = document.querySelector("#my-summary2");
+  window.onload = function() {
+    PerfTestRunner.measureTime({
+      description: "Measure performance of my-detail element in manual-slotting mode in shadow root when my-summary element is inserted.",
+      run: function() {
+        const start = PerfTestRunner.now();
+        for (let i = 0; i < 100; i++) {
+          host.appendChild(sum1);
+          host.insertBefore(sum2, sum);
+          PerfTestRunner.forceLayout();
+          sum1.remove();
+          sum2.remove();
+          PerfTestRunner.forceLayout();
+        }
+
+        return PerfTestRunner.now() - start;
+      }
+    });
+  }
+</script>
diff --git a/third_party/blink/perf_tests/shadow_dom/imperative-api-detail-summary.html b/third_party/blink/perf_tests/shadow_dom/imperative-api-detail-summary.html
new file mode 100644
index 0000000..d712eae
--- /dev/null
+++ b/third_party/blink/perf_tests/shadow_dom/imperative-api-detail-summary.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<script src="../resources/runner.js"></script>
+<detail id="detail">
+  <summary id="summary">summary</summary>
+</detail>
+<summary id="summary1">added-summary1</summary>
+<summary id="summary2">added-summary2</summary>
+<script>
+  const host = document.querySelector("#detail");
+  const sum = document.querySelector("#summary");
+  const sum1 = document.querySelector("#summary1");
+  const sum2 = document.querySelector("#summary2");
+  window.onload = function() {
+    PerfTestRunner.measureTime({
+      description: "Measure performance of built-in detail element when summary element is inserted.",
+      run: function() {
+        const start = PerfTestRunner.now();
+        for (let i = 0; i < 100; i++) {
+          host.appendChild(sum1);
+          host.insertBefore(sum2, sum);
+          PerfTestRunner.forceLayout();
+          sum1.remove();
+          sum2.remove();
+          PerfTestRunner.forceLayout();
+        }
+
+        return PerfTestRunner.now() - start;
+      }
+    });
+  }
+</script>
diff --git a/third_party/blink/public/platform/web_feature.mojom b/third_party/blink/public/platform/web_feature.mojom
index 06e6910..e4e34cd 100644
--- a/third_party/blink/public/platform/web_feature.mojom
+++ b/third_party/blink/public/platform/web_feature.mojom
@@ -1995,6 +1995,8 @@
   kTextDecoderStreamConstructor = 2540,
   kSignedExchangeInnerResponse = 2541,
   kPaymentAddressLanguageCode = 2542,
+  kDocumentDomainBlockedCrossOriginAccess = 2543,
+  kDocumentDomainEnabledCrossOriginAccess = 2544,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/renderer/bindings/bindings.gni b/third_party/blink/renderer/bindings/bindings.gni
index 1bcb1cf..b192bff 100644
--- a/third_party/blink/renderer/bindings/bindings.gni
+++ b/third_party/blink/renderer/bindings/bindings.gni
@@ -98,8 +98,8 @@
                     "core/v8/use_counter_callback.h",
                     "core/v8/v0_custom_element_constructor_builder.cc",
                     "core/v8/v0_custom_element_constructor_builder.h",
-                    "core/v8/v8_abstract_event_handler.cc",
-                    "core/v8/v8_abstract_event_handler.h",
+                    "core/v8/v8_abstract_event_listener.cc",
+                    "core/v8/v8_abstract_event_listener.h",
                     "core/v8/v8_binding_for_core.cc",
                     "core/v8/v8_binding_for_core.h",
                     "core/v8/v8_cache_options.h",
@@ -112,8 +112,6 @@
                     "core/v8/v8_embedder_graph_builder.h",
                     "core/v8/v8_error_handler.cc",
                     "core/v8/v8_error_handler.h",
-                    "core/v8/v8_event_listener_impl.cc",
-                    "core/v8/v8_event_listener_impl.h",
                     "core/v8/v8_event_listener_or_event_handler.cc",
                     "core/v8/v8_event_listener_or_event_handler.h",
                     "core/v8/v8_event_listener_helper.cc",
diff --git a/third_party/blink/renderer/bindings/core/v8/binding_security.cc b/third_party/blink/renderer/bindings/core/v8/binding_security.cc
index 8acd39b..ddc0dda9 100644
--- a/third_party/blink/renderer/bindings/core/v8/binding_security.cc
+++ b/third_party/blink/renderer/bindings/core/v8/binding_security.cc
@@ -65,10 +65,22 @@
   const SecurityOrigin* accessing_origin =
       accessing_window->document()->GetSecurityOrigin();
   const LocalDOMWindow* local_target_window = ToLocalDOMWindow(target_window);
-  if (!accessing_origin->CanAccess(
-          local_target_window->document()->GetSecurityOrigin())) {
-    return false;
+
+  SecurityOrigin::AccessResultDomainDetail detail;
+  bool can_access = accessing_origin->CanAccess(
+      local_target_window->document()->GetSecurityOrigin(), detail);
+  if (detail ==
+          SecurityOrigin::AccessResultDomainDetail::kDomainSetByOnlyOneOrigin ||
+      detail ==
+          SecurityOrigin::AccessResultDomainDetail::kDomainMatchNecessary ||
+      detail == SecurityOrigin::AccessResultDomainDetail::kDomainMismatch) {
+    UseCounter::Count(
+        accessing_window->GetFrame(),
+        can_access ? WebFeature::kDocumentDomainEnabledCrossOriginAccess
+                   : WebFeature::kDocumentDomainBlockedCrossOriginAccess);
   }
+  if (!can_access)
+    return false;
 
   // Notify the loader's client if the initial document has been accessed.
   LocalFrame* target_frame = local_target_window->GetFrame();
diff --git a/third_party/blink/renderer/bindings/core/v8/binding_security_test.cc b/third_party/blink/renderer/bindings/core/v8/binding_security_test.cc
index d1e2a81f..fdab789 100644
--- a/third_party/blink/renderer/bindings/core/v8/binding_security_test.cc
+++ b/third_party/blink/renderer/bindings/core/v8/binding_security_test.cc
@@ -17,56 +17,97 @@
 namespace {
 const char kMainFrame[] = "https://example.com/main.html";
 const char kSameOriginTarget[] = "https://example.com/target.html";
+const char kSameOriginDomainTarget[] = "https://sub.example.com/target.html";
 const char kCrossOriginTarget[] = "https://not-example.com/target.html";
+
+const char kTargetHTML[] =
+    "<!DOCTYPE html>"
+    "<script>"
+    "  (window.opener || window.top).postMessage('yay', '*');"
+    "</script>";
+const char kSameOriginDomainTargetHTML[] =
+    "<!DOCTYPE html>"
+    "<script>"
+    "  document.domain = 'example.com';"
+    "  (window.opener || window.top).postMessage('yay', '*');"
+    "</script>";
 }
 
 class BindingSecurityCounterTest
     : public SimTest,
       public testing::WithParamInterface<const char*> {
  public:
-  enum class OriginDisposition { CrossOrigin, SameOrigin };
+  enum class OriginDisposition { CrossOrigin, SameOrigin, SameOriginDomain };
 
   BindingSecurityCounterTest() = default;
 
   void LoadWindowAndAccessProperty(OriginDisposition which_origin,
                                    const String& property) {
+    const char* target_url;
+    const char* target_html;
+    switch (which_origin) {
+      case OriginDisposition::CrossOrigin:
+        target_url = kCrossOriginTarget;
+        target_html = kTargetHTML;
+        break;
+      case OriginDisposition::SameOrigin:
+        target_url = kSameOriginTarget;
+        target_html = kTargetHTML;
+        break;
+      case OriginDisposition::SameOriginDomain:
+        target_url = kSameOriginDomainTarget;
+        target_html = kSameOriginDomainTargetHTML;
+        break;
+    }
+
     SimRequest main(kMainFrame, "text/html");
-    SimRequest target(which_origin == OriginDisposition::CrossOrigin
-                          ? kCrossOriginTarget
-                          : kSameOriginTarget,
-                      "text/html");
+    SimRequest target(target_url, "text/html");
     const String& document = String::Format(
         "<!DOCTYPE html>"
         "<script>"
+        "  %s"
         "  window.addEventListener('message', e => {"
         "    window.other = e.source.%s;"
         "    console.log('yay');"
         "  });"
         "  var w = window.open('%s');"
         "</script>",
-        property.Utf8().data(),
-        which_origin == OriginDisposition::CrossOrigin ? kCrossOriginTarget
-                                                       : kSameOriginTarget);
+        which_origin == OriginDisposition::SameOriginDomain
+            ? "document.domain = 'example.com';"
+            : "",
+        property.Utf8().data(), target_url);
 
     LoadURL(kMainFrame);
     main.Complete(document);
-    target.Complete(
-        "<!DOCTYPE html>"
-        "<script>window.opener.postMessage('yay', '*');</script>");
+    target.Complete(target_html);
     test::RunPendingTasks();
   }
 
   void LoadFrameAndAccessProperty(OriginDisposition which_origin,
                                   const String& property) {
+    const char* target_url;
+    const char* target_html;
+    switch (which_origin) {
+      case OriginDisposition::CrossOrigin:
+        target_url = kCrossOriginTarget;
+        target_html = kTargetHTML;
+        break;
+      case OriginDisposition::SameOrigin:
+        target_url = kSameOriginTarget;
+        target_html = kTargetHTML;
+        break;
+      case OriginDisposition::SameOriginDomain:
+        target_url = kSameOriginDomainTarget;
+        target_html = kSameOriginDomainTargetHTML;
+        break;
+    }
     SimRequest main(kMainFrame, "text/html");
-    SimRequest target(which_origin == OriginDisposition::CrossOrigin
-                          ? kCrossOriginTarget
-                          : kSameOriginTarget,
-                      "text/html");
+    SimRequest target(target_url, "text/html");
     const String& document = String::Format(
         "<!DOCTYPE html>"
         "<body>"
         "<script>"
+        "  %s"
         "  var i = document.createElement('iframe');"
         "  window.addEventListener('message', e => {"
         "    window.other = e.source.%s;"
@@ -75,15 +116,14 @@
         "  i.src = '%s';"
         "  document.body.appendChild(i);"
         "</script>",
-        property.Utf8().data(),
-        which_origin == OriginDisposition::CrossOrigin ? kCrossOriginTarget
-                                                       : kSameOriginTarget);
+        which_origin == OriginDisposition::SameOriginDomain
+            ? "document.domain = 'example.com';"
+            : "",
+        property.Utf8().data(), target_url);
 
     LoadURL(kMainFrame);
     main.Complete(document);
-    target.Complete(
-        "<!DOCTYPE html>"
-        "<script>window.top.postMessage('yay', '*');</script>");
+    target.Complete(target_html);
     test::RunPendingTasks();
   }
 };
@@ -110,6 +150,8 @@
       WebFeature::kCrossOriginPropertyAccess));
   EXPECT_TRUE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
       WebFeature::kCrossOriginPropertyAccessFromOpener));
+  EXPECT_FALSE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
+      WebFeature::kDocumentDomainEnabledCrossOriginAccess));
 }
 
 TEST_P(BindingSecurityCounterTest, SameOriginWindow) {
@@ -118,6 +160,18 @@
       WebFeature::kCrossOriginPropertyAccess));
   EXPECT_FALSE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
       WebFeature::kCrossOriginPropertyAccessFromOpener));
+  EXPECT_FALSE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
+      WebFeature::kDocumentDomainEnabledCrossOriginAccess));
+}
+
+TEST_P(BindingSecurityCounterTest, SameOriginDomainWindow) {
+  LoadWindowAndAccessProperty(OriginDisposition::SameOriginDomain, GetParam());
+  EXPECT_FALSE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
+      WebFeature::kCrossOriginPropertyAccess));
+  EXPECT_FALSE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
+      WebFeature::kCrossOriginPropertyAccessFromOpener));
+  EXPECT_TRUE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
+      WebFeature::kDocumentDomainEnabledCrossOriginAccess));
 }
 
 TEST_P(BindingSecurityCounterTest, CrossOriginFrame) {
@@ -126,6 +180,8 @@
       WebFeature::kCrossOriginPropertyAccess));
   EXPECT_FALSE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
       WebFeature::kCrossOriginPropertyAccessFromOpener));
+  EXPECT_FALSE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
+      WebFeature::kDocumentDomainEnabledCrossOriginAccess));
 }
 
 TEST_P(BindingSecurityCounterTest, SameOriginFrame) {
@@ -134,6 +190,18 @@
       WebFeature::kCrossOriginPropertyAccess));
   EXPECT_FALSE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
       WebFeature::kCrossOriginPropertyAccessFromOpener));
+  EXPECT_FALSE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
+      WebFeature::kDocumentDomainEnabledCrossOriginAccess));
+}
+
+TEST_P(BindingSecurityCounterTest, SameOriginDomainFrame) {
+  LoadFrameAndAccessProperty(OriginDisposition::SameOriginDomain, GetParam());
+  EXPECT_FALSE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
+      WebFeature::kCrossOriginPropertyAccess));
+  EXPECT_FALSE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
+      WebFeature::kCrossOriginPropertyAccessFromOpener));
+  EXPECT_TRUE(GetDocument().Loader()->GetUseCounter().HasRecordedMeasurement(
+      WebFeature::kDocumentDomainEnabledCrossOriginAccess));
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/bindings/core/v8/script_event_listener.cc b/third_party/blink/renderer/bindings/core/v8/script_event_listener.cc
index 01c7dbb8..3450578 100644
--- a/third_party/blink/renderer/bindings/core/v8/script_event_listener.cc
+++ b/third_party/blink/renderer/bindings/core/v8/script_event_listener.cc
@@ -34,7 +34,6 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
 #include "third_party/blink/renderer/bindings/core/v8/source_location.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_event_listener_impl.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_lazy_event_listener.h"
 #include "third_party/blink/renderer/bindings/core/v8/window_proxy.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -98,13 +97,11 @@
 
 v8::Local<v8::Object> EventListenerHandler(ExecutionContext* execution_context,
                                            EventListener* listener) {
-  if (auto* v8_listener = V8AbstractEventHandler::Cast(listener)) {
-    return v8_listener->GetListenerObject(execution_context);
-  }
-  if (auto* v8_listener = V8EventListenerImpl::Cast(listener)) {
-    return v8_listener->GetListenerObject();
-  }
-  return v8::Local<v8::Object>();
+  if (listener->GetType() != EventListener::kJSEventListenerType)
+    return v8::Local<v8::Object>();
+  V8AbstractEventListener* v8_listener =
+      static_cast<V8AbstractEventListener*>(listener);
+  return v8_listener->GetListenerObject(execution_context);
 }
 
 v8::Local<v8::Function> EventListenerEffectiveFunction(
@@ -145,8 +142,6 @@
   column_number = function->GetScriptColumnNumber();
 }
 
-// TODO(yukiy): move this method into V8EventListenerImpl or interface class
-// of EventListener and EventHandler
 std::unique_ptr<SourceLocation> GetFunctionLocation(
     ExecutionContext* execution_context,
     EventListener* listener) {
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.cc b/third_party/blink/renderer/bindings/core/v8/v8_abstract_event_listener.cc
similarity index 86%
rename from third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.cc
rename to third_party/blink/renderer/bindings/core/v8/v8_abstract_event_listener.cc
index 9383312..2c686f39 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_abstract_event_listener.cc
@@ -28,7 +28,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_listener.h"
 
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_event.h"
@@ -48,24 +48,23 @@
 
 namespace blink {
 
-V8AbstractEventHandler::V8AbstractEventHandler(v8::Isolate* isolate,
-                                               bool is_attribute,
-                                               DOMWrapperWorld& world)
-    : EventListener(kJSEventHandlerType),
+V8AbstractEventListener::V8AbstractEventListener(v8::Isolate* isolate,
+                                                 bool is_attribute,
+                                                 DOMWrapperWorld& world)
+    : EventListener(kJSEventListenerType),
       is_attribute_(is_attribute),
       world_(&world),
       isolate_(isolate) {
-  if (IsMainThread()) {
+  if (IsMainThread())
     InstanceCounters::IncrementCounter(
         InstanceCounters::kJSEventListenerCounter);
-  }
 }
 
-V8AbstractEventHandler::~V8AbstractEventHandler() {
+V8AbstractEventListener::~V8AbstractEventListener() {
   // For non-main threads a termination garbage collection clears out the
   // wrapper links to CustomWrappable which result in CustomWrappable not being
   // rooted by JavaScript objects anymore. This means that
-  // V8AbstractEventHandler can be collected without while still holding a
+  // V8AbstractEventListener can be collected without while still holding a
   // valid weak references.
   if (IsMainThread()) {
     DCHECK(listener_.IsEmpty());
@@ -75,13 +74,13 @@
 }
 
 // static
-v8::Local<v8::Value> V8AbstractEventHandler::GetListenerOrNull(
+v8::Local<v8::Value> V8AbstractEventListener::GetListenerOrNull(
     v8::Isolate* isolate,
     EventTarget* event_target,
     EventListener* listener) {
-  if (listener && listener->GetType() == kJSEventHandlerType) {
+  if (listener && listener->GetType() == kJSEventListenerType) {
     v8::Local<v8::Object> v8_listener =
-        static_cast<V8AbstractEventHandler*>(listener)
+        static_cast<V8AbstractEventListener*>(listener)
             ->GetListenerObjectInternal(event_target->GetExecutionContext());
     if (!v8_listener.IsEmpty())
       return v8_listener;
@@ -89,8 +88,8 @@
   return v8::Null(isolate);
 }
 
-void V8AbstractEventHandler::handleEvent(ExecutionContext* execution_context,
-                                         Event* event) {
+void V8AbstractEventListener::handleEvent(ExecutionContext* execution_context,
+                                          Event* event) {
   if (!execution_context)
     return;
   // Don't reenter V8 if execution was terminated in this instance of V8.
@@ -111,8 +110,8 @@
   HandleEvent(script_state, event);
 }
 
-void V8AbstractEventHandler::HandleEvent(ScriptState* script_state,
-                                         Event* event) {
+void V8AbstractEventListener::HandleEvent(ScriptState* script_state,
+                                          Event* event) {
   ScriptState::Scope scope(script_state);
 
   // Get the V8 wrapper for the event object.
@@ -124,7 +123,7 @@
                      v8::Local<v8::Value>::New(GetIsolate(), js_event));
 }
 
-void V8AbstractEventHandler::SetListenerObject(
+void V8AbstractEventListener::SetListenerObject(
     ScriptState* script_state,
     v8::Local<v8::Object> listener,
     const V8PrivateProperty::Symbol& property) {
@@ -133,9 +132,10 @@
   Attach(script_state, listener, property, this);
 }
 
-void V8AbstractEventHandler::InvokeEventHandler(ScriptState* script_state,
-                                                Event* event,
-                                                v8::Local<v8::Value> js_event) {
+void V8AbstractEventListener::InvokeEventHandler(
+    ScriptState* script_state,
+    Event* event,
+    v8::Local<v8::Value> js_event) {
   if (!event->CanBeDispatchedInWorld(World()))
     return;
 
@@ -216,7 +216,7 @@
   }
 }
 
-bool V8AbstractEventHandler::ShouldPreventDefault(
+bool V8AbstractEventListener::ShouldPreventDefault(
     v8::Local<v8::Value> return_value,
     Event*) {
   // Prevent default action if the return value is false in accord with the spec
@@ -224,7 +224,7 @@
   return return_value->IsBoolean() && !return_value.As<v8::Boolean>()->Value();
 }
 
-v8::Local<v8::Object> V8AbstractEventHandler::GetReceiverObject(
+v8::Local<v8::Object> V8AbstractEventListener::GetReceiverObject(
     ScriptState* script_state,
     Event* event) {
   v8::Local<v8::Object> listener = listener_.NewLocal(GetIsolate());
@@ -240,7 +240,7 @@
                                     v8::Local<v8::Object>::Cast(value));
 }
 
-bool V8AbstractEventHandler::BelongsToTheCurrentWorld(
+bool V8AbstractEventListener::BelongsToTheCurrentWorld(
     ExecutionContext* execution_context) const {
   if (!GetIsolate()->GetCurrentContext().IsEmpty() &&
       &World() == &DOMWrapperWorld::Current(GetIsolate()))
@@ -256,19 +256,19 @@
   return false;
 }
 
-void V8AbstractEventHandler::ClearListenerObject() {
+void V8AbstractEventListener::ClearListenerObject() {
   if (!HasExistingListenerObject())
     return;
   probe::AsyncTaskCanceled(GetIsolate(), this);
   listener_.Clear();
 }
 
-void V8AbstractEventHandler::WrapperCleared(
-    const v8::WeakCallbackInfo<V8AbstractEventHandler>& data) {
+void V8AbstractEventListener::WrapperCleared(
+    const v8::WeakCallbackInfo<V8AbstractEventListener>& data) {
   data.GetParameter()->ClearListenerObject();
 }
 
-void V8AbstractEventHandler::Trace(blink::Visitor* visitor) {
+void V8AbstractEventListener::Trace(blink::Visitor* visitor) {
   visitor->Trace(listener_.Cast<v8::Value>());
   EventListener::Trace(visitor);
 }
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.h b/third_party/blink/renderer/bindings/core/v8/v8_abstract_event_listener.h
similarity index 89%
rename from third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.h
rename to third_party/blink/renderer/bindings/core/v8/v8_abstract_event_listener.h
index 5f04bf7..7ecb25f9 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.h
+++ b/third_party/blink/renderer/bindings/core/v8/v8_abstract_event_listener.h
@@ -28,8 +28,8 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_V8_ABSTRACT_EVENT_HANDLER_H_
-#define THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_V8_ABSTRACT_EVENT_HANDLER_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_V8_ABSTRACT_EVENT_LISTENER_H_
+#define THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_V8_ABSTRACT_EVENT_LISTENER_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/events/event_listener.h"
@@ -51,18 +51,18 @@
 // Why does this matter?
 // WebKit does not allow duplicated HTML event handlers of the same type,
 // but ALLOWs duplicated non-HTML event handlers.
-class CORE_EXPORT V8AbstractEventHandler : public EventListener {
+class CORE_EXPORT V8AbstractEventListener : public EventListener {
  public:
-  ~V8AbstractEventHandler() override;
+  ~V8AbstractEventListener() override;
 
-  static const V8AbstractEventHandler* Cast(const EventListener* listener) {
-    return listener->GetType() == kJSEventHandlerType
-               ? static_cast<const V8AbstractEventHandler*>(listener)
+  static const V8AbstractEventListener* Cast(const EventListener* listener) {
+    return listener->GetType() == kJSEventListenerType
+               ? static_cast<const V8AbstractEventListener*>(listener)
                : nullptr;
   }
 
-  static V8AbstractEventHandler* Cast(EventListener* listener) {
-    return const_cast<V8AbstractEventHandler*>(
+  static V8AbstractEventListener* Cast(EventListener* listener) {
+    return const_cast<V8AbstractEventListener*>(
         Cast(const_cast<const EventListener*>(listener)));
   }
 
@@ -115,7 +115,7 @@
   void Trace(blink::Visitor*) override;
 
  protected:
-  V8AbstractEventHandler(v8::Isolate*, bool is_attribute, DOMWrapperWorld&);
+  V8AbstractEventListener(v8::Isolate*, bool is_attribute, DOMWrapperWorld&);
 
   virtual v8::Local<v8::Object> GetListenerObjectInternal(
       ExecutionContext* execution_context) {
@@ -141,7 +141,7 @@
   virtual bool ShouldPreventDefault(v8::Local<v8::Value> return_value, Event*);
 
   static void WrapperCleared(
-      const v8::WeakCallbackInfo<V8AbstractEventHandler>&);
+      const v8::WeakCallbackInfo<V8AbstractEventListener>&);
 
   TraceWrapperV8Reference<v8::Object> listener_;
 
@@ -154,4 +154,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_V8_ABSTRACT_EVENT_HANDLER_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_V8_ABSTRACT_EVENT_LISTENER_H_
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.cc b/third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.cc
index e9208db..ac940688 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.cc
@@ -33,7 +33,6 @@
 #include "third_party/blink/renderer/bindings/core/v8/custom_wrappable_adapter.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_error_handler.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_event_listener_impl.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_event_listener_or_event_handler.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_window.h"
 #include "third_party/blink/renderer/platform/bindings/v8_private_property.h"
@@ -77,16 +76,11 @@
           ? V8PrivateProperty::GetCustomWrappableEventHandler(isolate)
           : V8PrivateProperty::GetCustomWrappableEventListener(isolate);
 
-  return GetEventListenerInternal<EventListener>(
+  return GetEventListenerInternal<V8AbstractEventListener>(
       script_state, object, listener_property, lookup,
       [object, is_attribute, script_state, listener_property]() {
-        return is_attribute
-                   ? static_cast<EventListener*>(
-                         V8EventListenerOrEventHandler::Create(
-                             object, is_attribute, script_state,
-                             listener_property))
-                   : static_cast<EventListener*>(V8EventListenerImpl::Create(
-                         object, script_state, listener_property));
+        return V8EventListenerOrEventHandler::Create(
+            object, is_attribute, script_state, listener_property);
       });
 }
 
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_impl.cc b/third_party/blink/renderer/bindings/core/v8/v8_event_listener_impl.cc
deleted file mode 100644
index b48cda0f..0000000
--- a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_impl.cc
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/bindings/core/v8/v8_event_listener_impl.h"
-
-#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
-#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
-#include "third_party/blink/renderer/core/dom/events/event.h"
-#include "third_party/blink/renderer/core/dom/events/event_target.h"
-#include "third_party/blink/renderer/core/execution_context/execution_context.h"
-#include "third_party/blink/renderer/core/probe/core_probes.h"
-#include "third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h"
-#include "third_party/blink/renderer/platform/bindings/v8_private_property.h"
-#include "third_party/blink/renderer/platform/instance_counters.h"
-
-namespace blink {
-
-V8EventListenerImpl::V8EventListenerImpl(
-    v8::Local<v8::Object> listener,
-    ScriptState* script_state,
-    const V8PrivateProperty::Symbol& property)
-    : EventListener(kJSEventListenerType),
-      event_listener_(V8EventListener::Create(listener)) {
-  Attach(script_state, listener, property, this);
-  if (IsMainThread()) {
-    InstanceCounters::IncrementCounter(
-        InstanceCounters::kJSEventListenerCounter);
-  }
-}
-
-V8EventListenerImpl::~V8EventListenerImpl() {
-  if (IsMainThread()) {
-    InstanceCounters::DecrementCounter(
-        InstanceCounters::kJSEventListenerCounter);
-  }
-}
-
-// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
-void V8EventListenerImpl::handleEvent(
-    ExecutionContext* execution_context_of_event_target,
-    Event* event) {
-  // Don't reenter V8 if execution was terminated in this instance of V8.
-  // For example, worker can be terminated in event listener, and also window
-  // can be terminated from inspector by the TerminateExecution method.
-  if (event_listener_->GetIsolate()->IsExecutionTerminating())
-    return;
-
-  DCHECK(execution_context_of_event_target);
-  DCHECK(event);
-  if (!event->CanBeDispatchedInWorld(World()))
-    return;
-
-  ScriptState* script_state_of_listener =
-      event_listener_->CallbackRelevantScriptState();
-  if (!script_state_of_listener->ContextIsValid())
-    return;
-
-  ScriptState::Scope scope(script_state_of_listener);
-
-  // https://dom.spec.whatwg.org/#firing-events
-  // Step 2. of firing events: Let event be the result of creating an event
-  // given eventConstructor, in the relevant Realm of target.
-  //
-  // (Note: a |js_event|, an v8::Value for |event| in the relevant realm of
-  // |event|'s target, is created here. It should be done before dispatching
-  // events, but Event and EventTarget do not have world to get |v8_context|, so
-  // it is done here with |execution_context_of_event_target|.)
-  v8::Local<v8::Context> v8_context =
-      ToV8Context(execution_context_of_event_target, World());
-  if (v8_context.IsEmpty())
-    return;
-  v8::Local<v8::Value> js_event =
-      ToV8(event, v8_context->Global(), event_listener_->GetIsolate());
-  if (js_event.IsEmpty())
-    return;
-
-  // Step 6: Let |global| be listener callback’s associated Realm’s global
-  // object.
-  v8::Local<v8::Object> global =
-      script_state_of_listener->GetContext()->Global();
-
-  // Step 8: If global is a Window object, then:
-  // Set currentEvent to global’s current event.
-  // If tuple’s item-in-shadow-tree is false, then set global’s current event to
-  // event.
-  V8PrivateProperty::Symbol event_symbol =
-      V8PrivateProperty::GetGlobalEvent(event_listener_->GetIsolate());
-  ExecutionContext* execution_context_of_listener =
-      ExecutionContext::From(script_state_of_listener);
-  v8::Local<v8::Value> current_event;
-  if (execution_context_of_listener->IsDocument()) {
-    current_event = event_symbol.GetOrUndefined(global).ToLocalChecked();
-    // Expose the event object as |window.event|, except when the event's target
-    // is in a V1 shadow tree.
-    Node* target_node = event->target()->ToNode();
-    if (!(target_node && target_node->IsInV1ShadowTree()))
-      event_symbol.Set(global, js_event);
-  }
-
-  {
-    // Catch exceptions thrown in the event listener if any and report them to
-    // DevTools console.
-    v8::TryCatch try_catch(event_listener_->GetIsolate());
-    try_catch.SetVerbose(true);
-
-    // Step 10: Call a listener with event's currentTarget as receiver and event
-    // and handle errors if thrown.
-    v8::Maybe<void> maybe_result =
-        event_listener_->handleEvent(event->currentTarget(), event);
-    ALLOW_UNUSED_LOCAL(maybe_result);
-
-    if (try_catch.HasCaught()) {
-      // Step 10-2: Set legacyOutputDidListenersThrowFlag if given.
-      event->LegacySetDidListenersThrowFlag();
-    }
-
-    // TODO(yukiy): consider to set |global|’s current event to |current_event|
-    // after execution is terminated if it is necessary and possible.
-    if (event_listener_->GetIsolate()->IsExecutionTerminating())
-      return;
-  }
-
-  // Step 12: If |global| is a Window object, then set |global|’s current event
-  // to |current_event|.
-  if (execution_context_of_listener->IsDocument())
-    event_symbol.Set(global, current_event);
-}
-
-void V8EventListenerImpl::Trace(blink::Visitor* visitor) {
-  visitor->Trace(event_listener_);
-  EventListener::Trace(visitor);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_impl.h b/third_party/blink/renderer/bindings/core/v8/v8_event_listener_impl.h
deleted file mode 100644
index 4ac9ba2..0000000
--- a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_impl.h
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_V8_EVENT_LISTENER_IMPL_H_
-#define THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_V8_EVENT_LISTENER_IMPL_H_
-
-#include "third_party/blink/renderer/bindings/core/v8/v8_event_listener.h"
-#include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/dom/events/event_listener.h"
-#include "third_party/blink/renderer/platform/bindings/trace_wrapper_member.h"
-#include "v8/include/v8.h"
-
-namespace blink {
-
-class Event;
-
-class CORE_EXPORT V8EventListenerImpl final : public EventListener {
- public:
-  static V8EventListenerImpl* Create(
-      v8::Local<v8::Object> listener,
-      ScriptState* script_state,
-      const V8PrivateProperty::Symbol& property) {
-    return new V8EventListenerImpl(listener, script_state, property);
-  }
-
-  static const V8EventListenerImpl* Cast(const EventListener* listener) {
-    return listener->GetType() == kJSEventListenerType
-               ? static_cast<const V8EventListenerImpl*>(listener)
-               : nullptr;
-  }
-
-  static V8EventListenerImpl* Cast(EventListener* listener) {
-    return const_cast<V8EventListenerImpl*>(
-        Cast(const_cast<const EventListener*>(listener)));
-  }
-
-  ~V8EventListenerImpl() override;
-
-  // Check the identity of |V8EventListener::callback_object_|. There can be
-  // multiple CallbackInterfaceBase objects that have the same
-  // |CallbackInterfaceBase::callback_object_| but have different
-  // |CallbackInterfaceBase::incumbent_script_state_|s.
-  bool operator==(const EventListener& other) const override {
-    const V8EventListenerImpl* other_event_listener = Cast(&other);
-    if (!other_event_listener)
-      return false;
-    return event_listener_->HasTheSameCallbackObject(
-        *other_event_listener->event_listener_);
-  }
-
-  // blink::EventListener overrides:
-  v8::Local<v8::Object> GetListenerObjectForInspector(
-      ExecutionContext* execution_context) override {
-    return event_listener_->CallbackObject();
-  }
-  DOMWrapperWorld* GetWorldForInspector() const override { return &World(); }
-  void handleEvent(ExecutionContext*, Event*) override;
-  void Trace(blink::Visitor*) override;
-
-  v8::Local<v8::Object> GetListenerObject() const {
-    return event_listener_->CallbackObject();
-  }
-
- private:
-  V8EventListenerImpl(v8::Local<v8::Object>,
-                      ScriptState*,
-                      const V8PrivateProperty::Symbol&);
-
-  DOMWrapperWorld& World() const {
-    return event_listener_->CallbackRelevantScriptState()->World();
-  }
-
-  const TraceWrapperMember<V8EventListener> event_listener_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_V8_EVENT_LISTENER_IMPL_H_
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_or_event_handler.cc b/third_party/blink/renderer/bindings/core/v8/v8_event_listener_or_event_handler.cc
index b345078b..4a87e26 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_or_event_handler.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_event_listener_or_event_handler.cc
@@ -42,9 +42,9 @@
 V8EventListenerOrEventHandler::V8EventListenerOrEventHandler(
     bool is_attribute,
     ScriptState* script_state)
-    : V8AbstractEventHandler(script_state->GetIsolate(),
-                             is_attribute,
-                             script_state->World()) {}
+    : V8AbstractEventListener(script_state->GetIsolate(),
+                              is_attribute,
+                              script_state->World()) {}
 
 v8::Local<v8::Function> V8EventListenerOrEventHandler::GetListenerFunction(
     ScriptState* script_state) {
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_or_event_handler.h b/third_party/blink/renderer/bindings/core/v8/v8_event_listener_or_event_handler.h
index b858e0c..c6c9680 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_or_event_handler.h
+++ b/third_party/blink/renderer/bindings/core/v8/v8_event_listener_or_event_handler.h
@@ -32,7 +32,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_V8_EVENT_LISTENER_OR_EVENT_HANDLER_H_
 
 #include "base/memory/scoped_refptr.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_listener.h"
 #include "v8/include/v8.h"
 
 namespace blink {
@@ -42,7 +42,7 @@
 // V8EventListenerOrEventHandler is a wrapper of a JS object implements
 // EventListener interface (has handleEvent(event) method), or a JS function
 // that can handle the event.
-class V8EventListenerOrEventHandler : public V8AbstractEventHandler {
+class V8EventListenerOrEventHandler : public V8AbstractEventListener {
  public:
   static V8EventListenerOrEventHandler* Create(
       v8::Local<v8::Object> listener,
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_lazy_event_listener.cc b/third_party/blink/renderer/bindings/core/v8/v8_lazy_event_listener.cc
index b36ccd6..6d103ef 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_lazy_event_listener.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_lazy_event_listener.cc
@@ -60,7 +60,7 @@
     const String source_url,
     const TextPosition& position,
     Node* node)
-    : V8AbstractEventHandler(isolate, true, DOMWrapperWorld::MainWorld()),
+    : V8AbstractEventListener(isolate, true, DOMWrapperWorld::MainWorld()),
       was_compilation_failed_(false),
       function_name_(function_name),
       event_parameter_name_(event_parameter_name),
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_lazy_event_listener.h b/third_party/blink/renderer/bindings/core/v8/v8_lazy_event_listener.h
index a936395..9bddefe 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_lazy_event_listener.h
+++ b/third_party/blink/renderer/bindings/core/v8/v8_lazy_event_listener.h
@@ -32,7 +32,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_V8_LAZY_EVENT_LISTENER_H_
 
 #include "base/memory/scoped_refptr.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_listener.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_position.h"
 #include "v8/include/v8.h"
 
@@ -44,7 +44,7 @@
 // V8LazyEventListener is a wrapper for a JavaScript code string that is
 // compiled and evaluated when an event is fired.  A V8LazyEventListener is
 // either a HTML or SVG event handler.
-class V8LazyEventListener final : public V8AbstractEventHandler {
+class V8LazyEventListener final : public V8AbstractEventListener {
  public:
   static V8LazyEventListener* Create(const AtomicString& function_name,
                                      const AtomicString& event_parameter_name,
@@ -59,7 +59,7 @@
 
   void Trace(blink::Visitor* visitor) override {
     visitor->Trace(node_);
-    V8AbstractEventHandler::Trace(visitor);
+    V8AbstractEventListener::Trace(visitor);
   }
 
   const String& Code() const override { return code_; }
diff --git a/third_party/blink/renderer/bindings/scripts/v8_types.py b/third_party/blink/renderer/bindings/scripts/v8_types.py
index 5b90f734..84f53633 100644
--- a/third_party/blink/renderer/bindings/scripts/v8_types.py
+++ b/third_party/blink/renderer/bindings/scripts/v8_types.py
@@ -412,7 +412,7 @@
                             'core/typed_arrays/array_buffer_view_helpers.h',
                             'core/typed_arrays/flexible_array_buffer_view.h']),
     'Dictionary': set(['bindings/core/v8/dictionary.h']),
-    'EventHandler': set(['bindings/core/v8/v8_abstract_event_handler.h',
+    'EventHandler': set(['bindings/core/v8/v8_abstract_event_listener.h',
                          'bindings/core/v8/v8_event_listener_helper.h']),
     'EventListener': set(['bindings/core/v8/binding_security.h',
                           'bindings/core/v8/v8_event_listener_helper.h',
@@ -1040,7 +1040,7 @@
     # Special cases
     'Dictionary': '{cpp_value}.V8Value()',
     'EventHandler':
-        'V8AbstractEventHandler::GetListenerOrNull({isolate}, impl, {cpp_value})',
+        'V8AbstractEventListener::GetListenerOrNull({isolate}, impl, {cpp_value})',
     'NodeFilter': 'ToV8({cpp_value}, {creation_context}, {isolate})',
     'Record': 'ToV8({cpp_value}, {creation_context}, {isolate})',
     'ScriptValue': '{cpp_value}.V8Value()',
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface.cc
index 41f24183..58935f73 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface.cc
@@ -15,7 +15,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_listener.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_configuration.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_element.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.h"
@@ -994,7 +994,7 @@
 
   EventListener* cppValue(WTF::GetPtr(impl->implementsEventHandlerAttribute()));
 
-  V8SetReturnValue(info, V8AbstractEventHandler::GetListenerOrNull(info.GetIsolate(), impl, cppValue));
+  V8SetReturnValue(info, V8AbstractEventListener::GetListenerOrNull(info.GetIsolate(), impl, cppValue));
 }
 
 static void implementsEventHandlerAttributeAttributeSetter(v8::Local<v8::Value> v8Value, const v8::FunctionCallbackInfo<v8::Value>& info) {
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface_node.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface_node.cc
index 40df4e5..8d7ecc3 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface_node.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface_node.cc
@@ -13,7 +13,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "third_party/blink/renderer/bindings/core/v8/idl_types.h"
 #include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_listener.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_configuration.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_test_interface_empty.h"
@@ -133,7 +133,7 @@
 
   EventListener* cppValue(WTF::GetPtr(impl->eventHandlerAttribute()));
 
-  V8SetReturnValue(info, V8AbstractEventHandler::GetListenerOrNull(info.GetIsolate(), impl, cppValue));
+  V8SetReturnValue(info, V8AbstractEventListener::GetListenerOrNull(info.GetIsolate(), impl, cppValue));
 }
 
 static void eventHandlerAttributeAttributeSetter(v8::Local<v8::Value> v8Value, const v8::FunctionCallbackInfo<v8::Value>& info) {
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_test_object.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_test_object.cc
index 371e7c6..0f6c0d0 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_test_object.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_test_object.cc
@@ -20,7 +20,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_factory.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_listener.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_array_buffer.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_array_buffer_view.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_attr.h"
@@ -1607,7 +1607,7 @@
 
   EventListener* cppValue(WTF::GetPtr(impl->eventHandlerAttribute()));
 
-  V8SetReturnValue(info, V8AbstractEventHandler::GetListenerOrNull(info.GetIsolate(), impl, cppValue));
+  V8SetReturnValue(info, V8AbstractEventListener::GetListenerOrNull(info.GetIsolate(), impl, cppValue));
 }
 
 static void eventHandlerAttributeAttributeSetter(v8::Local<v8::Value> v8Value, const v8::FunctionCallbackInfo<v8::Value>& info) {
diff --git a/third_party/blink/renderer/core/core_idl_files.gni b/third_party/blink/renderer/core/core_idl_files.gni
index f385828..68ec938 100644
--- a/third_party/blink/renderer/core/core_idl_files.gni
+++ b/third_party/blink/renderer/core/core_idl_files.gni
@@ -132,7 +132,6 @@
                     "dom/xml_document.idl",
                     "dom/events/custom_event.idl",
                     "dom/events/event.idl",
-                    "dom/events/event_listener.idl",
                     "dom/events/event_target.idl",
                     "trustedtypes/trusted_html.idl",
                     "trustedtypes/trusted_script.idl",
@@ -513,6 +512,7 @@
                     "dom/non_document_type_child_node.idl",
                     "dom/non_element_parent_node.idl",
                     "dom/parent_node.idl",
+                    "dom/events/event_listener.idl",
                     "events/navigator_events.idl",
                     "fetch/window_fetch.idl",
                     "fetch/worker_fetch.idl",
diff --git a/third_party/blink/renderer/core/css/resolver/style_adjuster.cc b/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
index a3289f99..ba38716 100644
--- a/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
@@ -567,7 +567,7 @@
                   element->LayoutObjectIsNeeded(style))) {
     // TODO(rakina): Move this attribute check somewhere else.
     if (RuntimeEnabledFeatures::InvisibleDOMEnabled() &&
-        !element->invisible().IsNull())
+        element->Invisible() != InvisibleState::kMissing)
       style.SetDisplay(EDisplay::kNone);
     else if (element->IsHTMLElement())
       AdjustStyleForHTMLElement(style, ToHTMLElement(*element));
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index 47590516..aaed7ad 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -1545,6 +1545,21 @@
   return *environment_variables_.get();
 }
 
+void StyleEngine::RecalcStyle(StyleRecalcChange change) {
+  DCHECK(GetDocument().documentElement());
+  DCHECK(GetDocument().ChildNeedsStyleRecalc() || change == kForce);
+
+  GetDocument().documentElement()->RecalcStyle(change);
+}
+
+void StyleEngine::RebuildLayoutTree() {
+  DCHECK(GetDocument().documentElement());
+  DCHECK(GetDocument().ChildNeedsReattachLayoutTree());
+
+  WhitespaceAttacher whitespace_attacher;
+  GetDocument().documentElement()->RebuildLayoutTree(whitespace_attacher);
+}
+
 void StyleEngine::Trace(blink::Visitor* visitor) {
   visitor->Trace(document_);
   visitor->Trace(injected_user_style_sheets_);
diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/blink/renderer/core/css/style_engine.h
index 12d3e36..27ad76e 100644
--- a/third_party/blink/renderer/core/css/style_engine.h
+++ b/third_party/blink/renderer/core/css/style_engine.h
@@ -319,6 +319,9 @@
 
   DocumentStyleEnvironmentVariables& EnsureEnvironmentVariables();
 
+  void RecalcStyle(StyleRecalcChange change);
+  void RebuildLayoutTree();
+
   void Trace(blink::Visitor*) override;
   const char* NameInHeapSnapshot() const override { return "StyleEngine"; }
 
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc
index f6ba60f0..2e11be1 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -848,14 +848,12 @@
     GetDocument().View()->UpdateAllLifecyclePhases();
   }
 
-  EXPECT_TRUE(
-      GetDocument().GetStyleEngine().HasViewportDependentMediaQueries());
+  EXPECT_TRUE(GetStyleEngine().HasViewportDependentMediaQueries());
 
   GetDocument().body()->RemoveChild(style_element);
   GetDocument().View()->UpdateAllLifecyclePhases();
 
-  EXPECT_FALSE(
-      GetDocument().GetStyleEngine().HasViewportDependentMediaQueries());
+  EXPECT_FALSE(GetStyleEngine().HasViewportDependentMediaQueries());
 }
 
 TEST_F(StyleEngineTest, StyleMediaAttributeStyleChange) {
@@ -1482,7 +1480,7 @@
   )HTML");
   GetDocument().View()->UpdateAllLifecyclePhases();
 
-  StyleEngine& engine = GetDocument().GetStyleEngine();
+  StyleEngine& engine = GetStyleEngine();
   engine.SetStatsEnabled(true);
 
   StyleResolverStats* stats = engine.Stats();
@@ -1493,7 +1491,7 @@
   div->SetInlineStyleProperty(CSSPropertyColor, "green");
 
   GetDocument().Lifecycle().AdvanceTo(DocumentLifecycle::kInStyleRecalc);
-  GetDocument().documentElement()->RecalcStyle(kNoChange);
+  GetStyleEngine().RecalcStyle(kNoChange);
 
   // Should fast reject ".not-in-filter div::before {}" for both the div and its
   // ::before pseudo element.
@@ -1514,35 +1512,35 @@
   GetDocument().View()->UpdateAllLifecyclePhases();
 
   d1->firstChild()->remove();
-  EXPECT_TRUE(GetDocument().GetStyleEngine().NeedsWhitespaceReattachment(d1));
+  EXPECT_TRUE(GetStyleEngine().NeedsWhitespaceReattachment(d1));
   EXPECT_FALSE(GetDocument().ChildNeedsStyleInvalidation());
   EXPECT_FALSE(GetDocument().ChildNeedsStyleRecalc());
   EXPECT_FALSE(GetDocument().ChildNeedsReattachLayoutTree());
 
-  GetDocument().GetStyleEngine().MarkForWhitespaceReattachment();
+  GetStyleEngine().MarkForWhitespaceReattachment();
   EXPECT_FALSE(GetDocument().ChildNeedsReattachLayoutTree());
 
   GetDocument().View()->UpdateAllLifecyclePhases();
 
   d2->firstChild()->remove();
   d2->firstChild()->remove();
-  EXPECT_TRUE(GetDocument().GetStyleEngine().NeedsWhitespaceReattachment(d2));
+  EXPECT_TRUE(GetStyleEngine().NeedsWhitespaceReattachment(d2));
   EXPECT_FALSE(GetDocument().ChildNeedsStyleInvalidation());
   EXPECT_FALSE(GetDocument().ChildNeedsStyleRecalc());
   EXPECT_FALSE(GetDocument().ChildNeedsReattachLayoutTree());
 
-  GetDocument().GetStyleEngine().MarkForWhitespaceReattachment();
+  GetStyleEngine().MarkForWhitespaceReattachment();
   EXPECT_FALSE(GetDocument().ChildNeedsReattachLayoutTree());
 
   GetDocument().View()->UpdateAllLifecyclePhases();
 
   d3->firstChild()->remove();
-  EXPECT_TRUE(GetDocument().GetStyleEngine().NeedsWhitespaceReattachment(d3));
+  EXPECT_TRUE(GetStyleEngine().NeedsWhitespaceReattachment(d3));
   EXPECT_FALSE(GetDocument().ChildNeedsStyleInvalidation());
   EXPECT_FALSE(GetDocument().ChildNeedsStyleRecalc());
   EXPECT_FALSE(GetDocument().ChildNeedsReattachLayoutTree());
 
-  GetDocument().GetStyleEngine().MarkForWhitespaceReattachment();
+  GetStyleEngine().MarkForWhitespaceReattachment();
   EXPECT_TRUE(GetDocument().ChildNeedsReattachLayoutTree());
 }
 
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index e628c1a..2be447d8 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -2304,7 +2304,7 @@
       TRACE_EVENT0("blink,blink_style", "Document::recalcStyle");
       SCOPED_BLINK_UMA_HISTOGRAM_TIMER_HIGHRES("Style.RecalcTime");
       Element* viewport_defining = ViewportDefiningElement();
-      document_element->RecalcStyle(change);
+      GetStyleEngine().RecalcStyle(change);
       if (viewport_defining != ViewportDefiningElement())
         ViewportDefiningElementDidChange();
     }
@@ -2315,8 +2315,7 @@
       TRACE_EVENT0("blink,blink_style", "Document::rebuildLayoutTree");
       SCOPED_BLINK_UMA_HISTOGRAM_TIMER_HIGHRES("Style.RebuildLayoutTreeTime");
       ReattachLegacyLayoutObjectList legacy_layout_objects(*this);
-      WhitespaceAttacher whitespace_attacher;
-      document_element->RebuildLayoutTree(whitespace_attacher);
+      GetStyleEngine().RebuildLayoutTree();
       legacy_layout_objects.ForceLegacyLayoutIfNeeded();
     }
   }
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index dfd5ac9a..c5d19bd 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -1439,12 +1439,13 @@
   return rare_data.EnsureAccessibleNode(this);
 }
 
-const AtomicString& Element::invisible() const {
-  return FastGetAttribute(invisibleAttr);
-}
-
-void Element::setInvisible(const AtomicString& value) {
-  setAttribute(invisibleAttr, value);
+InvisibleState Element::Invisible() const {
+  const AtomicString& value = FastGetAttribute(invisibleAttr);
+  if (value.IsNull())
+    return InvisibleState::kMissing;
+  if (EqualIgnoringASCIICase(value, "static"))
+    return InvisibleState::kStatic;
+  return InvisibleState::kInvisible;
 }
 
 void Element::DispatchActivateInvisibleEventIfNeeded() {
@@ -1459,7 +1460,8 @@
   HeapVector<Member<Element>> invisible_ancestors;
   HeapVector<Member<Element>> activated_elements;
   for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(*this)) {
-    if (ancestor.IsElementNode() && ToElement(ancestor).invisible()) {
+    if (ancestor.IsElementNode() &&
+        ToElement(ancestor).Invisible() != InvisibleState::kMissing) {
       invisible_ancestors.push_back(ToElement(ancestor));
       activated_elements.push_back(ancestor.GetTreeScope().Retarget(*this));
     }
@@ -1473,10 +1475,28 @@
   }
 }
 
-void Element::InvisibleAttributeChanged() {
-  SetNeedsStyleRecalc(
-      kLocalStyleChange,
-      StyleChangeReasonForTracing::Create(StyleChangeReason::kInvisibleChange));
+bool Element::IsInsideInvisibleStaticSubtree() {
+  for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(*this)) {
+    if (ancestor.IsElementNode() &&
+        ToElement(ancestor).Invisible() == InvisibleState::kStatic)
+      return true;
+  }
+  return false;
+}
+
+void Element::InvisibleAttributeChanged(const AtomicString& old_value,
+                                        const AtomicString& new_value) {
+  if (old_value.IsNull() != new_value.IsNull()) {
+    SetNeedsStyleRecalc(kLocalStyleChange,
+                        StyleChangeReasonForTracing::Create(
+                            StyleChangeReason::kInvisibleChange));
+  }
+  if (EqualIgnoringASCIICase(old_value, "static") &&
+      !IsInsideInvisibleStaticSubtree()) {
+    // This element and its descendants are not in an invisible="static" tree
+    // anymore.
+    CustomElement::Registry(*this)->upgrade(this);
+  }
 }
 
 void Element::DefaultEventHandler(Event& event) {
@@ -1788,8 +1808,8 @@
                           StyleChangeReasonForTracing::FromAttribute(name));
     } else if (RuntimeEnabledFeatures::InvisibleDOMEnabled() &&
                name == HTMLNames::invisibleAttr &&
-               params.old_value.IsNull() != params.new_value.IsNull()) {
-      InvisibleAttributeChanged();
+               params.old_value != params.new_value) {
+      InvisibleAttributeChanged(params.old_value, params.new_value);
     }
   }
 
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 899ea5b..311b33d 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -121,6 +121,12 @@
   kNameOrIdWithName,
 };
 
+enum class InvisibleState {
+  kMissing,
+  kStatic,
+  kInvisible,
+};
+
 struct FocusParams {
   STACK_ALLOCATED();
 
@@ -333,9 +339,9 @@
   AccessibleNode* ExistingAccessibleNode() const;
   AccessibleNode* accessibleNode();
 
-  const AtomicString& invisible() const;
-  void setInvisible(const AtomicString&);
+  InvisibleState Invisible() const;
   void DispatchActivateInvisibleEventIfNeeded();
+  bool IsInsideInvisibleStaticSubtree();
 
   void DefaultEventHandler(Event&) override;
 
@@ -508,9 +514,8 @@
   void RecalcStyle(StyleRecalcChange);
   bool NeedsRebuildLayoutTree(
       const WhitespaceAttacher& whitespace_attacher) const {
-    // TODO(futhark@chromium.org): !CanParticipateInFlatTree() can be replaced
-    // by IsActiveV0InsertionPoint() when slots are always part of the flat
-    // tree, and removed completely when Shadow DOM V0 support is removed.
+    // TODO(futhark@chromium.org): !CanParticipateInFlatTree() can be removed
+    // when Shadow DOM V0 support is removed.
     return NeedsReattachLayoutTree() || ChildNeedsReattachLayoutTree() ||
            !CanParticipateInFlatTree() ||
            (whitespace_attacher.TraverseIntoDisplayContents() &&
@@ -992,7 +997,8 @@
   void InlineStyleChanged();
   void SetInlineStyleFromString(const AtomicString&);
 
-  void InvisibleAttributeChanged();
+  void InvisibleAttributeChanged(const AtomicString& old_value,
+                                 const AtomicString& new_value);
 
   // If the only inherited changes in the parent element are independent,
   // these changes can be directly propagated to this element (the child).
diff --git a/third_party/blink/renderer/core/dom/element.idl b/third_party/blink/renderer/core/dom/element.idl
index 4aba4f7..0726b0d 100644
--- a/third_party/blink/renderer/core/dom/element.idl
+++ b/third_party/blink/renderer/core/dom/element.idl
@@ -135,7 +135,7 @@
     // Accessibility Object Model
     [RuntimeEnabled=AccessibilityObjectModel] readonly attribute AccessibleNode? accessibleNode;
 
-    [RuntimeEnabled=InvisibleDOM, CEReactions, CustomElementCallbacks] attribute DOMString invisible;
+    [RuntimeEnabled=InvisibleDOM, CEReactions, CustomElementCallbacks, Reflect, ReflectOnly=("static","invisible"), ReflectEmpty="invisible", ReflectInvalid="invisible"] attribute DOMString invisible;
 
     // Event handler attributes
     attribute EventHandler onbeforecopy;
diff --git a/third_party/blink/renderer/core/dom/events/event_listener.h b/third_party/blink/renderer/core/dom/events/event_listener.h
index 0e73cdb..70942bb3 100644
--- a/third_party/blink/renderer/core/dom/events/event_listener.h
+++ b/third_party/blink/renderer/core/dom/events/event_listener.h
@@ -36,13 +36,7 @@
 class CORE_EXPORT EventListener : public CustomWrappableAdapter {
  public:
   enum ListenerType {
-    // |kJSEventListenerType| corresponds to EventListener defined in standard:
-    // https://dom.spec.whatwg.org/#callbackdef-eventlistener
     kJSEventListenerType,
-    // |kJSEventHandlerType| corresponds to EventHandler defined in standard:
-    // https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-attributes
-    kJSEventHandlerType,
-    // These are for C++ native callbacks.
     kImageEventListenerType,
     kCPPEventListenerType,
     kConditionEventListenerType,
@@ -70,14 +64,6 @@
 
   ListenerType GetType() const { return type_; }
 
-  // Returns true if this EventListener is implemented based on JS object.
-  bool IsJSBased() const {
-    return type_ == kJSEventListenerType || type_ == kJSEventHandlerType;
-  }
-
-  // Returns true if this EventListener is C++ native callback.
-  bool IsNativeBased() const { return !IsJSBased(); }
-
   const char* NameInHeapSnapshot() const override { return "EventListener"; }
 
  protected:
diff --git a/third_party/blink/renderer/core/dom/events/event_target.cc b/third_party/blink/renderer/core/dom/events/event_target.cc
index c42026d..80ceabc 100644
--- a/third_party/blink/renderer/core/dom/events/event_target.cc
+++ b/third_party/blink/renderer/core/dom/events/event_target.cc
@@ -39,8 +39,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/event_listener_options_or_boolean.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_event_listener.h"
 #include "third_party/blink/renderer/bindings/core/v8/source_location.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_event_listener_impl.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_listener.h"
 #include "third_party/blink/renderer/core/dom/events/event.h"
 #include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h"
 #include "third_party/blink/renderer/core/dom/events/event_target_impl.h"
@@ -290,34 +289,31 @@
   if (RuntimeEnabledFeatures::SmoothScrollJSInterventionEnabled() &&
       event_type == EventTypeNames::mousewheel && ToLocalDOMWindow() &&
       event_listener && !options.hasPassive()) {
-    v8::Local<v8::Object> callback_object;
-    if (V8AbstractEventHandler* v8_listener =
-            V8AbstractEventHandler::Cast(event_listener))
-      callback_object = v8_listener->GetExistingListenerObject();
-    if (V8EventListenerImpl* v8_listener =
-            V8EventListenerImpl::Cast(event_listener))
-      callback_object = v8_listener->GetListenerObject();
-    if (!callback_object.IsEmpty() && callback_object->IsFunction() &&
-        strcmp(
-            "ssc_wheel",
-            *v8::String::Utf8Value(
-                v8::Isolate::GetCurrent(),
-                v8::Local<v8::Function>::Cast(callback_object)->GetName())) ==
-            0) {
-      options.setPassive(true);
-      if (executing_window) {
-        UseCounter::Count(executing_window->document(),
-                          WebFeature::kSmoothScrollJSInterventionActivated);
+    if (V8AbstractEventListener* v8_listener =
+            V8AbstractEventListener::Cast(event_listener)) {
+      v8::Local<v8::Object> function = v8_listener->GetExistingListenerObject();
+      if (function->IsFunction() &&
+          strcmp("ssc_wheel",
+                 *v8::String::Utf8Value(
+                     v8::Isolate::GetCurrent(),
+                     v8::Local<v8::Function>::Cast(function)->GetName())) ==
+              0) {
+        options.setPassive(true);
+        if (executing_window) {
+          UseCounter::Count(executing_window->document(),
+                            WebFeature::kSmoothScrollJSInterventionActivated);
 
-        executing_window->GetFrame()->Console().AddMessage(
-            ConsoleMessage::Create(
-                kInterventionMessageSource, kWarningMessageLevel,
-                "Registering mousewheel event as passive due to "
-                "smoothscroll.js usage. The smoothscroll.js library is "
-                "buggy, no longer necessary and degrades performance. See "
-                "https://www.chromestatus.com/feature/5749447073988608"));
+          executing_window->GetFrame()->Console().AddMessage(
+              ConsoleMessage::Create(
+                  kInterventionMessageSource, kWarningMessageLevel,
+                  "Registering mousewheel event as passive due to "
+                  "smoothscroll.js usage. The smoothscroll.js library is "
+                  "buggy, no longer necessary and degrades performance. See "
+                  "https://www.chromestatus.com/feature/5749447073988608"));
+        }
+
+        return;
       }
-      return;
     }
   }
 
@@ -406,7 +402,8 @@
       event_type, listener, options, &registered_listener);
   if (added) {
     AddedEventListener(event_type, registered_listener);
-    if (listener->IsJSBased() && IsInstrumentedForAsyncStack(event_type)) {
+    if (V8AbstractEventListener::Cast(listener) &&
+        IsInstrumentedForAsyncStack(event_type)) {
       probe::AsyncTaskScheduled(GetExecutionContext(), event_type, listener);
     }
   }
@@ -541,7 +538,8 @@
     return false;
   }
   if (registered_listener) {
-    if (listener->IsJSBased() && IsInstrumentedForAsyncStack(event_type)) {
+    if (V8AbstractEventListener::Cast(listener) &&
+        IsInstrumentedForAsyncStack(event_type)) {
       probe::AsyncTaskScheduled(GetExecutionContext(), event_type, listener);
     }
     registered_listener->SetCallback(listener);
diff --git a/third_party/blink/renderer/core/dom/layout_tree_builder.cc b/third_party/blink/renderer/core/dom/layout_tree_builder.cc
index cc566ec..0c2925b 100644
--- a/third_party/blink/renderer/core/dom/layout_tree_builder.cc
+++ b/third_party/blink/renderer/core/dom/layout_tree_builder.cc
@@ -298,10 +298,8 @@
   for (const LayoutObject* block : blocks_)
     ToElement(*block->GetNode()).LazyReattachIfAttached();
   state_ = State::kForcingLegacyLayout;
-  Element& document_element = *document_->documentElement();
-  document_element.RecalcStyle(kNoChange);
-  WhitespaceAttacher whitespace_attacher;
-  document_element.RebuildLayoutTree(whitespace_attacher);
+  document_->GetStyleEngine().RecalcStyle(kNoChange);
+  document_->GetStyleEngine().RebuildLayoutTree();
   state_ = State::kClosed;
 }
 
diff --git a/third_party/blink/renderer/core/dom/node_test.cc b/third_party/blink/renderer/core/dom/node_test.cc
index b03e16f1..a977ddc6 100644
--- a/third_party/blink/renderer/core/dom/node_test.cc
+++ b/third_party/blink/renderer/core/dom/node_test.cc
@@ -6,6 +6,7 @@
 
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
+#include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/comment.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
@@ -37,7 +38,7 @@
   LayoutObject* ReattachLayoutTreeForNode(Node& node) {
     node.LazyReattachIfAttached();
     GetDocument().Lifecycle().AdvanceTo(DocumentLifecycle::kInStyleRecalc);
-    GetDocument().documentElement()->RecalcStyle(kNoChange);
+    GetDocument().GetStyleEngine().RecalcStyle(kNoChange);
     PushSelectorFilterAncestors(
         GetDocument().EnsureStyleResolver().GetSelectorFilter(), node);
     ReattachLegacyLayoutObjectList legacy_objects(GetDocument());
diff --git a/third_party/blink/renderer/core/fetch/fetch_header_list.cc b/third_party/blink/renderer/core/fetch/fetch_header_list.cc
index 2839f94..7bacec12 100644
--- a/third_party/blink/renderer/core/fetch/fetch_header_list.cc
+++ b/third_party/blink/renderer/core/fetch/fetch_header_list.cc
@@ -128,13 +128,6 @@
   header_list_.clear();
 }
 
-bool FetchHeaderList::ContainsNonCORSSafelistedHeader() const {
-  return std::any_of(
-      header_list_.cbegin(), header_list_.cend(), [](const Header& header) {
-        return !CORS::IsCORSSafelistedHeader(header.first, header.second);
-      });
-}
-
 Vector<FetchHeaderList::Header> FetchHeaderList::SortAndCombine() const {
   // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
   // "To sort and combine a header list (|list|), run these steps:
diff --git a/third_party/blink/renderer/core/fetch/fetch_header_list.h b/third_party/blink/renderer/core/fetch/fetch_header_list.h
index e84f0985..7cd863c 100644
--- a/third_party/blink/renderer/core/fetch/fetch_header_list.h
+++ b/third_party/blink/renderer/core/fetch/fetch_header_list.h
@@ -41,7 +41,6 @@
   bool Has(const String&) const;
   void ClearList();
 
-  bool ContainsNonCORSSafelistedHeader() const;
   Vector<Header> SortAndCombine() const;
 
   const std::multimap<String, String, ByteCaseInsensitiveCompare>& List()
@@ -67,8 +66,7 @@
   // This would cause FetchHeaderList::size() to have to manually
   // iterate through all keys and vectors in the HashMap. Similarly,
   // list() would require callers to manually iterate through the
-  // HashMap's keys and value vector, and so would
-  // ContainsNonCORSSafelistedHeader().
+  // HashMap's keys and value vector.
   std::multimap<String, String, ByteCaseInsensitiveCompare> header_list_;
 };
 
diff --git a/third_party/blink/renderer/core/fetch/fetch_header_list_test.cc b/third_party/blink/renderer/core/fetch/fetch_header_list_test.cc
index e23bc70..05b0f54 100644
--- a/third_party/blink/renderer/core/fetch/fetch_header_list_test.cc
+++ b/third_party/blink/renderer/core/fetch/fetch_header_list_test.cc
@@ -117,26 +117,6 @@
   EXPECT_FALSE(headerList->Has("X-Bar"));
 }
 
-TEST(FetchHeaderListTest, ContainsNonCORSSafelistedHeader) {
-  FetchHeaderList* headerList = FetchHeaderList::Create();
-  EXPECT_FALSE(headerList->ContainsNonCORSSafelistedHeader());
-
-  headerList->Append("Host", "foobar");
-  headerList->Append("X-Foo", "bar");
-  EXPECT_TRUE(headerList->ContainsNonCORSSafelistedHeader());
-
-  headerList->ClearList();
-  headerList->Append("ConTenT-TyPe", "text/plain");
-  headerList->Append("content-type", "application/xml");
-  headerList->Append("X-Foo", "bar");
-  EXPECT_TRUE(headerList->ContainsNonCORSSafelistedHeader());
-
-  headerList->ClearList();
-  headerList->Append("ConTenT-TyPe", "multipart/form-data");
-  headerList->Append("Accept", "xyz");
-  EXPECT_FALSE(headerList->ContainsNonCORSSafelistedHeader());
-}
-
 TEST(FetchHeaderListTest, SortAndCombine) {
   FetchHeaderList* headerList = FetchHeaderList::Create();
   EXPECT_TRUE(headerList->SortAndCombine().IsEmpty());
diff --git a/third_party/blink/renderer/core/fetch/fetch_manager.cc b/third_party/blink/renderer/core/fetch/fetch_manager.cc
index 9cc875b..9a2df6fc 100644
--- a/third_party/blink/renderer/core/fetch/fetch_manager.cc
+++ b/third_party/blink/renderer/core/fetch/fetch_manager.cc
@@ -858,27 +858,9 @@
   request.SetUseStreamOnResponse(true);
   request.SetExternalRequestStateFromRequestorAddressSpace(
       execution_context_->GetSecurityContext().AddressSpace());
+  request.SetReferrerString(fetch_request_data_->ReferrerString());
+  request.SetReferrerPolicy(fetch_request_data_->GetReferrerPolicy());
 
-  // "2. Append `Referer`/empty byte sequence, if |HTTPRequest|'s |referrer|
-  // is none, and `Referer`/|HTTPRequest|'s referrer, serialized and utf-8
-  // encoded, otherwise, to HTTPRequest's header list.
-  //
-  // The following code also invokes "determine request's referrer" which is
-  // written in "Main fetch" operation.
-  const ReferrerPolicy referrer_policy =
-      fetch_request_data_->GetReferrerPolicy() == kReferrerPolicyDefault
-          ? execution_context_->GetReferrerPolicy()
-          : fetch_request_data_->GetReferrerPolicy();
-  const String referrer_string =
-      fetch_request_data_->ReferrerString() == Referrer::ClientReferrerString()
-          ? execution_context_->OutgoingReferrer()
-          : fetch_request_data_->ReferrerString();
-  // Note that generateReferrer generates |no-referrer| from |no-referrer|
-  // referrer string (i.e. String()).
-  // TODO(domfarolino): Can we use ResourceRequest's SetReferrerString() and
-  // SetReferrerPolicy() instead of calling SetHTTPReferrer()?
-  request.SetHTTPReferrer(SecurityPolicy::GenerateReferrer(
-      referrer_policy, fetch_request_data_->Url(), referrer_string));
   request.SetSkipServiceWorker(is_isolated_world_);
 
   if (fetch_request_data_->Keepalive()) {
diff --git a/third_party/blink/renderer/core/fetch/form_data_bytes_consumer.cc b/third_party/blink/renderer/core/fetch/form_data_bytes_consumer.cc
index 2f69f7c..80737ae 100644
--- a/third_party/blink/renderer/core/fetch/form_data_bytes_consumer.cc
+++ b/third_party/blink/renderer/core/fetch/form_data_bytes_consumer.cc
@@ -475,7 +475,7 @@
 
 FormDataBytesConsumer::FormDataBytesConsumer(const String& string)
     : impl_(new SimpleFormDataBytesConsumer(EncodedFormData::Create(
-          UTF8Encoding().Encode(string, WTF::kEntitiesForUnencodables)))) {}
+          UTF8Encoding().Encode(string, WTF::kNoUnencodables)))) {}
 
 FormDataBytesConsumer::FormDataBytesConsumer(DOMArrayBuffer* buffer)
     : FormDataBytesConsumer(buffer->Data(), buffer->ByteLength()) {}
diff --git a/third_party/blink/renderer/core/fetch/headers.cc b/third_party/blink/renderer/core/fetch/headers.cc
index fc60cc3c..a581c74 100644
--- a/third_party/blink/renderer/core/fetch/headers.cc
+++ b/third_party/blink/renderer/core/fetch/headers.cc
@@ -98,9 +98,9 @@
   if (guard_ == kRequestGuard && CORS::IsForbiddenHeaderName(name))
     return;
   // "5. Otherwise, if guard is |request-no-CORS| and |name|/|value| is not a
-  //     CORS-safelisted header, return."
+  //     no-CORS-safelisted header, return."
   if (guard_ == kRequestNoCORSGuard &&
-      !CORS::IsCORSSafelistedHeader(name, normalized_value)) {
+      !CORS::IsNoCORSSafelistedHeader(name, normalized_value)) {
     return;
   }
   // "6. Otherwise, if guard is |response| and |name| is a forbidden response
@@ -130,9 +130,9 @@
   if (guard_ == kRequestGuard && CORS::IsForbiddenHeaderName(name))
     return;
   // "4. Otherwise, if guard is |request-no-CORS| and |name|/`invalid` is not
-  //     a CORS-safelisted header, return."
+  //     a no-CORS-safelisted header, return."
   if (guard_ == kRequestNoCORSGuard &&
-      !CORS::IsCORSSafelistedHeader(name, "invalid")) {
+      !CORS::IsNoCORSSafelistedHeader(name, "invalid")) {
     return;
   }
   // "5. Otherwise, if guard is |response| and |name| is a forbidden response
@@ -198,9 +198,9 @@
   if (guard_ == kRequestGuard && CORS::IsForbiddenHeaderName(name))
     return;
   // "5. Otherwise, if guard is |request-no-CORS| and |name|/|value| is not a
-  //     CORS-safelisted header, return."
+  //     no-CORS-safelisted header, return."
   if (guard_ == kRequestNoCORSGuard &&
-      !CORS::IsCORSSafelistedHeader(name, normalized_value)) {
+      !CORS::IsNoCORSSafelistedHeader(name, normalized_value)) {
     return;
   }
   // "6. Otherwise, if guard is |response| and |name| is a forbidden response
diff --git a/third_party/blink/renderer/core/html/custom/custom_element.cc b/third_party/blink/renderer/core/html/custom/custom_element.cc
index 2675165..a089d6b 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element.cc
+++ b/third_party/blink/renderer/core/html/custom/custom_element.cc
@@ -230,7 +230,8 @@
                                                 new_value);
 }
 
-void CustomElement::TryToUpgrade(Element* element) {
+void CustomElement::TryToUpgrade(Element* element,
+                                 bool upgrade_invisible_elements) {
   // Try to upgrade an element
   // https://html.spec.whatwg.org/multipage/scripting.html#concept-try-upgrade
 
@@ -244,7 +245,7 @@
           registry->DefinitionFor(CustomElementDescriptor(
               is_value.IsNull() ? element->localName() : is_value,
               element->localName())))
-    definition->EnqueueUpgradeReaction(element);
+    definition->EnqueueUpgradeReaction(element, upgrade_invisible_elements);
   else
     registry->AddCandidate(element);
 }
diff --git a/third_party/blink/renderer/core/html/custom/custom_element.h b/third_party/blink/renderer/core/html/custom/custom_element.h
index 9dc8887..4d21f54 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element.h
+++ b/third_party/blink/renderer/core/html/custom/custom_element.h
@@ -94,7 +94,7 @@
                                               const AtomicString& old_value,
                                               const AtomicString& new_value);
 
-  static void TryToUpgrade(Element*);
+  static void TryToUpgrade(Element*, bool upgrade_invisible_elements = false);
 
  private:
   // Some existing specs have element names with hyphens in them,
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_definition.cc b/third_party/blink/renderer/core/html/custom/custom_element_definition.cc
index c927986..1dc7f5275 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_definition.cc
+++ b/third_party/blink/renderer/core/html/custom/custom_element_definition.cc
@@ -223,8 +223,11 @@
   return has_style_attribute_changed_callback_;
 }
 
-void CustomElementDefinition::EnqueueUpgradeReaction(Element* element) {
-  CustomElement::Enqueue(element, new CustomElementUpgradeReaction(this));
+void CustomElementDefinition::EnqueueUpgradeReaction(
+    Element* element,
+    bool upgrade_invisible_elements) {
+  CustomElement::Enqueue(element, new CustomElementUpgradeReaction(
+                                      this, upgrade_invisible_elements));
 }
 
 void CustomElementDefinition::EnqueueConnectedCallback(Element* element) {
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_definition.h b/third_party/blink/renderer/core/html/custom/custom_element_definition.h
index 009e8c08..1c47dec2 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_definition.h
+++ b/third_party/blink/renderer/core/html/custom/custom_element_definition.h
@@ -79,7 +79,8 @@
                                            const AtomicString& old_value,
                                            const AtomicString& new_value) = 0;
 
-  void EnqueueUpgradeReaction(Element*);
+  void EnqueueUpgradeReaction(Element*,
+                              bool upgrade_invisible_elements = false);
   void EnqueueConnectedCallback(Element*);
   void EnqueueDisconnectedCallback(Element*);
   void EnqueueAdoptedCallback(Element*,
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_registry.cc b/third_party/blink/renderer/core/html/custom/custom_element_registry.cc
index e1f7583d..b6e28d1 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_registry.cc
+++ b/third_party/blink/renderer/core/html/custom/custom_element_registry.cc
@@ -371,7 +371,8 @@
 
   // 2. For each candidate of candidates, try to upgrade candidate.
   for (auto& candidate : candidates) {
-    CustomElement::TryToUpgrade(candidate);
+    CustomElement::TryToUpgrade(candidate,
+                                true /* upgrade_invisible_elements */);
   }
 }
 
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_upgrade_reaction.cc b/third_party/blink/renderer/core/html/custom/custom_element_upgrade_reaction.cc
index 7a91660..8c49a3e6 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_upgrade_reaction.cc
+++ b/third_party/blink/renderer/core/html/custom/custom_element_upgrade_reaction.cc
@@ -5,19 +5,28 @@
 #include "third_party/blink/renderer/core/html/custom/custom_element_upgrade_reaction.h"
 
 #include "third_party/blink/renderer/core/html/custom/custom_element_definition.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
 namespace blink {
 
 CustomElementUpgradeReaction::CustomElementUpgradeReaction(
-    CustomElementDefinition* definition)
-    : CustomElementReaction(definition) {}
+    CustomElementDefinition* definition,
+    bool upgrade_invisible_elements)
+    : CustomElementReaction(definition),
+      upgrade_invisible_elements_(upgrade_invisible_elements) {}
 
 void CustomElementUpgradeReaction::Invoke(Element* element) {
   // Don't call upgrade() if it's already upgraded. Multiple upgrade reactions
   // could be enqueued because the state changes in step 10 of upgrades.
   // https://html.spec.whatwg.org/multipage/scripting.html#upgrades
-  if (element->GetCustomElementState() == CustomElementState::kUndefined)
-    definition_->Upgrade(element);
+  if (element->GetCustomElementState() == CustomElementState::kUndefined) {
+    // Don't upgrade elements inside an invisible-static tree, unless it was
+    // triggered by CustomElementRegistry::upgrade.
+    if (!RuntimeEnabledFeatures::InvisibleDOMEnabled() ||
+        !element->IsInsideInvisibleStaticSubtree() ||
+        upgrade_invisible_elements_)
+      definition_->Upgrade(element);
+  }
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_upgrade_reaction.h b/third_party/blink/renderer/core/html/custom/custom_element_upgrade_reaction.h
index 4e3ee67a..7cb4ad0 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_upgrade_reaction.h
+++ b/third_party/blink/renderer/core/html/custom/custom_element_upgrade_reaction.h
@@ -17,11 +17,14 @@
 class CORE_EXPORT CustomElementUpgradeReaction final
     : public CustomElementReaction {
  public:
-  CustomElementUpgradeReaction(CustomElementDefinition*);
+  CustomElementUpgradeReaction(CustomElementDefinition*,
+                               bool upgrade_invisible_elements);
 
  private:
   void Invoke(Element*) override;
 
+  bool upgrade_invisible_elements_;
+
   DISALLOW_COPY_AND_ASSIGN(CustomElementUpgradeReaction);
 };
 
diff --git a/third_party/blink/renderer/core/html/html_content_element_test.cc b/third_party/blink/renderer/core/html/html_content_element_test.cc
index 7b36534d..0fbc4ee2 100644
--- a/third_party/blink/renderer/core/html/html_content_element_test.cc
+++ b/third_party/blink/renderer/core/html/html_content_element_test.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/html/html_content_element.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
@@ -38,7 +39,7 @@
 
   GetDocument().UpdateDistributionForLegacyDistributedNodes();
   GetDocument().Lifecycle().AdvanceTo(DocumentLifecycle::kInStyleRecalc);
-  GetDocument().documentElement()->RecalcStyle(kNoChange);
+  GetDocument().GetStyleEngine().RecalcStyle(kNoChange);
 
   EXPECT_TRUE(fallback->GetNonAttachedStyle());
 }
diff --git a/third_party/blink/renderer/core/html/html_object_element_test.cc b/third_party/blink/renderer/core/html/html_object_element_test.cc
index 04582be..4e10fe86 100644
--- a/third_party/blink/renderer/core/html/html_object_element_test.cc
+++ b/third_party/blink/renderer/core/html/html_object_element_test.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/html/html_object_element.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
@@ -38,7 +39,7 @@
 
   object->RenderFallbackContent();
   GetDocument().Lifecycle().AdvanceTo(DocumentLifecycle::kInStyleRecalc);
-  GetDocument().documentElement()->RecalcStyle(kForce);
+  GetDocument().GetStyleEngine().RecalcStyle(kForce);
 
   EXPECT_TRUE(IsHTMLSlotElement(slot));
   EXPECT_TRUE(object->UseFallbackContent());
diff --git a/third_party/blink/renderer/core/html/html_slot_element_test.cc b/third_party/blink/renderer/core/html/html_slot_element_test.cc
index 0dd9204..9d4ac83 100644
--- a/third_party/blink/renderer/core/html/html_slot_element_test.cc
+++ b/third_party/blink/renderer/core/html/html_slot_element_test.cc
@@ -7,6 +7,7 @@
 #include <array>
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
 
@@ -161,7 +162,7 @@
   shadow_span.setAttribute(HTMLNames::styleAttr, "display:block");
 
   GetDocument().Lifecycle().AdvanceTo(DocumentLifecycle::kInStyleRecalc);
-  GetDocument().documentElement()->RecalcStyle(kNoChange);
+  GetDocument().GetStyleEngine().RecalcStyle(kNoChange);
 
   EXPECT_TRUE(shadow_span.GetNonAttachedStyle());
   EXPECT_TRUE(span.GetNonAttachedStyle());
diff --git a/third_party/blink/renderer/core/html/shadow/progress_shadow_element_test.cc b/third_party/blink/renderer/core/html/shadow/progress_shadow_element_test.cc
index 8f23c8d..213362b 100644
--- a/third_party/blink/renderer/core/html/shadow/progress_shadow_element_test.cc
+++ b/third_party/blink/renderer/core/html/shadow/progress_shadow_element_test.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/html/shadow/progress_shadow_element.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
@@ -40,7 +41,7 @@
 
   progress->LazyReattachIfAttached();
   GetDocument().Lifecycle().AdvanceTo(DocumentLifecycle::kInStyleRecalc);
-  GetDocument().documentElement()->RecalcStyle(kForce);
+  GetDocument().GetStyleEngine().RecalcStyle(kForce);
   EXPECT_TRUE(shadow_element->GetNonAttachedStyle());
 
   scoped_refptr<ComputedStyle> style = shadow_element->StyleForLayoutObject();
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.cc
index 6267cd8..211bc57 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.cc
@@ -108,7 +108,7 @@
       continue;
     for (size_t k = 0; k < listeners->size(); ++k) {
       EventListener* event_listener = listeners->at(k).Callback();
-      if (event_listener->IsNativeBased())
+      if (event_listener->GetType() != EventListener::kJSEventListenerType)
         continue;
       // TODO(yukiy): Use a child class of blink::EventListener that is for v8
       // event listeners here if it is implemented in redesigning
@@ -118,8 +118,8 @@
       // Optionally hide listeners from other contexts.
       if (!report_for_all_contexts && context != isolate->GetCurrentContext())
         continue;
-      // GetListenerObjectForInspector() may cause JS in the event attribute to
-      // get compiled, potentially unsuccessfully.  In that case, the function
+      // getListenerObject() may cause JS in the event attribute to get
+      // compiled, potentially unsuccessfully.  In that case, the function
       // returns the empty handle without an exception.
       v8::Local<v8::Object> handler =
           event_listener->GetListenerObjectForInspector(execution_context);
diff --git a/third_party/blink/renderer/core/loader/threadable_loader.cc b/third_party/blink/renderer/core/loader/threadable_loader.cc
index 15acdfc..040bd2e 100644
--- a/third_party/blink/renderer/core/loader/threadable_loader.cc
+++ b/third_party/blink/renderer/core/loader/threadable_loader.cc
@@ -79,20 +79,8 @@
 // Fetch API Spec: https://fetch.spec.whatwg.org/#cors-preflight-fetch-0
 AtomicString CreateAccessControlRequestHeadersHeader(
     const HTTPHeaderMap& headers) {
-  Vector<String> filtered_headers;
-  for (const auto& header : headers) {
-    // Exclude CORS-safelisted headers.
-    if (CORS::IsCORSSafelistedHeader(header.key, header.value))
-      continue;
-    // Calling a deprecated function, but eventually this function,
-    // |CreateAccessControlRequestHeadersHeader| will be removed.
-    // When the request is from a Worker, referrer header was added by
-    // WorkerThreadableLoader. But it should not be added to
-    // Access-Control-Request-Headers header.
-    if (DeprecatedEqualIgnoringCase(header.key, "referer"))
-      continue;
-    filtered_headers.push_back(header.key.DeprecatedLower());
-  }
+  Vector<String> filtered_headers = CORS::CORSUnsafeRequestHeaderNames(headers);
+
   if (!filtered_headers.size())
     return g_null_atom;
 
@@ -176,9 +164,7 @@
   preflight_request->SetFetchCredentialsMode(
       network::mojom::FetchCredentialsMode::kOmit);
   preflight_request->SetSkipServiceWorker(true);
-  // TODO(domfarolino): Use ReferrerString() once https://crbug.com/850813 is
-  // closed and we stop storing the referrer string as a `Referer` header.
-  preflight_request->SetReferrerString(request.HttpReferrer());
+  preflight_request->SetReferrerString(request.ReferrerString());
   preflight_request->SetReferrerPolicy(request.GetReferrerPolicy());
 
   if (request.IsExternalRequest()) {
diff --git a/third_party/blink/renderer/core/paint/box_model_object_painter.cc b/third_party/blink/renderer/core/paint/box_model_object_painter.cc
index bd4552d..1e10f88 100644
--- a/third_party/blink/renderer/core/paint/box_model_object_painter.cc
+++ b/third_party/blink/renderer/core/paint/box_model_object_painter.cc
@@ -4,10 +4,12 @@
 
 #include "third_party/blink/renderer/core/paint/box_model_object_painter.h"
 
-#include "third_party/blink/renderer/core/layout/layout_block.h"
+#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
 #include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
+#include "third_party/blink/renderer/core/layout/layout_inline.h"
 #include "third_party/blink/renderer/core/layout/line/root_inline_box.h"
 #include "third_party/blink/renderer/core/paint/background_image_geometry.h"
+#include "third_party/blink/renderer/core/paint/line_box_list_painter.h"
 #include "third_party/blink/renderer/core/paint/object_painter.h"
 #include "third_party/blink/renderer/core/paint/paint_info.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
@@ -83,9 +85,14 @@
     flow_box_->Paint(paint_info, paint_offset - local_offset, root.LineTop(),
                      root.LineBottom());
   } else {
-    // We should go through the above path for LayoutInlines.
-    DCHECK(box_model_.IsLayoutBlock());
-    ToLayoutBlock(box_model_).PaintObject(paint_info, paint_offset);
+    const LineBoxList* line_boxes = nullptr;
+    if (box_model_.IsLayoutBlockFlow())
+      line_boxes = &ToLayoutBlockFlow(box_model_).LineBoxes();
+    else if (box_model_.IsLayoutInline())
+      line_boxes = ToLayoutInline(box_model_).LineBoxes();
+    if (!line_boxes)
+      return;
+    LineBoxListPainter(*line_boxes).Paint(box_model_, paint_info, paint_offset);
   }
 }
 
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
index 2722d868..43f1acd 100644
--- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
+++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
@@ -1482,6 +1482,11 @@
   LayoutPoint host_layer_position;
 
   if (NeedsToReparentOverflowControls()) {
+    // This should never be true, but for some reason it is.
+    // See https://crbug.com/880930.
+    if (!compositing_stacking_context)
+      return;
+
     CompositedLayerMapping* stacking_clm =
         compositing_stacking_context->GetCompositedLayerMapping();
     DCHECK(stacking_clm);
diff --git a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
index d6b476c..97610c21 100644
--- a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
+++ b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
@@ -398,6 +398,16 @@
   return GetVisualViewport().ScrollLayer();
 }
 
+#if DCHECK_IS_ON()
+static void AssertWholeTreeNotComposited(const PaintLayer& paint_layer) {
+  DCHECK(paint_layer.GetCompositingState() == kNotComposited);
+  for (PaintLayer* child = paint_layer.FirstChild(); child;
+       child = child->NextSibling()) {
+    AssertWholeTreeNotComposited(*child);
+  }
+}
+#endif
+
 void PaintLayerCompositor::UpdateIfNeeded(
     DocumentLifecycle::LifecycleState target_state,
     CompositingReasonsStats& compositing_reasons_stats) {
@@ -485,6 +495,12 @@
                          ->Parent();
   }
 
+#if DCHECK_IS_ON()
+  if (update_root->GetCompositingState() != kPaintsIntoOwnBacking) {
+    AssertWholeTreeNotComposited(*update_root);
+  }
+#endif
+
   GraphicsLayerUpdater updater;
   updater.Update(*update_root, layers_needing_paint_invalidation);
 
diff --git a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
index f17d4cd1d..10fc9e5 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
@@ -762,8 +762,8 @@
                                              bool object_has_multiple_boxes) {
   PaintInfo paint_info(context, mask_rect, PaintPhase::kTextClip,
                        kGlobalPaintNormalPhase, 0);
-  LayoutSize local_offset = box_fragment_.Offset().ToLayoutSize();
   if (object_has_multiple_boxes) {
+    LayoutSize local_offset = box_fragment_.Offset().ToLayoutSize();
     NGInlineBoxFragmentPainter inline_box_painter(box_fragment_);
     if (box_fragment_.Style().BoxDecorationBreak() ==
         EBoxDecorationBreak::kSlice) {
@@ -778,7 +778,7 @@
     }
     inline_box_painter.Paint(paint_info, paint_offset - local_offset);
   } else {
-    PaintObject(paint_info, paint_offset - local_offset);
+    PaintObject(paint_info, paint_offset);
   }
 }
 
diff --git a/third_party/blink/renderer/core/svg/svg_animated_integer.h b/third_party/blink/renderer/core/svg/svg_animated_integer.h
index 4c9ad639..aed647f 100644
--- a/third_party/blink/renderer/core/svg/svg_animated_integer.h
+++ b/third_party/blink/renderer/core/svg/svg_animated_integer.h
@@ -49,6 +49,13 @@
  public:
   static SVGAnimatedInteger* Create(SVGElement* context_element,
                                     const QualifiedName& attribute_name,
+                                    int initial) {
+    SVGInteger* initial_value = SVGInteger::Create(initial);
+    return new SVGAnimatedInteger(context_element, attribute_name,
+                                  initial_value);
+  }
+  static SVGAnimatedInteger* Create(SVGElement* context_element,
+                                    const QualifiedName& attribute_name,
                                     SVGInteger* initial_value) {
     return new SVGAnimatedInteger(context_element, attribute_name,
                                   initial_value);
@@ -69,7 +76,9 @@
                      SVGInteger* initial_value)
       : SVGAnimatedProperty<SVGInteger>(context_element,
                                         attribute_name,
-                                        initial_value),
+                                        initial_value,
+                                        CSSPropertyInvalid,
+                                        initial_value->Value()),
         parent_integer_optional_integer_(nullptr) {}
 
   Member<SVGAnimatedIntegerOptionalInteger> parent_integer_optional_integer_;
diff --git a/third_party/blink/renderer/core/svg/svg_animated_integer_optional_integer.cc b/third_party/blink/renderer/core/svg/svg_animated_integer_optional_integer.cc
index 9d4b1ce..a3460f0 100644
--- a/third_party/blink/renderer/core/svg/svg_animated_integer_optional_integer.cc
+++ b/third_party/blink/renderer/core/svg/svg_animated_integer_optional_integer.cc
@@ -35,14 +35,14 @@
 SVGAnimatedIntegerOptionalInteger::SVGAnimatedIntegerOptionalInteger(
     SVGElement* context_element,
     const QualifiedName& attribute_name,
-    float initial_first_value,
-    float initial_second_value)
+    int initial_value)
     : SVGAnimatedPropertyCommon<SVGIntegerOptionalInteger>(
           context_element,
           attribute_name,
-          SVGIntegerOptionalInteger::Create(
-              SVGInteger::Create(initial_first_value),
-              SVGInteger::Create(initial_second_value))),
+          SVGIntegerOptionalInteger::Create(SVGInteger::Create(initial_value),
+                                            SVGInteger::Create(initial_value)),
+          CSSPropertyInvalid,
+          initial_value),
       first_integer_(SVGAnimatedInteger::Create(context_element,
                                                 attribute_name,
                                                 BaseValue()->FirstInteger())),
diff --git a/third_party/blink/renderer/core/svg/svg_animated_integer_optional_integer.h b/third_party/blink/renderer/core/svg/svg_animated_integer_optional_integer.h
index 09e6942..a7a095b 100644
--- a/third_party/blink/renderer/core/svg/svg_animated_integer_optional_integer.h
+++ b/third_party/blink/renderer/core/svg/svg_animated_integer_optional_integer.h
@@ -52,11 +52,9 @@
   static SVGAnimatedIntegerOptionalInteger* Create(
       SVGElement* context_element,
       const QualifiedName& attribute_name,
-      float initial_first_value = 0,
-      float initial_second_value = 0) {
-    return new SVGAnimatedIntegerOptionalInteger(
-        context_element, attribute_name, initial_first_value,
-        initial_second_value);
+      int initial_value) {
+    return new SVGAnimatedIntegerOptionalInteger(context_element,
+                                                 attribute_name, initial_value);
   }
 
   void SetAnimatedValue(SVGPropertyBase*) override;
@@ -71,8 +69,7 @@
  protected:
   SVGAnimatedIntegerOptionalInteger(SVGElement* context_element,
                                     const QualifiedName& attribute_name,
-                                    float initial_first_value,
-                                    float initial_second_value);
+                                    int initial_value);
 
   Member<SVGAnimatedInteger> first_integer_;
   Member<SVGAnimatedInteger> second_integer_;
diff --git a/third_party/blink/renderer/core/svg/svg_fe_convolve_matrix_element.cc b/third_party/blink/renderer/core/svg/svg_fe_convolve_matrix_element.cc
index f9634c7..78cda0df 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_convolve_matrix_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_convolve_matrix_element.cc
@@ -50,8 +50,7 @@
   SVGAnimatedOrder(SVGElement* context_element)
       : SVGAnimatedIntegerOptionalInteger(context_element,
                                           SVGNames::orderAttr,
-                                          0,
-                                          0) {}
+                                          3) {}
 
   static SVGParsingError CheckValue(SVGParsingError parse_status, int value) {
     if (parse_status != SVGParseStatus::kNoError)
@@ -94,12 +93,8 @@
       order_(SVGAnimatedOrder::Create(this)),
       preserve_alpha_(
           SVGAnimatedBoolean::Create(this, SVGNames::preserveAlphaAttr)),
-      target_x_(SVGAnimatedInteger::Create(this,
-                                           SVGNames::targetXAttr,
-                                           SVGInteger::Create())),
-      target_y_(SVGAnimatedInteger::Create(this,
-                                           SVGNames::targetYAttr,
-                                           SVGInteger::Create())) {
+      target_x_(SVGAnimatedInteger::Create(this, SVGNames::targetXAttr, 0)),
+      target_y_(SVGAnimatedInteger::Create(this, SVGNames::targetYAttr, 0)) {
   AddToPropertyMap(preserve_alpha_);
   AddToPropertyMap(divisor_);
   AddToPropertyMap(bias_);
diff --git a/third_party/blink/renderer/core/svg/svg_fe_turbulence_element.cc b/third_party/blink/renderer/core/svg/svg_fe_turbulence_element.cc
index 2a5626c2..6ace7ca2 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_turbulence_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_turbulence_element.cc
@@ -61,9 +61,8 @@
           this,
           SVGNames::typeAttr,
           FETURBULENCE_TYPE_TURBULENCE)),
-      num_octaves_(SVGAnimatedInteger::Create(this,
-                                              SVGNames::numOctavesAttr,
-                                              SVGInteger::Create(1))) {
+      num_octaves_(
+          SVGAnimatedInteger::Create(this, SVGNames::numOctavesAttr, 1)) {
   AddToPropertyMap(base_frequency_);
   AddToPropertyMap(seed_);
   AddToPropertyMap(stitch_tiles_);
diff --git a/third_party/blink/renderer/core/svg/svg_integer.h b/third_party/blink/renderer/core/svg/svg_integer.h
index b08fc11..2abffc47 100644
--- a/third_party/blink/renderer/core/svg/svg_integer.h
+++ b/third_party/blink/renderer/core/svg/svg_integer.h
@@ -64,6 +64,9 @@
 
   static AnimatedPropertyType ClassType() { return kAnimatedInteger; }
 
+  void SetInitial(unsigned value) { SetValue(static_cast<int>(value)); }
+  static constexpr int kInitialValueBits = 2;
+
  protected:
   explicit SVGInteger(int);
 
diff --git a/third_party/blink/renderer/core/svg/svg_integer_optional_integer.cc b/third_party/blink/renderer/core/svg/svg_integer_optional_integer.cc
index 4ea35ac..02481d3 100644
--- a/third_party/blink/renderer/core/svg/svg_integer_optional_integer.cc
+++ b/third_party/blink/renderer/core/svg/svg_integer_optional_integer.cc
@@ -81,6 +81,12 @@
   return parse_status;
 }
 
+void SVGIntegerOptionalInteger::SetInitial(unsigned value) {
+  // Propagate the value to the split representation.
+  first_integer_->SetInitial(value);
+  second_integer_->SetInitial(value);
+}
+
 void SVGIntegerOptionalInteger::Add(SVGPropertyBase* other, SVGElement*) {
   SVGIntegerOptionalInteger* other_integer_optional_integer =
       ToSVGIntegerOptionalInteger(other);
diff --git a/third_party/blink/renderer/core/svg/svg_integer_optional_integer.h b/third_party/blink/renderer/core/svg/svg_integer_optional_integer.h
index fbdaf92..2caaf58 100644
--- a/third_party/blink/renderer/core/svg/svg_integer_optional_integer.h
+++ b/third_party/blink/renderer/core/svg/svg_integer_optional_integer.h
@@ -53,6 +53,8 @@
 
   String ValueAsString() const override;
   SVGParsingError SetValueAsString(const String&);
+  void SetInitial(unsigned);
+  static constexpr int kInitialValueBits = SVGInteger::kInitialValueBits;
 
   void Add(SVGPropertyBase*, SVGElement*) override;
   void CalculateAnimatedValue(SVGAnimationElement*,
diff --git a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
index c35bbd7e..d7ce75e 100644
--- a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
+++ b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
@@ -836,7 +836,7 @@
     String body = CreateMarkup(document);
 
     http_body = EncodedFormData::Create(
-        UTF8Encoding().Encode(body, WTF::kEntitiesForUnencodables));
+        UTF8Encoding().Encode(body, WTF::kNoUnencodables));
   }
 
   CreateRequest(std::move(http_body), exception_state);
@@ -852,7 +852,7 @@
 
   if (!body.IsNull() && AreMethodAndURLValidForSend()) {
     http_body = EncodedFormData::Create(
-        UTF8Encoding().Encode(body, WTF::kEntitiesForUnencodables));
+        UTF8Encoding().Encode(body, WTF::kNoUnencodables));
     UpdateContentTypeAndCharset("text/plain;charset=UTF-8", "UTF-8");
   }
 
diff --git a/third_party/blink/renderer/devtools/front_end/quick_open/module.json b/third_party/blink/renderer/devtools/front_end/quick_open/module.json
index 7fa61716..ccdeab8e7 100644
--- a/third_party/blink/renderer/devtools/front_end/quick_open/module.json
+++ b/third_party/blink/renderer/devtools/front_end/quick_open/module.json
@@ -14,6 +14,7 @@
         {
             "type": "action",
             "actionId": "commandMenu.show",
+            "title": "Run command",
             "className": "QuickOpen.CommandMenu.ShowActionDelegate",
             "bindings": [
                 {
@@ -27,6 +28,11 @@
             ]
         },
         {
+            "type": "context-menu-item",
+            "location": "mainMenu/default",
+            "actionId": "commandMenu.show"
+        },
+        {
             "type": "action",
             "actionId": "quickOpen.show",
             "title": "Open file",
diff --git a/third_party/blink/renderer/modules/encoding/text_encoder.cc b/third_party/blink/renderer/modules/encoding/text_encoder.cc
index 81726d58..3f9161f 100644
--- a/third_party/blink/renderer/modules/encoding/text_encoder.cc
+++ b/third_party/blink/renderer/modules/encoding/text_encoder.cc
@@ -67,10 +67,10 @@
   // are present in the input.
   if (input.Is8Bit()) {
     result = codec_->Encode(input.Characters8(), input.length(),
-                            WTF::kEntitiesForUnencodables);
+                            WTF::kNoUnencodables);
   } else {
     result = codec_->Encode(input.Characters16(), input.length(),
-                            WTF::kEntitiesForUnencodables);
+                            WTF::kNoUnencodables);
   }
 
   const char* buffer = result.data();
diff --git a/third_party/blink/renderer/modules/encoding/text_encoder_stream.cc b/third_party/blink/renderer/modules/encoding/text_encoder_stream.cc
index 130874d..ee4599e 100644
--- a/third_party/blink/renderer/modules/encoding/text_encoder_stream.cc
+++ b/third_party/blink/renderer/modules/encoding/text_encoder_stream.cc
@@ -60,14 +60,8 @@
         // check is needed.
         prefix = ReplacementCharacterInUtf8();
       }
-      // Note that the third argument here is ignored since the encoding is
-      // UTF-8, which will use U+FFFD-replacement rather than ASCII fallback
-      // substitution when unencodable sequences (for instance, unpaired UTF-16
-      // surrogates) are present in the input.
-      // TODO(ricea): Add WTF::kNoUnencodables enum value to make this
-      // behaviour explicit for UTF-N encodings.
       result = encoder_->Encode(input.Characters8(), input.length(),
-                                WTF::kEntitiesForUnencodables);
+                                WTF::kNoUnencodables);
     } else {
       bool have_output =
           Encode16BitString(input, high_surrogate, &prefix, &result);
@@ -134,7 +128,7 @@
         // Third argument is ignored, as above.
         *prefix =
             encoder_->Encode(astral_character, base::size(astral_character),
-                             WTF::kEntitiesForUnencodables);
+                             WTF::kNoUnencodables);
         ++begin;
         if (begin == end)
           return true;
diff --git a/third_party/blink/renderer/modules/push_messaging/push_message_data.cc b/third_party/blink/renderer/modules/push_messaging/push_message_data.cc
index e5f0b9a..6282e7a 100644
--- a/third_party/blink/renderer/modules/push_messaging/push_message_data.cc
+++ b/third_party/blink/renderer/modules/push_messaging/push_message_data.cc
@@ -43,7 +43,7 @@
 
   if (message_data.IsUSVString()) {
     CString encoded_string = UTF8Encoding().Encode(
-        message_data.GetAsUSVString(), WTF::kEntitiesForUnencodables);
+        message_data.GetAsUSVString(), WTF::kNoUnencodables);
     return new PushMessageData(encoded_string.data(), encoded_string.length());
   }
 
diff --git a/third_party/blink/renderer/modules/service_worker/navigator_service_worker.cc b/third_party/blink/renderer/modules/service_worker/navigator_service_worker.cc
index 157a288..4c438f47 100644
--- a/third_party/blink/renderer/modules/service_worker/navigator_service_worker.cc
+++ b/third_party/blink/renderer/modules/service_worker/navigator_service_worker.cc
@@ -22,6 +22,9 @@
   if (!frame)
     return nullptr;
 
+  // TODO(kouhei): Remove below after M72, since the check is now done in
+  // RenderFrameImpl::CreateServiceWorkerProvider instead.
+  //
   // Bail-out if we are about to be navigated away.
   // We check that DocumentLoader is attached since:
   // - This serves as the signal since the DocumentLoader is detached in
diff --git a/third_party/blink/renderer/platform/bindings/callback_interface_base.h b/third_party/blink/renderer/platform/bindings/callback_interface_base.h
index dd83a4e5..4f86ee39 100644
--- a/third_party/blink/renderer/platform/bindings/callback_interface_base.h
+++ b/third_party/blink/renderer/platform/bindings/callback_interface_base.h
@@ -38,21 +38,9 @@
 
   virtual void Trace(blink::Visitor*);
 
-  // Check the identity of |callback_object_|. There can be multiple
-  // CallbackInterfaceBase objects that have the same |callback_object_| but
-  // have different |incumbent_script_state_|s.
-  bool HasTheSameCallbackObject(const CallbackInterfaceBase& other) const {
-    return callback_object_ == other.callback_object_;
-  }
-
-  v8::Local<v8::Object> CallbackObject() {
-    return callback_object_.NewLocal(GetIsolate());
-  }
-
   v8::Isolate* GetIsolate() {
     return callback_relevant_script_state_->GetIsolate();
   }
-
   ScriptState* CallbackRelevantScriptState() {
     return callback_relevant_script_state_;
   }
@@ -67,6 +55,9 @@
   CallbackInterfaceBase(v8::Local<v8::Object> callback_object,
                         SingleOperationOrNot);
 
+  v8::Local<v8::Object> CallbackObject() {
+    return callback_object_.NewLocal(GetIsolate());
+  }
   // Returns true iff the callback interface is a single operation callback
   // interface and the callback interface type value is callable.
   bool IsCallbackObjectCallable() const { return is_callback_object_callable_; }
diff --git a/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h b/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h
index 78f0ba2..f8c9446 100644
--- a/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h
+++ b/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h
@@ -28,10 +28,6 @@
 
   ~TraceWrapperV8Reference() { Clear(); }
 
-  bool operator==(const TraceWrapperV8Reference& other) const {
-    return handle_ == other.handle_;
-  }
-
   void Set(v8::Isolate* isolate, v8::Local<T> handle) {
     InternalSet(isolate, handle);
     handle_.SetWeak();
diff --git a/third_party/blink/renderer/platform/blob/blob_data.cc b/third_party/blink/renderer/platform/blob/blob_data.cc
index 678cee04..57889d8d 100644
--- a/third_party/blink/renderer/platform/blob/blob_data.cc
+++ b/third_party/blink/renderer/platform/blob/blob_data.cc
@@ -215,8 +215,7 @@
                           bool do_normalize_line_endings_to_native) {
   DCHECK_EQ(file_composition_, FileCompositionStatus::NO_UNKNOWN_SIZE_FILES)
       << "Blobs with a unknown-size file cannot have other items.";
-  CString utf8_text =
-      UTF8Encoding().Encode(text, WTF::kEntitiesForUnencodables);
+  CString utf8_text = UTF8Encoding().Encode(text, WTF::kNoUnencodables);
 
   if (do_normalize_line_endings_to_native) {
     if (utf8_text.length() >
diff --git a/third_party/blink/renderer/platform/fonts/font.cc b/third_party/blink/renderer/platform/fonts/font.cc
index 2df09085..1a86e0c3 100644
--- a/third_party/blink/renderer/platform/fonts/font.cc
+++ b/third_party/blink/renderer/platform/fonts/font.cc
@@ -364,7 +364,7 @@
 
 FloatRect Font::SelectionRectForText(const TextRun& run,
                                      const FloatPoint& point,
-                                     int height,
+                                     float height,
                                      int from,
                                      int to) const {
   to = (to == -1 ? run.length() : to);
diff --git a/third_party/blink/renderer/platform/fonts/font.h b/third_party/blink/renderer/platform/fonts/font.h
index a661792..559bd97f 100644
--- a/third_party/blink/renderer/platform/fonts/font.h
+++ b/third_party/blink/renderer/platform/fonts/font.h
@@ -147,7 +147,7 @@
                         BreakGlyphsOption) const;
   FloatRect SelectionRectForText(const TextRun&,
                                  const FloatPoint&,
-                                 int h,
+                                 float height,
                                  int from = 0,
                                  int to = -1) const;
   FloatRect BoundingBox(const TextRun&, int from = 0, int to = -1) const;
diff --git a/third_party/blink/renderer/platform/loader/cors/cors.cc b/third_party/blink/renderer/platform/loader/cors/cors.cc
index 297a18e..e514903 100644
--- a/third_party/blink/renderer/platform/loader/cors/cors.cc
+++ b/third_party/blink/renderer/platform/loader/cors/cors.cc
@@ -232,6 +232,26 @@
       std::string(utf8_value.data(), utf8_value.length()));
 }
 
+bool IsNoCORSSafelistedHeader(const String& name, const String& value) {
+  DCHECK(!name.IsNull());
+  DCHECK(!value.IsNull());
+  return network::cors::IsNoCORSSafelistedHeader(WebString(name).Latin1(),
+                                                 WebString(value).Latin1());
+}
+
+Vector<String> CORSUnsafeRequestHeaderNames(const HTTPHeaderMap& headers) {
+  net::HttpRequestHeaders::HeaderVector in;
+  for (const auto& entry : headers) {
+    in.push_back(net::HttpRequestHeaders::HeaderKeyValuePair(
+        WebString(entry.key).Latin1(), WebString(entry.value).Latin1()));
+  }
+
+  Vector<String> header_names;
+  for (const auto& name : network::cors::CORSUnsafeRequestHeaderNames(in))
+    header_names.push_back(WebString::FromLatin1(name));
+  return header_names;
+}
+
 bool IsForbiddenHeaderName(const String& name) {
   CString utf8_name = name.Utf8();
   return network::cors::IsForbiddenHeader(
@@ -239,21 +259,20 @@
 }
 
 bool ContainsOnlyCORSSafelistedHeaders(const HTTPHeaderMap& header_map) {
-  for (const auto& header : header_map) {
-    if (!IsCORSSafelistedHeader(header.key, header.value))
-      return false;
-  }
-  return true;
+  Vector<String> header_names = CORSUnsafeRequestHeaderNames(header_map);
+  return header_names.IsEmpty();
 }
 
 bool ContainsOnlyCORSSafelistedOrForbiddenHeaders(
-    const HTTPHeaderMap& header_map) {
-  for (const auto& header : header_map) {
-    if (!IsCORSSafelistedHeader(header.key, header.value) &&
-        !IsForbiddenHeaderName(header.key))
-      return false;
+    const HTTPHeaderMap& headers) {
+  Vector<String> header_names;
+
+  net::HttpRequestHeaders::HeaderVector in;
+  for (const auto& entry : headers) {
+    in.push_back(net::HttpRequestHeaders::HeaderKeyValuePair(
+        WebString(entry.key).Latin1(), WebString(entry.value).Latin1()));
   }
-  return true;
+  return network::cors::CORSUnsafeNotForbiddenRequestHeaderNames(in).empty();
 }
 
 bool IsOkStatus(int status) {
diff --git a/third_party/blink/renderer/platform/loader/cors/cors.h b/third_party/blink/renderer/platform/loader/cors/cors.h
index 9d5eb4e..de2f73f4 100644
--- a/third_party/blink/renderer/platform/loader/cors/cors.h
+++ b/third_party/blink/renderer/platform/loader/cors/cors.h
@@ -11,6 +11,7 @@
 #include "services/network/public/mojom/fetch_api.mojom-shared.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 
@@ -89,6 +90,10 @@
 PLATFORM_EXPORT bool IsCORSSafelistedContentType(const String&);
 PLATFORM_EXPORT bool IsCORSSafelistedHeader(const String& name,
                                             const String& value);
+PLATFORM_EXPORT bool IsNoCORSSafelistedHeader(const String& name,
+                                              const String& value);
+PLATFORM_EXPORT Vector<String> CORSUnsafeRequestHeaderNames(
+    const HTTPHeaderMap& headers);
 PLATFORM_EXPORT bool IsForbiddenHeaderName(const String& name);
 PLATFORM_EXPORT bool ContainsOnlyCORSSafelistedHeaders(const HTTPHeaderMap&);
 PLATFORM_EXPORT bool ContainsOnlyCORSSafelistedOrForbiddenHeaders(
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index ad012e8..fdd0999 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -674,7 +674,7 @@
     },
     {
       name:"ManualSlotting",
-      status:"test",
+      status:"experimental",
     },
     {
       name: "MediaCapabilities",
diff --git a/third_party/blink/renderer/platform/weborigin/kurl.cc b/third_party/blink/renderer/platform/weborigin/kurl.cc
index 4afb31a3..d4e6803 100644
--- a/third_party/blink/renderer/platform/weborigin/kurl.cc
+++ b/third_party/blink/renderer/platform/weborigin/kurl.cc
@@ -632,8 +632,8 @@
 }
 
 String EncodeWithURLEscapeSequences(const String& not_encoded_string) {
-  CString utf8 = UTF8Encoding().Encode(not_encoded_string,
-                                       WTF::kURLEncodedEntitiesForUnencodables);
+  CString utf8 =
+      UTF8Encoding().Encode(not_encoded_string, WTF::kNoUnencodables);
 
   url::RawCanonOutputT<char> buffer;
   int input_length = utf8.length();
diff --git a/third_party/blink/renderer/platform/weborigin/security_origin.cc b/third_party/blink/renderer/platform/weborigin/security_origin.cc
index 7fc040b..fc86c2d 100644
--- a/third_party/blink/renderer/platform/weborigin/security_origin.cc
+++ b/third_party/blink/renderer/platform/weborigin/security_origin.cc
@@ -244,15 +244,22 @@
   return false;
 }
 
-bool SecurityOrigin::CanAccess(const SecurityOrigin* other) const {
-  if (universal_access_)
+bool SecurityOrigin::CanAccess(const SecurityOrigin* other,
+                               AccessResultDomainDetail& detail) const {
+  if (universal_access_) {
+    detail = AccessResultDomainDetail::kDomainNotRelevant;
     return true;
+  }
 
-  if (this == other)
+  if (this == other) {
+    detail = AccessResultDomainDetail::kDomainNotRelevant;
     return true;
+  }
 
-  if (IsOpaque() || other->IsOpaque())
+  if (IsOpaque() || other->IsOpaque()) {
+    detail = AccessResultDomainDetail::kDomainNotRelevant;
     return false;
+  }
 
   // document.domain handling, as per
   // https://html.spec.whatwg.org/multipage/browsers.html#dom-document-domain:
@@ -266,6 +273,7 @@
   bool can_access = false;
   if (protocol_ == other->protocol_) {
     if (!domain_was_set_in_dom_ && !other->domain_was_set_in_dom_) {
+      detail = AccessResultDomainDetail::kDomainNotSet;
       if (host_ == other->host_ && port_ == other->port_)
         can_access = true;
     } else if (domain_was_set_in_dom_ && other->domain_was_set_in_dom_) {
@@ -274,12 +282,25 @@
       // https://crbug.com/733150
       if (domain_ == other->domain_ && domain_ != "null") {
         can_access = true;
+        detail = (host_ == other->host_ && port_ == other->port_)
+                     ? AccessResultDomainDetail::kDomainMatchUnnecessary
+                     : AccessResultDomainDetail::kDomainMatchNecessary;
+      } else {
+        detail = (host_ == other->host_ && port_ == other->port_)
+                     ? AccessResultDomainDetail::kDomainMismatch
+                     : AccessResultDomainDetail::kDomainNotRelevant;
       }
+    } else {
+      detail = (host_ == other->host_ && port_ == other->port_)
+                   ? AccessResultDomainDetail::kDomainSetByOnlyOneOrigin
+                   : AccessResultDomainDetail::kDomainNotRelevant;
     }
   }
 
-  if (can_access && IsLocal())
-    can_access = PassesFileCheck(other);
+  if (can_access && IsLocal() && !PassesFileCheck(other)) {
+    detail = AccessResultDomainDetail::kDomainNotRelevant;
+    can_access = false;
+  }
 
   return can_access;
 }
diff --git a/third_party/blink/renderer/platform/weborigin/security_origin.h b/third_party/blink/renderer/platform/weborigin/security_origin.h
index 9f7ba5b..08dba0c9 100644
--- a/third_party/blink/renderer/platform/weborigin/security_origin.h
+++ b/third_party/blink/renderer/platform/weborigin/security_origin.h
@@ -55,6 +55,15 @@
   WTF_MAKE_NONCOPYABLE(SecurityOrigin);
 
  public:
+  enum class AccessResultDomainDetail {
+    kDomainNotRelevant,
+    kDomainNotSet,
+    kDomainSetByOnlyOneOrigin,
+    kDomainMatchNecessary,
+    kDomainMatchUnnecessary,
+    kDomainMismatch,
+  };
+
   static scoped_refptr<SecurityOrigin> Create(const KURL&);
   // Creates a new opaque SecurityOrigin that is guaranteed to be cross-origin
   // to all currently existing SecurityOrigins.
@@ -112,7 +121,17 @@
   // SecurityOrigin. For example, call this function before allowing
   // script from one security origin to read or write objects from
   // another SecurityOrigin.
-  bool CanAccess(const SecurityOrigin*) const;
+  bool CanAccess(const SecurityOrigin* other) const {
+    AccessResultDomainDetail unused_detail;
+    return CanAccess(other, unused_detail);
+  }
+
+  // Returns true if this SecurityOrigin can script objects in |other|, just
+  // as above, but also returns the category into which the access check fell.
+  //
+  // TODO(crbug.com/787905): Remove this variant once we have enough data to
+  // make decisions about `document.domain`.
+  bool CanAccess(const SecurityOrigin* other, AccessResultDomainDetail&) const;
 
   // Returns true if this SecurityOrigin can read content retrieved from
   // the given URL.
diff --git a/third_party/blink/renderer/platform/weborigin/security_origin_test.cc b/third_party/blink/renderer/platform/weborigin/security_origin_test.cc
index 0b34431..5069782 100644
--- a/third_party/blink/renderer/platform/weborigin/security_origin_test.cc
+++ b/third_party/blink/renderer/platform/weborigin/security_origin_test.cc
@@ -247,6 +247,79 @@
   }
 }
 
+TEST_F(SecurityOriginTest, CanAccessDetail) {
+  struct TestCase {
+    SecurityOrigin::AccessResultDomainDetail expected;
+    const char* origin1;
+    const char* domain1;
+    const char* origin2;
+    const char* domain2;
+  };
+
+  TestCase tests[] = {
+      // Actually cross-origin origins
+      {SecurityOrigin::AccessResultDomainDetail::kDomainNotSet,
+       "https://example.com", nullptr, "https://not-example.com", nullptr},
+      {SecurityOrigin::AccessResultDomainDetail::kDomainNotRelevant,
+       "https://example.com", "example.com", "https://not-example.com",
+       nullptr},
+      {SecurityOrigin::AccessResultDomainDetail::kDomainNotRelevant,
+       "https://example.com", nullptr, "https://not-example.com",
+       "not-example.com"},
+      {SecurityOrigin::AccessResultDomainDetail::kDomainNotRelevant,
+       "https://example.com", "example.com", "https://not-example.com",
+       "not-example.com"},
+
+      // Same-origin origins
+      {SecurityOrigin::AccessResultDomainDetail::kDomainNotSet,
+       "https://example.com", nullptr, "https://example.com", nullptr},
+      {SecurityOrigin::AccessResultDomainDetail::kDomainSetByOnlyOneOrigin,
+       "https://example.com", "example.com", "https://example.com", nullptr},
+      {SecurityOrigin::AccessResultDomainDetail::kDomainSetByOnlyOneOrigin,
+       "https://example.com", nullptr, "https://example.com", "example.com"},
+      {SecurityOrigin::AccessResultDomainDetail::kDomainMismatch,
+       "https://www.example.com", "www.example.com", "https://www.example.com",
+       "example.com"},
+      {SecurityOrigin::AccessResultDomainDetail::kDomainMatchUnnecessary,
+       "https://example.com", "example.com", "https://example.com",
+       "example.com"},
+
+      // Same-origin-domain origins
+      {SecurityOrigin::AccessResultDomainDetail::kDomainNotSet,
+       "https://a.example.com", nullptr, "https://b.example.com", nullptr},
+      {SecurityOrigin::AccessResultDomainDetail::kDomainNotRelevant,
+       "https://a.example.com", "example.com", "https://b.example.com",
+       nullptr},
+      {SecurityOrigin::AccessResultDomainDetail::kDomainNotRelevant,
+       "https://a.example.com", nullptr, "https://b.example.com",
+       "example.com"},
+      {SecurityOrigin::AccessResultDomainDetail::kDomainMatchNecessary,
+       "https://a.example.com", "example.com", "https://b.example.com",
+       "example.com"},
+  };
+
+  for (TestCase test : tests) {
+    SCOPED_TRACE(testing::Message()
+                 << "\nOrigin 1: `" << test.origin1 << "` ("
+                 << (test.domain1 ? test.domain1 : "") << ") \n"
+                 << "Origin 2: `" << test.origin2 << "` ("
+                 << (test.domain2 ? test.domain2 : "") << ")\n");
+    scoped_refptr<SecurityOrigin> origin1 =
+        SecurityOrigin::CreateFromString(test.origin1);
+    if (test.domain1)
+      origin1->SetDomainFromDOM(test.domain1);
+    scoped_refptr<SecurityOrigin> origin2 =
+        SecurityOrigin::CreateFromString(test.origin2);
+    if (test.domain2)
+      origin2->SetDomainFromDOM(test.domain2);
+    SecurityOrigin::AccessResultDomainDetail detail;
+    origin1->CanAccess(origin2.get(), detail);
+    EXPECT_EQ(test.expected, detail);
+    origin2->CanAccess(origin1.get(), detail);
+    EXPECT_EQ(test.expected, detail);
+  }
+}
+
 TEST_F(SecurityOriginTest, CanRequest) {
   struct TestCase {
     bool can_request;
diff --git a/third_party/blink/renderer/platform/wtf/text/text_codec.cc b/third_party/blink/renderer/platform/wtf/text/text_codec.cc
index 39ee19f0..2943851 100644
--- a/third_party/blink/renderer/platform/wtf/text/text_codec.cc
+++ b/third_party/blink/renderer/platform/wtf/text/text_codec.cc
@@ -48,6 +48,9 @@
       snprintf(replacement, sizeof(UnencodableReplacementArray), "\\%x ",
                code_point);
       return static_cast<uint32_t>(strlen(replacement));
+
+    case kNoUnencodables:
+      break;
   }
   NOTREACHED();
   replacement[0] = 0;
diff --git a/third_party/blink/renderer/platform/wtf/text/text_codec.h b/third_party/blink/renderer/platform/wtf/text/text_codec.h
index e13cad7..51c0777 100644
--- a/third_party/blink/renderer/platform/wtf/text/text_codec.h
+++ b/third_party/blink/renderer/platform/wtf/text/text_codec.h
@@ -52,6 +52,10 @@
   // Encodes the character as a CSS entity.  For example U+06DE
   // would be \06de.  See: https://www.w3.org/TR/css-syntax-3/#escaping
   kCSSEncodedEntitiesForUnencodables,
+
+  // Used when all characters can be encoded in the character set. Only
+  // applicable to UTF-N encodings.
+  kNoUnencodables,
 };
 
 typedef char UnencodableReplacementArray[32];
diff --git a/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc b/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc
index 5b93374..2406032e 100644
--- a/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc
+++ b/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc
@@ -616,6 +616,16 @@
 }
 #endif  // USING_SYSTEM_ICU
 
+static void NotReachedEntityCallback(const void* context,
+                                     UConverterFromUnicodeArgs* from_u_args,
+                                     const UChar* code_units,
+                                     int32_t length,
+                                     UChar32 code_point,
+                                     UConverterCallbackReason reason,
+                                     UErrorCode* err) {
+  NOTREACHED();
+}
+
 class TextCodecInput final {
   STACK_ALLOCATED();
 
@@ -685,6 +695,13 @@
                             0, 0, 0, &err);
 #endif
       break;
+    case kNoUnencodables:
+      DCHECK(encoding_ == UTF16BigEndianEncoding() ||
+             encoding_ == UTF16LittleEndianEncoding() ||
+             encoding_ == UTF8Encoding());
+      ucnv_setFromUCallBack(converter_icu_, NotReachedEntityCallback, nullptr,
+                            nullptr, nullptr, &err);
+      break;
   }
 
   DCHECK(U_SUCCESS(err));
diff --git a/third_party/blink/renderer/platform/wtf/text/text_codec_latin1.cc b/third_party/blink/renderer/platform/wtf/text/text_codec_latin1.cc
index 966ebbc..ebd699c 100644
--- a/third_party/blink/renderer/platform/wtf/text/text_codec_latin1.cc
+++ b/third_party/blink/renderer/platform/wtf/text/text_codec_latin1.cc
@@ -26,6 +26,7 @@
 #include "third_party/blink/renderer/platform/wtf/text/text_codec_latin1.h"
 
 #include <memory>
+#include "third_party/blink/renderer/platform/wtf/assertions.h"
 #include "third_party/blink/renderer/platform/wtf/text/cstring.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_buffer.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_codec_ascii_fast_path.h"
@@ -205,6 +206,7 @@
 static CString EncodeComplexWindowsLatin1(const CharType* characters,
                                           size_t length,
                                           UnencodableHandling handling) {
+  DCHECK_NE(handling, kNoUnencodables);
   size_t target_length = length;
   Vector<char> result(target_length);
   char* bytes = result.data();
diff --git a/third_party/blink/renderer/platform/wtf/text/text_codec_user_defined.cc b/third_party/blink/renderer/platform/wtf/text/text_codec_user_defined.cc
index 635815a8..20a1bb9 100644
--- a/third_party/blink/renderer/platform/wtf/text/text_codec_user_defined.cc
+++ b/third_party/blink/renderer/platform/wtf/text/text_codec_user_defined.cc
@@ -26,8 +26,7 @@
 #include "third_party/blink/renderer/platform/wtf/text/text_codec_user_defined.h"
 
 #include <memory>
-
-#include <memory>
+#include "third_party/blink/renderer/platform/wtf/assertions.h"
 #include "third_party/blink/renderer/platform/wtf/text/cstring.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_buffer.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
@@ -70,6 +69,7 @@
 static CString EncodeComplexUserDefined(const CharType* characters,
                                         size_t length,
                                         UnencodableHandling handling) {
+  DCHECK_NE(handling, kNoUnencodables);
   size_t target_length = length;
   Vector<char> result(target_length);
   char* bytes = result.data();
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/android.py b/third_party/blink/tools/blinkpy/web_tests/port/android.py
index b388554..b3b215e 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/android.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/android.py
@@ -793,7 +793,7 @@
                 stderr += '********* [%s] breakpad minidump %s:\n%s' % (
                     self._port.host.filesystem.basename(crash),
                     self._device.serial,
-                    stack)
+                    stack.encode('ascii', 'replace'))
 
         return super(ChromiumAndroidDriver, self)._get_crash_log(
             stdout, stderr, newer_than)
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index be3b603..025864ac3 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -11766,6 +11766,57 @@
   </description>
 </action>
 
+<action name="MobileTabGridBeganReordering">
+  <owner>edchin@chromium.org</owner>
+  <owner>marq@chromium.org</owner>
+  <description>User in the iOS tab grid began reordering tabs.</description>
+</action>
+
+<action name="MobileTabGridCloseAllIncognitoTabs">
+  <owner>edchin@chromium.org</owner>
+  <owner>marq@chromium.org</owner>
+  <description>
+    User in the iOS tab grid used the Close All control while viewing the
+    incognito tabs.
+  </description>
+</action>
+
+<action name="MobileTabGridCloseAllRegularTabs">
+  <owner>edchin@chromium.org</owner>
+  <owner>marq@chromium.org</owner>
+  <description>
+    User in the iOS tab grid used the Close All control while viewing the
+    regular tabs.
+  </description>
+</action>
+
+<action name="MobileTabGridEndedWithoutReordering">
+  <owner>edchin@chromium.org</owner>
+  <owner>marq@chromium.org</owner>
+  <description>
+    User in the iOS tab grid finished reordering tabs, but didn't change the tab
+    order.
+  </description>
+</action>
+
+<action name="MobileTabGridReordered">
+  <owner>edchin@chromium.org</owner>
+  <owner>marq@chromium.org</owner>
+  <description>
+    User in the iOS tab grid finished reordering tabs, and changed the tab
+    order.
+  </description>
+</action>
+
+<action name="MobileTabGridUndoCloseAllRegularTabs">
+  <owner>edchin@chromium.org</owner>
+  <owner>marq@chromium.org</owner>
+  <description>
+    User in the iOS tab grid used the Undo control after closing all regular
+    tabs.
+  </description>
+</action>
+
 <action name="MobileTabReturnedToCurrentTab">
   <owner>rlanday@chromium.org</owner>
   <description>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 218d050f..962ff65 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -19945,6 +19945,8 @@
   <int value="2540" label="TextDecoderStreamConstructor"/>
   <int value="2541" label="SignedExchangeInnerResponse"/>
   <int value="2542" label="PaymentAddressLanguageCode"/>
+  <int value="2543" label="DocumentDomainBlockedCrossOriginAccess"/>
+  <int value="2544" label="DocumentDomainEnabledCrossOriginAccess"/>
 </enum>
 
 <enum name="FeaturePolicyFeature">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 362fb9ab..e735c07d 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -36065,6 +36065,37 @@
   <summary>The time it takes to open a hyphenation dictionary file.</summary>
 </histogram>
 
+<histogram name="ImageLoader.Client.Cache.HitMiss" enum="BooleanCacheHit"
+    expires_after="2019-01-01">
+  <owner>chromeos-files-app@google.com</owner>
+  <owner>tapted@chromium.org</owner>
+  <summary>
+    For each image load request that requested caching, records whether or not
+    it was found in the client-side cache. A hit means the request was not
+    forwarded to the ImageLoader extension.
+  </summary>
+</histogram>
+
+<histogram name="ImageLoader.Client.Cache.Usage" units="%"
+    expires_after="2019-01-01">
+  <owner>chromeos-files-app@google.com</owner>
+  <owner>tapted@chromium.org</owner>
+  <summary>
+    Returns the percentage of the client-side cache that is used for loading
+    images, before they are sent to the ImageLoader extension. Expressed as a
+    percentage of ImageLoaderClient.CACHE_MEMORY_LIMIT (e.g. 20MB).
+  </summary>
+</histogram>
+
+<histogram name="ImageLoader.Client.Cached" enum="BooleanRequested"
+    expires_after="2019-01-01">
+  <owner>chromeos-files-app@google.com</owner>
+  <owner>tapted@chromium.org</owner>
+  <summary>
+    For each image load request records whether or not it requested caching.
+  </summary>
+</histogram>
+
 <histogram name="Import.ImportedHistorySize.AutoImportFromIE" units="urls">
   <owner>gcomanici@chromium.org</owner>
   <summary>
diff --git a/tools/perf/contrib/cros_benchmarks/cros_utils.py b/tools/perf/contrib/cros_benchmarks/cros_utils.py
index 400c0421..58916f7 100644
--- a/tools/perf/contrib/cros_benchmarks/cros_utils.py
+++ b/tools/perf/contrib/cros_benchmarks/cros_utils.py
@@ -126,7 +126,9 @@
       for i in range(5):
         kbd.SwitchTab()
   """
-  REMOTE_LOG_KEY_FILENAME = '/usr/local/tmp/log_key_tab_switch'
+  REMOTE_TEMP_DIR = '/usr/local/tmp/'
+  REMOTE_LOG_KEY_FILENAME = 'log_key_tab_switch'
+  REMOTE_KEY_PROP_FILENAME = 'keyboard.prop'
 
   def __init__(self, dut_ip):
     """Inits KeyboardEmulator.
@@ -146,7 +148,8 @@
     Raises:
       RuntimeError: Keyboard emulation failed.
     """
-    kbd_prop_filename = '/usr/local/autotest/cros/input_playback/keyboard.prop'
+    kbd_prop_filename = os.path.join(KeyboardEmulator.REMOTE_TEMP_DIR,
+                                     KeyboardEmulator.REMOTE_KEY_PROP_FILENAME)
 
     ret = _RunCommand(self._dut_ip, 'test -e %s' % kbd_prop_filename)
     if ret != 0:
@@ -171,22 +174,25 @@
     return key_device_name
 
   def _SetupKeyDispatch(self):
-    """Uploads the script to send key to switch tabs."""
+    """Uploads required files to emulate keyboard."""
     cur_dir = os.path.dirname(os.path.abspath(__file__))
-    log_key_filename = os.path.join(cur_dir, 'data', 'log_key_tab_switch')
-    _CopyToDUT(self._dut_ip, log_key_filename,
-               KeyboardEmulator.REMOTE_LOG_KEY_FILENAME)
+    for filename in (KeyboardEmulator.REMOTE_KEY_PROP_FILENAME,
+                     KeyboardEmulator.REMOTE_LOG_KEY_FILENAME):
+      src = os.path.join(cur_dir, 'data', filename)
+      dest = os.path.join(KeyboardEmulator.REMOTE_TEMP_DIR, filename)
+      _CopyToDUT(self._dut_ip, src, dest)
 
   def __enter__(self):
-    self._key_device_name = self._StartRemoteKeyboardEmulator()
     self._SetupKeyDispatch()
+    self._key_device_name = self._StartRemoteKeyboardEmulator()
     return self
 
   def SwitchTab(self):
     """Sending Ctrl-tab key to trigger tab switching."""
+    log_key_filename = os.path.join(KeyboardEmulator.REMOTE_TEMP_DIR,
+                                    KeyboardEmulator.REMOTE_LOG_KEY_FILENAME)
     cmd = ('evemu-play --insert-slot0 %s < %s' %
-           (self._key_device_name,
-            KeyboardEmulator.REMOTE_LOG_KEY_FILENAME))
+           (self._key_device_name, log_key_filename))
     _RunCommand(self._dut_ip, cmd)
 
   def __exit__(self, exc_type, exc_value, traceback):
diff --git a/tools/perf/contrib/cros_benchmarks/data/keyboard.prop b/tools/perf/contrib/cros_benchmarks/data/keyboard.prop
new file mode 100644
index 0000000..09b1accc
--- /dev/null
+++ b/tools/perf/contrib/cros_benchmarks/data/keyboard.prop
@@ -0,0 +1,24 @@
+N: Emulated Keyboard
+I: 0003 0461 4e05 0111
+P: 00 00 00 00 00 00 00 00
+B: 00 13 00 12 00 00 00 00 00
+B: 01 fe ff ff ff ff ff ff ff
+B: 01 ff ff ef ff df ff be fe
+B: 01 ff 57 40 c1 7a 20 9f ff
+B: 01 07 00 00 00 00 00 01 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 02 00 00 00 00 00 00 00 00
+B: 03 00 00 00 00 00 00 00 00
+B: 04 10 00 00 00 00 00 00 00
+B: 05 00 00 00 00 00 00 00 00
+B: 11 1f 00 00 00 00 00 00 00
+B: 12 00 00 00 00 00 00 00 00
+B: 15 00 00 00 00 00 00 00 00
+B: 15 00 00 00 00 00 00 00 00
diff --git a/ui/accessibility/BUILD.gn b/ui/accessibility/BUILD.gn
index 9232ca1..bc7470d 100644
--- a/ui/accessibility/BUILD.gn
+++ b/ui/accessibility/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/config/linux/pkg_config.gni")
 import("//build/config/features.gni")
+import("//build/config/jumbo.gni")
 import("//build/config/ui.gni")
 import("//mojo/public/tools/bindings/mojom.gni")
 import("//services/service_manager/public/service_manifest.gni")
@@ -28,7 +29,7 @@
   ]
 }
 
-component("accessibility") {
+jumbo_component("accessibility") {
   sources = [
     "ax_action_data.cc",
     "ax_action_data.h",
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index 07d7594..54900335 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/buildflag_header.gni")
 import("//build/config/compiler/compiler.gni")
+import("//build/config/jumbo.gni")
 import("//build/config/linux/pangocairo/pangocairo.gni")
 import("//build/config/sanitizers/sanitizers.gni")
 import("//build/config/ui.gni")
@@ -62,7 +63,7 @@
   ]
 }
 
-component("base") {
+jumbo_component("base") {
   output_name = "ui_base"
 
   sources = [
@@ -682,7 +683,7 @@
   }
 }
 
-static_library("test_support") {
+jumbo_static_library("test_support") {
   testonly = true
   sources = [
     "test/material_design_controller_test_api.cc",
diff --git a/ui/base/ime/BUILD.gn b/ui/base/ime/BUILD.gn
index f143cd4d..75290424 100644
--- a/ui/base/ime/BUILD.gn
+++ b/ui/base/ime/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/config/jumbo.gni")
 import("//build/config/linux/pangocairo/pangocairo.gni")
+import("//build/config/jumbo.gni")
 import("//build/config/ui.gni")
 import("//testing/test.gni")
 
diff --git a/ui/compositor/BUILD.gn b/ui/compositor/BUILD.gn
index e7f9be1..ab830a6 100644
--- a/ui/compositor/BUILD.gn
+++ b/ui/compositor/BUILD.gn
@@ -115,7 +115,7 @@
   }
 }
 
-static_library("test_support") {
+jumbo_static_library("test_support") {
   testonly = true
   sources = [
     "test/context_factories_for_test.cc",
diff --git a/ui/display/BUILD.gn b/ui/display/BUILD.gn
index bd7de89c..1b7c1b6 100644
--- a/ui/display/BUILD.gn
+++ b/ui/display/BUILD.gn
@@ -2,11 +2,12 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/jumbo.gni")
 import("//build/config/ui.gni")
 import("//testing/test.gni")
 import("//ui/display/display.gni")
 
-component("display") {
+jumbo_component("display") {
   sources = [
     "display.cc",
     "display.h",
@@ -101,7 +102,7 @@
   ]
 }
 
-static_library("test_support") {
+jumbo_static_library("test_support") {
   testonly = true
   sources = [
     "test/display_matchers.cc",
diff --git a/ui/events/BUILD.gn b/ui/events/BUILD.gn
index 25b3dc3..3361550 100644
--- a/ui/events/BUILD.gn
+++ b/ui/events/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/features.gni")
+import("//build/config/jumbo.gni")
 import("//build/config/ui.gni")
 import("//testing/test.gni")
 import("//ui/base/ui_features.gni")
@@ -16,7 +17,7 @@
   import("//ios/build/config.gni")
 }
 
-static_library("dom_keycode_converter") {
+jumbo_static_library("dom_keycode_converter") {
   public = [
     "keycodes/dom/dom_code.h",
     "keycodes/dom/dom_codes.h",
@@ -92,7 +93,7 @@
   ]
 }
 
-component("events_base") {
+jumbo_component("events_base") {
   sources = [
     "base_event_utils.cc",
     "base_event_utils.h",
@@ -153,7 +154,7 @@
   }
 }
 
-component("events") {
+jumbo_component("events") {
   sources = [
     "cocoa/cocoa_event_utils.h",
     "cocoa/cocoa_event_utils.mm",
@@ -300,7 +301,7 @@
   }
 }
 
-component("gesture_detection") {
+jumbo_component("gesture_detection") {
   sources = [
     "gesture_detection/bitset_32.h",
     "gesture_detection/filtered_gesture_provider.cc",
@@ -364,7 +365,7 @@
   }
 }
 
-static_library("test_support") {
+jumbo_static_library("test_support") {
   sources = [
     "test/cocoa_test_event_utils.h",
     "test/cocoa_test_event_utils.mm",
diff --git a/ui/file_manager/BUILD.gn b/ui/file_manager/BUILD.gn
index 01983b8..0b6badbb 100644
--- a/ui/file_manager/BUILD.gn
+++ b/ui/file_manager/BUILD.gn
@@ -50,7 +50,9 @@
 
 group("unit_test_data") {
   deps = [
+    "file_manager/foreground/js:unit_tests",
     "gallery/js:unit_tests",
     "gallery/js/image_editor:unit_tests",
+    "image_loader:unit_tests",
   ]
 }
diff --git a/ui/file_manager/externs/BUILD.gn b/ui/file_manager/externs/BUILD.gn
index 1d7d52a..fef4d25 100644
--- a/ui/file_manager/externs/BUILD.gn
+++ b/ui/file_manager/externs/BUILD.gn
@@ -15,3 +15,16 @@
     "webview_tag.js",
   ]
 }
+
+js_library("file_manager_private") {
+  sources = []
+
+  # The file_manager_private extern depends on file_system_provider and
+  # extension APIs. Ensure they're pulled in together.
+  externs_list = [
+    "$externs_path/chrome.js",
+    "$externs_path/chrome_extensions.js",
+    "$externs_path/file_manager_private.js",
+    "$externs_path/file_system_provider.js",
+  ]
+}
diff --git a/ui/file_manager/file_manager/background/js/BUILD.gn b/ui/file_manager/file_manager/background/js/BUILD.gn
index d6a933f..a0a7e43 100644
--- a/ui/file_manager/file_manager/background/js/BUILD.gn
+++ b/ui/file_manager/file_manager/background/js/BUILD.gn
@@ -40,8 +40,6 @@
   sources = []
   externs_list = [
     "$externs_path/command_line_private.js",
-    "$externs_path/file_manager_private.js",
-    "$externs_path/file_system_provider.js",
     "$externs_path/metrics_private.js",
     "../../../externs/background/file_browser_background.js",
     "../../../externs/background/file_browser_background_full.js",
diff --git a/ui/file_manager/file_manager/common/js/BUILD.gn b/ui/file_manager/file_manager/common/js/BUILD.gn
index 506fde5f..a2ff1b61 100644
--- a/ui/file_manager/file_manager/common/js/BUILD.gn
+++ b/ui/file_manager/file_manager/common/js/BUILD.gn
@@ -58,11 +58,10 @@
 js_library("metrics") {
   deps = [
     ":metrics_base",
+    "../../../externs:file_manager_private",
     "//ui/webui/resources/js:assert",
   ]
   externs_list = [
-    "$externs_path/file_manager_private.js",
-    "$externs_path/file_system_provider.js",
     "$externs_path/metrics_private.js",
     "//third_party/analytics/externs.js",
   ]
@@ -97,14 +96,13 @@
   deps = [
     ":files_app_entry_types",
     ":volume_manager_common",
+    "../../../externs:file_manager_private",
     "//ui/webui/resources/js:load_time_data",
     "//ui/webui/resources/js:util",
     "//ui/webui/resources/js/cr:event_target",
     "//ui/webui/resources/js/cr:ui",
   ]
   externs_list = [
-    "$externs_path/chrome.js",
-    "$externs_path/chrome_extensions.js",
     "$externs_path/command_line_private.js",
     "../../../externs/app_window_common.js",
     "../../../externs/entry_location.js",
diff --git a/ui/file_manager/file_manager/foreground/js/BUILD.gn b/ui/file_manager/file_manager/foreground/js/BUILD.gn
index 4d9de83b..7350b5ac 100644
--- a/ui/file_manager/file_manager/foreground/js/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/BUILD.gn
@@ -3,8 +3,9 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
+import("//ui/file_manager/js_unit_tests.gni")
 
-js_type_check("closure_compile") {
+js_type_check("closure_compile_module") {
   deps = [
     ":actions_controller",
     ":actions_model",
@@ -586,6 +587,15 @@
   ]
 }
 
+js_library("thumbnail_loader_unittest") {
+  deps = [
+    ":thumbnail_loader",
+    "../../common/js:mock_entry",
+    "../../common/js:unittest_util",
+    "//ui/webui/resources/js:webui_resource_test",
+  ]
+}
+
 js_library("toolbar_controller") {
   deps = [
     ":file_selection",
@@ -598,6 +608,7 @@
 
 js_library("volume_manager_wrapper") {
   deps = [
+    "../../../externs:file_manager_private",
     "../../common/js:async_util",
     "../../common/js:volume_manager_common",
     "//ui/webui/resources/js:cr",
@@ -605,11 +616,6 @@
     "//ui/webui/resources/js/cr/ui:array_data_model",
   ]
   externs_list = [
-    # Note: volume_info has a dependency on chrome.fileManagerPrivate.IconSet.
-    # Also, fileManagerPrivate depends on chrome.fileSystemProvider, so these
-    # must be introduced together.
-    "$externs_path/file_manager_private.js",
-    "$externs_path/file_system_provider.js",
     "../../../externs/background/volume_manager_factory.js",
     "../../../externs/volume_info.js",
     "../../../externs/volume_info_list.js",
@@ -629,3 +635,16 @@
     "//ui/webui/resources/js/cr/ui:command",
   ]
 }
+
+js_unit_tests("unit_tests") {
+  deps = [
+    ":thumbnail_loader_unittest",
+  ]
+}
+
+group("closure_compile") {
+  deps = [
+    ":closure_compile_module",
+    ":unit_tests_type_check",
+  ]
+}
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn b/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn
index 4269da7..ae6b0098 100644
--- a/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/metadata/BUILD.gn
@@ -36,8 +36,6 @@
   sources = []
   externs_list = [
     "$externs_path/command_line_private.js",
-    "$externs_path/file_manager_private.js",
-    "$externs_path/file_system_provider.js",
     "../../../../externs/app_window_common.js",
     "../../../../externs/entry_location.js",
     "../../../../externs/platform.js",
diff --git a/ui/file_manager/file_manager/foreground/js/thumbnail_loader_unittest.html b/ui/file_manager/file_manager/foreground/js/thumbnail_loader_unittest.html
deleted file mode 100644
index a8e2aec..0000000
--- a/ui/file_manager/file_manager/foreground/js/thumbnail_loader_unittest.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!DOCTYPE html>
-<!-- Copyright 2015 The Chromium Authors. All rights reserved.
-  -- Use of this source code is governed by a BSD-style license that can be
-  -- found in the LICENSE file.
-  -->
-<script src="../../../../webui/resources/js/assert.js"></script>
-<script src="../../common/js/file_type.js"></script>
-<script src="../../common/js/mock_entry.js"></script>
-<script src="../../common/js/unittest_util.js"></script>
-<script src="../../../image_loader/image_loader_client.js"></script>
-<script src="thumbnail_loader.js"></script>
-<script src="thumbnail_loader_unittest.js"></script>
diff --git a/ui/file_manager/file_manager/foreground/js/thumbnail_loader_unittest.js b/ui/file_manager/file_manager/foreground/js/thumbnail_loader_unittest.js
index 807fc8a..4ee77adc 100644
--- a/ui/file_manager/file_manager/foreground/js/thumbnail_loader_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/thumbnail_loader_unittest.js
@@ -3,7 +3,8 @@
 // found in the LICENSE file.
 
 function getLoadTarget(entry, metadata) {
-  return new ThumbnailLoader(entry, null, metadata).getLoadTarget();
+  return new ThumbnailLoader(entry, ThumbnailLoader.LoaderType.CANVAS, metadata)
+      .getLoadTarget();
 }
 
 /**
@@ -26,6 +27,17 @@
   return canvas.toDataURL('image/png');
 }
 
+/**
+ * Installs a mock ImageLoader with a compatible load method.
+ *
+ * @param {function(string, function(!Object), Object=)} mockLoad
+ */
+function installMockLoad(mockLoad) {
+  ImageLoaderClient.getInstance = function() {
+    return /** @type {!ImageLoaderClient} */ ({load: mockLoad});
+  };
+}
+
 function testShouldUseMetadataThumbnail() {
   var mockFileSystem = new MockFileSystem('volumeId');
   var imageEntry = new MockEntry(mockFileSystem, '/test.jpg');
@@ -55,14 +67,9 @@
 }
 
 function testLoadAsDataUrlFromImageClient(callback) {
-  ImageLoaderClient.getInstance = function() {
-    return {
-      load: function(url, callback, opt_option) {
-        callback({
-          status: 'success', data: 'imageDataUrl', width: 32, height: 32});
-      }
-    };
-  };
+  installMockLoad(function(url, callback, opt_option) {
+    callback({status: 'success', data: 'imageDataUrl', width: 32, height: 32});
+  });
 
   var fileSystem = new MockFileSystem('volume-id');
   var entry = new MockEntry(fileSystem, '/Test1.jpg');
@@ -75,15 +82,11 @@
 }
 
 function testLoadAsDataUrlFromExifThumbnail(callback) {
-  ImageLoaderClient.getInstance = function() {
-    return {
-      load: function(url, callback, opt_option) {
-        // Assert that data url is passed.
-        assertTrue(/^data:/i.test(url));
-        callback({status: 'success', data: url, width: 32, height: 32});
-      }
-    };
-  };
+  installMockLoad(function(url, callback, opt_option) {
+    // Assert that data url is passed.
+    assertTrue(/^data:/i.test(url));
+    callback({status: 'success', data: url, width: 32, height: 32});
+  });
 
   var metadata = {
     thumbnail: {
@@ -102,17 +105,17 @@
 }
 
 function testLoadAsDataUrlFromExifThumbnailPropagatesTransform(callback) {
-  ImageLoaderClient.getInstance = function() {
-    return {
-      load: function(url, callback, opt_option) {
-        // Assert that data url and transform info is passed.
-        assertTrue(/^data:/i.test(url));
-        assertEquals(1, opt_option.orientation.rotate90);
-        callback({status: 'success', data: generateSampleImageDataUrl(32, 64),
-            width: 32, height: 64});
-      }
-    };
-  };
+  installMockLoad(function(url, callback, opt_option) {
+    // Assert that data url and transform info is passed.
+    assertTrue(/^data:/i.test(url));
+    assertEquals(1, opt_option.orientation.rotate90);
+    callback({
+      status: 'success',
+      data: generateSampleImageDataUrl(32, 64),
+      width: 32,
+      height: 64
+    });
+  });
 
   var metadata = {
     thumbnail: {
@@ -142,15 +145,15 @@
   var externalCroppedThumbnailUrl = 'https://external-cropped-thumbnail-url/';
   var externalThumbnailDataUrl = generateSampleImageDataUrl(32, 32);
 
-  ImageLoaderClient.getInstance = function() {
-    return {
-      load: function(url, callback, opt_option) {
-        assertEquals(externalCroppedThumbnailUrl, url);
-        callback({status: 'success', data: externalThumbnailDataUrl,
-          width: 32, height: 32});
-      }
-    };
-  };
+  installMockLoad(function(url, callback, opt_option) {
+    assertEquals(externalCroppedThumbnailUrl, url);
+    callback({
+      status: 'success',
+      data: externalThumbnailDataUrl,
+      width: 32,
+      height: 32
+    });
+  });
 
   var metadata = {
     external: {
@@ -170,19 +173,19 @@
 }
 
 function testLoadDetachedFromExifInCavnasModeThumbnailDoesNotRotate(callback) {
-  ImageLoaderClient.getInstance = function() {
-    return {
-      load: function(url, callback, opt_option) {
-        // Assert that data url is passed.
-        assertTrue(/^data:/i.test(url));
-        // Assert that the rotation is propagated to ImageLoader.
-        assertEquals(1, opt_option.orientation.rotate90);
-        // ImageLoader returns rotated image.
-        callback({status: 'success', data: generateSampleImageDataUrl(32, 64),
-            width: 32, height: 64});
-      }
-    };
-  };
+  installMockLoad(function(url, callback, opt_option) {
+    // Assert that data url is passed.
+    assertTrue(/^data:/i.test(url));
+    // Assert that the rotation is propagated to ImageLoader.
+    assertEquals(1, opt_option.orientation.rotate90);
+    // ImageLoader returns rotated image.
+    callback({
+      status: 'success',
+      data: generateSampleImageDataUrl(32, 64),
+      width: 32,
+      height: 64
+    });
+  });
 
   var metadata = {
     thumbnail: {
diff --git a/ui/file_manager/file_manager/test/BUILD.gn b/ui/file_manager/file_manager/test/BUILD.gn
index 6ec1da2..e4916d3 100644
--- a/ui/file_manager/file_manager/test/BUILD.gn
+++ b/ui/file_manager/file_manager/test/BUILD.gn
@@ -50,8 +50,6 @@
   externs_list = [
     "js/externs.js",
     "$externs_path/command_line_private.js",
-    "$externs_path/file_manager_private.js",
-    "$externs_path/file_system_provider.js",
     "$externs_path/metrics_private.js",
     "../../externs/app_window_common.js",
     "../../externs/background/file_browser_background.js",
diff --git a/ui/file_manager/gallery/js/BUILD.gn b/ui/file_manager/gallery/js/BUILD.gn
index 4462d03..b66006f 100644
--- a/ui/file_manager/gallery/js/BUILD.gn
+++ b/ui/file_manager/gallery/js/BUILD.gn
@@ -52,13 +52,10 @@
 
 js_library("entry_list_watcher") {
   deps = [
+    "../../externs:file_manager_private",
     "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js/cr/ui:array_data_model",
   ]
-  externs_list = [
-    "$externs_path/file_system_provider.js",
-    "$externs_path/file_manager_private.js",
-  ]
 }
 
 js_library("entry_list_watcher_unittest") {
diff --git a/ui/file_manager/image_loader/BUILD.gn b/ui/file_manager/image_loader/BUILD.gn
index 1b461874..7398c50 100644
--- a/ui/file_manager/image_loader/BUILD.gn
+++ b/ui/file_manager/image_loader/BUILD.gn
@@ -3,12 +3,12 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
+import("//ui/file_manager/js_unit_tests.gni")
 
-js_type_check("closure_compile") {
+js_type_check("closure_compile_module") {
   deps = [
     ":background",
     ":cache",
-    ":closure_compile_externs",
     ":image_loader",
     ":image_loader_client",
     ":image_loader_util",
@@ -18,17 +18,6 @@
   ]
 }
 
-js_library("closure_compile_externs") {
-  sources = []
-  externs_list = [
-    "$externs_path/chrome_extensions.js",
-    "$externs_path/file_manager_private.js",
-    "$externs_path/file_system_provider.js",
-    "$externs_path/metrics_private.js",
-    "//third_party/analytics/externs.js",
-  ]
-}
-
 js_library("background") {
   deps = [
     ":image_loader",
@@ -38,12 +27,27 @@
 js_library("cache") {
 }
 
+js_library("cache_unittest") {
+  deps = [
+    ":cache",
+    "//ui/webui/resources/js:webui_resource_test",
+  ]
+}
+
 js_library("image_loader") {
   deps = [
     ":cache",
     ":piex_loader",
     ":request",
     ":scheduler",
+    "../externs:file_manager_private",
+  ]
+}
+
+js_library("image_loader_unittest") {
+  deps = [
+    ":image_loader",
+    "//ui/webui/resources/js:webui_resource_test",
   ]
 }
 
@@ -58,6 +62,19 @@
   deps = [
     "../file_manager/common/js:lru_cache",
   ]
+  externs_list = [
+    "$externs_path/chrome.js",
+    "$externs_path/chrome_extensions.js",
+    "$externs_path/metrics_private.js",
+  ]
+}
+
+js_library("image_loader_client_unittest") {
+  deps = [
+    ":image_loader_client",
+    "../file_manager/common/js:unittest_util",
+    "//ui/webui/resources/js:webui_resource_test",
+  ]
 }
 
 js_library("piex_loader") {
@@ -66,6 +83,15 @@
   ]
 }
 
+js_library("piex_loader_unittest") {
+  deps = [
+    ":piex_loader",
+    "../file_manager/common/js:unittest_util",
+    "//ui/webui/resources/js:webui_resource_test",
+    "//ui/webui/resources/js/cr:ui",
+  ]
+}
+
 js_library("request") {
   deps = [
     ":cache",
@@ -82,3 +108,19 @@
     ":request",
   ]
 }
+
+js_unit_tests("unit_tests") {
+  deps = [
+    ":cache_unittest",
+    ":image_loader_client_unittest",
+    ":image_loader_unittest",
+    ":piex_loader_unittest",
+  ]
+}
+
+group("closure_compile") {
+  deps = [
+    ":closure_compile_module",
+    ":unit_tests_type_check",
+  ]
+}
diff --git a/ui/file_manager/image_loader/cache_unittest.html b/ui/file_manager/image_loader/cache_unittest.html
deleted file mode 100644
index ab844595..0000000
--- a/ui/file_manager/image_loader/cache_unittest.html
+++ /dev/null
@@ -1,6 +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.
-  -->
-<script src="cache.js"></script>
-<script src="cache_unittest.js"></script>
diff --git a/ui/file_manager/image_loader/image_loader_client_unittest.html b/ui/file_manager/image_loader/image_loader_client_unittest.html
deleted file mode 100644
index 5de38cdb..0000000
--- a/ui/file_manager/image_loader/image_loader_client_unittest.html
+++ /dev/null
@@ -1,9 +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.
-  -->
-<script src="../file_manager/common/js/lru_cache.js"></script>
-<script src="../file_manager/common/js/unittest_util.js"></script>
-<script src="image_loader_client.js"></script>
-
-<script src="image_loader_client_unittest.js"></script>
diff --git a/ui/file_manager/image_loader/image_loader_client_unittest.js b/ui/file_manager/image_loader/image_loader_client_unittest.js
index dc31dcd1..2c1de53 100644
--- a/ui/file_manager/image_loader/image_loader_client_unittest.js
+++ b/ui/file_manager/image_loader/image_loader_client_unittest.js
@@ -4,19 +4,19 @@
 
 'use strict';
 
-var chrome = {
-  metricsPrivate: {
-    MetricTypeType: {
-      HISTOGRAM_LOG: 'histogram-log',
-      HISTOGRAM_LINEAR: 'histogram-linear'
-    },
+/** @suppress {const|checkTypes} */
+function setUp() {
+  chrome.metricsPrivate = {
+    MetricTypeType:
+        {HISTOGRAM_LOG: 'histogram-log', HISTOGRAM_LINEAR: 'histogram-linear'},
     recordPercentage: function() {},
     recordValue: function() {}
-  },
-  i18n: {
-    getMessage: function() {}
-  }
-};
+  };
+
+  chrome.i18n = {
+    getMessage: function() {},
+  };
+}
 
 /**
  * Lets the client to load URL and returns the local cache (not caches in the
@@ -30,6 +30,7 @@
 function loadAndCheckCacheUsed(client, url, options) {
   var cacheUsed = true;
 
+  /** @suppress {accessControls} */
   ImageLoaderClient.sendMessage_ = function(message, callback) {
     cacheUsed = false;
     if (callback)
diff --git a/ui/file_manager/image_loader/image_loader_unittest.html b/ui/file_manager/image_loader/image_loader_unittest.html
deleted file mode 100644
index 03e7b2b7..0000000
--- a/ui/file_manager/image_loader/image_loader_unittest.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!DOCTYPE html>
-<!-- Copyright 2015 The Chromium Authors. All rights reserved.
-  -- Use of this source code is governed by a BSD-style license that can be
-  -- found in the LICENSE file.
-  -->
-
-<script src="../../webui/resources/js/assert.js"></script>
-<script src="../file_manager/foreground/js/metadata/image_orientation.js"></script>
-<script src="image_loader_util.js"></script>
-
-<script src="image_loader.js"></script>
-<script src="image_loader_unittest.js"></script>
diff --git a/ui/file_manager/image_loader/piex_loader_unittest.html b/ui/file_manager/image_loader/piex_loader_unittest.html
deleted file mode 100644
index def61cc..0000000
--- a/ui/file_manager/image_loader/piex_loader_unittest.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<!-- Copyright 2017 The Chromium Authors. All rights reserved.
-  -- Use of this source code is governed by a BSD-style license that can be
-  -- found in the LICENSE file.
-  -->
-
-<script src="../../webui/resources/js/assert.js"></script>
-<script src="../../webui/resources/js/cr.js"></script>
-<script src="../../webui/resources/js/cr/ui.js"></script>
-<script src="../file_manager/common/js/unittest_util.js"></script>
-<script src="../file_manager/foreground/js/metadata/image_orientation.js"></script>
-
-<script src="piex_loader.js"></script>
-<script src="piex_loader_unittest.js"></script>
diff --git a/ui/file_manager/image_loader/piex_loader_unittest.js b/ui/file_manager/image_loader/piex_loader_unittest.js
index 240f31a..9d09aea 100644
--- a/ui/file_manager/image_loader/piex_loader_unittest.js
+++ b/ui/file_manager/image_loader/piex_loader_unittest.js
@@ -8,7 +8,11 @@
   }
 };
 
-var MockModule = cr.ui.define('div');
+/**
+ * @constructor
+ * @extends {HTMLDivElement}
+ */
+var MockModule = /** @type{function(new:MockModule)}*/ (cr.ui.define('div'));
 MockModule.prototype = Object.create(HTMLDivElement.prototype);
 MockModule.prototype.constructor = MockModule;
 
@@ -70,7 +74,7 @@
               assertEquals('thumbnail-data', data.thumbnail);
               assertEquals(0, unloadCount);
               assertEquals(1, loadCount);
-              return loader.load('http://foobar/another.raw')
+              return loader.load('http://foobar/another.raw');
             })
             .then(function(data) {
               // The NaCl module is not unloaded, as the next request came
@@ -85,7 +89,7 @@
               // unload the NaCl module.
               loader.simulateIdleTimeoutPassedForTests();
               assertEquals(1, unloadCount);
-              return loader.load('http://foobar/chocolate.raw')
+              return loader.load('http://foobar/chocolate.raw');
             })
             .then(function(data) {
               // Following requests should reload the NaCl module.
@@ -97,4 +101,4 @@
         unloadPromise
       ]),
       callback);
-};
+}
diff --git a/ui/file_manager/video_player/js/cast/BUILD.gn b/ui/file_manager/video_player/js/cast/BUILD.gn
index 2435e5b..b0ca2c8 100644
--- a/ui/file_manager/video_player/js/cast/BUILD.gn
+++ b/ui/file_manager/video_player/js/cast/BUILD.gn
@@ -19,8 +19,6 @@
   externs_list = [
     "$externs_path/chrome_extensions.js",
     "$externs_path/command_line_private.js",
-    "$externs_path/file_manager_private.js",
-    "$externs_path/file_system_provider.js",
     "$externs_path/media_player_private.js",
     "$externs_path/metrics_private.js",
     "../../../externs/app_window_common.js",
diff --git a/ui/gfx/BUILD.gn b/ui/gfx/BUILD.gn
index 9bfe0d99..26c1273 100644
--- a/ui/gfx/BUILD.gn
+++ b/ui/gfx/BUILD.gn
@@ -483,7 +483,7 @@
 }
 
 # Cannot be a static_library in component builds due to exported functions
-source_set("memory_buffer_sources") {
+jumbo_source_set("memory_buffer_sources") {
   visibility = [ ":*" ]  # Depend on through ":memory_buffer".
 
   # TODO(brettw) refactor this so these sources are in a coherent directory
@@ -574,7 +574,7 @@
   ]
 }
 
-static_library("test_support") {
+jumbo_static_library("test_support") {
   testonly = true
   sources = [
     "animation/animation_test_api.cc",
diff --git a/ui/gfx/mojo/BUILD.gn b/ui/gfx/mojo/BUILD.gn
index 046afff7..fb02266 100644
--- a/ui/gfx/mojo/BUILD.gn
+++ b/ui/gfx/mojo/BUILD.gn
@@ -15,6 +15,7 @@
     "overlay_transform.mojom",
     "presentation_feedback.mojom",
     "selection_bound.mojom",
+    "swap_result.mojom",
     "transform.mojom",
   ]
 
diff --git a/ui/gfx/mojo/swap_result.mojom b/ui/gfx/mojo/swap_result.mojom
new file mode 100644
index 0000000..b73d4e43
--- /dev/null
+++ b/ui/gfx/mojo/swap_result.mojom
@@ -0,0 +1,16 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module gfx.mojom;
+
+// SwapResult information which is used to indicate whether buffer swap
+// succeeded or not. These values correspond to gfx::SwapResult values in
+// ui/gfx/swap_result.h. Currently, it is used by the Ozone/Wayland to identify
+// whether a buffer swap requested by the GPU process has been successful on
+// the browser process side or not.
+enum SwapResult {
+  ACK,
+  FAILED,
+  NAK_RECREATE_BUFFERS,
+};
diff --git a/ui/gfx/mojo/swap_result.typemap b/ui/gfx/mojo/swap_result.typemap
new file mode 100644
index 0000000..76d24a50
--- /dev/null
+++ b/ui/gfx/mojo/swap_result.typemap
@@ -0,0 +1,8 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+mojom = "//ui/gfx/mojo/swap_result.mojom"
+public_headers = [ "//ui/gfx/swap_result.h" ]
+traits_headers = [ "//ui/gfx/mojo/swap_result_enum_traits.h" ]
+type_mappings = [ "gfx.mojom.SwapResult=gfx::SwapResult" ]
diff --git a/ui/gfx/mojo/swap_result_enum_traits.h b/ui/gfx/mojo/swap_result_enum_traits.h
new file mode 100644
index 0000000..c32ca59b
--- /dev/null
+++ b/ui/gfx/mojo/swap_result_enum_traits.h
@@ -0,0 +1,48 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_MOJO_SWAP_RESULT_ENUM_TRAITS_H_
+#define UI_GFX_MOJO_SWAP_RESULT_ENUM_TRAITS_H_
+
+#include "mojo/public/cpp/bindings/enum_traits.h"
+#include "ui/gfx/mojo/swap_result.mojom-shared.h"
+#include "ui/gfx/swap_result.h"
+
+namespace mojo {
+
+template <>
+struct EnumTraits<gfx::mojom::SwapResult, gfx::SwapResult> {
+  static gfx::mojom::SwapResult ToMojom(gfx::SwapResult input) {
+    switch (input) {
+      case gfx::SwapResult::SWAP_ACK:
+        return gfx::mojom::SwapResult::ACK;
+      case gfx::SwapResult::SWAP_FAILED:
+        return gfx::mojom::SwapResult::FAILED;
+      case gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS:
+        return gfx::mojom::SwapResult::NAK_RECREATE_BUFFERS;
+    }
+    NOTREACHED();
+    return gfx::mojom::SwapResult::FAILED;
+  }
+
+  static bool FromMojom(gfx::mojom::SwapResult input, gfx::SwapResult* out) {
+    switch (input) {
+      case gfx::mojom::SwapResult::ACK:
+        *out = gfx::SwapResult::SWAP_ACK;
+        return true;
+      case gfx::mojom::SwapResult::FAILED:
+        *out = gfx::SwapResult::SWAP_FAILED;
+        return true;
+      case gfx::mojom::SwapResult::NAK_RECREATE_BUFFERS:
+        *out = gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS;
+        return true;
+    }
+    NOTREACHED();
+    return false;
+  }
+};
+
+}  // namespace mojo
+
+#endif  // UI_GFX_MOJO_SWAP_RESULT_ENUM_TRAITS_H_
diff --git a/ui/gfx/typemaps.gni b/ui/gfx/typemaps.gni
index ba383a8..c4ea97d 100644
--- a/ui/gfx/typemaps.gni
+++ b/ui/gfx/typemaps.gni
@@ -13,6 +13,7 @@
   "//ui/gfx/mojo/gpu_fence_handle.typemap",
   "//ui/gfx/mojo/overlay_transform.typemap",
   "//ui/gfx/mojo/presentation_feedback.typemap",
+  "//ui/gfx/mojo/swap_result.typemap",
   "//ui/gfx/mojo/selection_bound.typemap",
   "//ui/gfx/mojo/transform.typemap",
   "//ui/gfx/range/mojo/range.typemap",
diff --git a/ui/gl/BUILD.gn b/ui/gl/BUILD.gn
index 2699acc..c92de71 100644
--- a/ui/gl/BUILD.gn
+++ b/ui/gl/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/buildflag_header.gni")
+import("//build/config/jumbo.gni")
 import("//build/config/chrome_build.gni")
 import("//build/config/linux/pkg_config.gni")
 import("//build/config/ui.gni")
@@ -46,7 +47,7 @@
   }
 }
 
-component("gl") {
+jumbo_component("gl") {
   output_name = "gl_wrapper"  # Avoid colliding with OS X"s libGL.dylib.
 
   sources = [
@@ -375,7 +376,7 @@
   }
 }
 
-static_library("gl_unittest_utils") {
+jumbo_static_library("gl_unittest_utils") {
   testonly = true
   sources = [
     "egl_bindings_autogen_mock.cc",
@@ -401,7 +402,7 @@
   ]
 }
 
-static_library("test_support") {
+jumbo_static_library("test_support") {
   testonly = true
   sources = [
     "test/gl_image_test_support.cc",
diff --git a/ui/ozone/common/linux/gbm_wrapper.cc b/ui/ozone/common/linux/gbm_wrapper.cc
index 99eed07..b9a05676 100644
--- a/ui/ozone/common/linux/gbm_wrapper.cc
+++ b/ui/ozone/common/linux/gbm_wrapper.cc
@@ -4,7 +4,9 @@
 
 #include "ui/ozone/common/linux/gbm_wrapper.h"
 
+#include <fcntl.h>
 #include <gbm.h>
+#include <xf86drm.h>
 
 #include "base/posix/eintr_wrapper.h"
 #include "ui/gfx/buffer_format_util.h"
@@ -14,6 +16,91 @@
 
 namespace gbm_wrapper {
 
+namespace {
+
+// Minigbm and system linux gbm have some differences. There is no clear way how
+// to distinguish linux gbm (Mesa, basically) and minigbm, which can be both
+// third_party and minigbm. Thus, use GBM_BO_IMPORT_FD_PLANAR define to
+// identify, which gbm is used.
+#if defined(GBM_BO_IMPORT_FD_PLANAR)
+// Minigbm defines GBM_BO_IMPORT_FD_PLANAR, which is unknown in linux gbm.
+// Redefine it in a common define.
+#define GBM_BO_IMPORT_FD_DATA GBM_BO_IMPORT_FD_PLANAR
+
+// There are some methods, which require knowing whether minigbm is used or not.
+#ifndef USING_MINIGBM
+#define USING_MINIGBM
+#endif  // USING_MINIGBM
+
+// Minigbm and system linux gbm have alike gbm_bo_import* structures, but some
+// of the data variables have different type.
+using gbm_bo_import_fd_data_with_modifier = struct gbm_import_fd_planar_data;
+#else
+// See comment above. Linux defines GBM_BO_IMPORT_FD_MODIFIER, thus, redefine it
+// in a common define.
+#define GBM_BO_IMPORT_FD_DATA GBM_BO_IMPORT_FD_MODIFIER
+// See comment above.
+using gbm_bo_import_fd_data_with_modifier = struct gbm_import_fd_modifier_data;
+#endif
+
+void InitializeImportData(uint32_t format,
+                          const gfx::Size& size,
+                          const std::vector<base::ScopedFD>& fds,
+                          const std::vector<gfx::NativePixmapPlane>& planes,
+                          gbm_bo_import_fd_data_with_modifier* fd_data) {
+  fd_data->width = size.width();
+  fd_data->height = size.height();
+  fd_data->format = format;
+
+  DCHECK_LE(planes.size(), 3u);
+  for (size_t i = 0; i < planes.size(); ++i) {
+    fd_data->fds[i] = fds[i < fds.size() ? i : 0].get();
+    fd_data->strides[i] = planes[i].stride;
+    fd_data->offsets[i] = planes[i].offset;
+#if defined(USING_MINIGBM)
+    fd_data->format_modifiers[i] = planes[i].modifier;
+#else
+    fd_data->modifier = planes[i].modifier;
+#endif
+  }
+}
+
+int GetPlaneFdForBo(gbm_bo* bo, size_t plane) {
+  DCHECK(plane < gbm_bo_get_plane_count(bo));
+
+  // System linux gbm (or Mesa gbm) does not provide fds per plane basis. Thus,
+  // get plane handle and use drm ioctl to get a prime fd out of it avoid having
+  // two different branches for minigbm and Mesa gbm here.
+  gbm_device* gbm_dev = gbm_bo_get_device(bo);
+  int dev_fd = gbm_device_get_fd(gbm_dev);
+  if (dev_fd <= 0) {
+    LOG(ERROR) << "Unable to get device fd";
+    return -1;
+  }
+
+  const uint32_t plane_handle = gbm_bo_get_handle_for_plane(bo, plane).u32;
+  int fd = -1;
+  int ret;
+  // Use DRM_RDWR to allow the fd to be mappable in another process.
+  ret = drmPrimeHandleToFD(dev_fd, plane_handle, DRM_CLOEXEC | DRM_RDWR, &fd);
+
+  // Older DRM implementations blocked DRM_RDWR, but gave a read/write mapping
+  // anyways
+  if (ret)
+    ret = drmPrimeHandleToFD(dev_fd, plane_handle, DRM_CLOEXEC, &fd);
+
+  return ret ? ret : fd;
+}
+
+size_t GetSizeOfPlane(gbm_bo* bo, size_t plane) {
+  // System linux gbm (or Mesa gbm) does not provide plane size. Thus, calculate
+  // it by ourselves and avoid having two different branches for minigbm and
+  // Mesa gbm here.
+  return gbm_bo_get_height(bo) * gbm_bo_get_stride_for_plane(bo, plane);
+}
+
+}  // namespace
+
 class Buffer final : public ui::GbmBuffer {
  public:
   Buffer(struct gbm_bo* bo,
@@ -72,7 +159,7 @@
   }
   uint32_t GetPlaneHandle(size_t plane) const override {
     DCHECK_LT(plane, planes_.size());
-    return gbm_bo_get_plane_handle(bo_, plane).u32;
+    return gbm_bo_get_handle_for_plane(bo_, plane).u32;
   }
   uint32_t GetHandle() const override { return gbm_bo_get_handle(bo_).u32; }
   gfx::NativePixmapHandle ExportHandle() const override {
@@ -121,11 +208,18 @@
   std::vector<base::ScopedFD> fds;
   std::vector<gfx::NativePixmapPlane> planes;
 
-  const uint64_t modifier = gbm_bo_get_format_modifier(bo);
-  for (size_t i = 0; i < gbm_bo_get_num_planes(bo); ++i) {
+  const uint64_t modifier = gbm_bo_get_modifier(bo);
+  const int plane_count = gbm_bo_get_plane_count(bo);
+  // The Mesa's gbm implementation explicitly checks whether plane count <= and
+  // returns 1 if the condition is true. Nevertheless, use a DCHECK here to make
+  // sure the condition is not broken there.
+  DCHECK(plane_count > 0);
+  // Ensure there are no differences in integer signs by casting any possible
+  // values to size_t.
+  for (size_t i = 0; i < static_cast<size_t>(plane_count); ++i) {
     // The fd returned by gbm_bo_get_fd is not ref-counted and need to be
     // kept open for the lifetime of the buffer.
-    base::ScopedFD fd(gbm_bo_get_plane_fd(bo, i));
+    base::ScopedFD fd(GetPlaneFdForBo(bo, i));
 
     // TODO(dcastagna): support multiple fds.
     // crbug.com/642410
@@ -138,9 +232,9 @@
       fds.emplace_back(std::move(fd));
     }
 
-    planes.emplace_back(gbm_bo_get_plane_stride(bo, i),
-                        gbm_bo_get_plane_offset(bo, i),
-                        gbm_bo_get_plane_size(bo, i), modifier);
+    planes.emplace_back(gbm_bo_get_stride_for_plane(bo, i),
+                        gbm_bo_get_offset(bo, i), GetSizeOfPlane(bo, i),
+                        modifier);
   }
   return std::make_unique<Buffer>(bo, format, flags, modifier, std::move(fds),
                                   size, std::move(planes));
@@ -187,28 +281,21 @@
     DCHECK_EQ(planes[0].offset, 0);
 
     // Try to use scanout if supported.
-    int gbm_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_TEXTURING;
+    int gbm_flags = GBM_BO_USE_SCANOUT;
+#if defined(GBM_BO_USE_TEXTURING)
+    gbm_flags |= GBM_BO_USE_TEXTURING;
+#endif
     if (!gbm_device_is_format_supported(device_, format, gbm_flags))
       gbm_flags &= ~GBM_BO_USE_SCANOUT;
 
     struct gbm_bo* bo = nullptr;
     if (gbm_device_is_format_supported(device_, format, gbm_flags)) {
-      struct gbm_import_fd_planar_data fd_data;
-      fd_data.width = size.width();
-      fd_data.height = size.height();
-      fd_data.format = format;
-
-      DCHECK_LE(planes.size(), 3u);
-      for (size_t i = 0; i < planes.size(); ++i) {
-        fd_data.fds[i] = fds[i < fds.size() ? i : 0].get();
-        fd_data.strides[i] = planes[i].stride;
-        fd_data.offsets[i] = planes[i].offset;
-        fd_data.format_modifiers[i] = planes[i].modifier;
-      }
+      gbm_bo_import_fd_data_with_modifier fd_data;
+      InitializeImportData(format, size, fds, planes, &fd_data);
 
       // The fd passed to gbm_bo_import is not ref-counted and need to be
       // kept open for the lifetime of the buffer.
-      bo = gbm_bo_import(device_, GBM_BO_IMPORT_FD_PLANAR, &fd_data, gbm_flags);
+      bo = gbm_bo_import(device_, GBM_BO_IMPORT_FD_DATA, &fd_data, gbm_flags);
       if (!bo) {
         LOG(ERROR) << "nullptr returned from gbm_bo_import";
         return nullptr;
diff --git a/ui/ozone/platform/wayland/BUILD.gn b/ui/ozone/platform/wayland/BUILD.gn
index 7a846766..76dcdb5 100644
--- a/ui/ozone/platform/wayland/BUILD.gn
+++ b/ui/ozone/platform/wayland/BUILD.gn
@@ -85,6 +85,7 @@
     "//third_party/minigbm",
     "//third_party/wayland:wayland_client",
     "//third_party/wayland-protocols:linux_dmabuf_protocol",
+    "//third_party/wayland-protocols:presentation_time_protocol",
     "//third_party/wayland-protocols:xdg_shell_protocol",
     "//ui/base",
     "//ui/base:ui_features",
@@ -157,6 +158,7 @@
     "//testing/gtest",
     "//third_party/wayland:wayland_server",
     "//third_party/wayland-protocols:xdg_shell_protocol",
+    "//ui/base",
     "//ui/base:ui_features",
     "//ui/events/ozone:events_ozone_layout",
     "//ui/ozone:platform",
diff --git a/ui/ozone/platform/wayland/DEPS b/ui/ozone/platform/wayland/DEPS
index fde6dba3..42e7c86b 100644
--- a/ui/ozone/platform/wayland/DEPS
+++ b/ui/ozone/platform/wayland/DEPS
@@ -1,5 +1,7 @@
 include_rules = [
   "+ui/base/ui_features.h",  # UI features doesn't bring in all of ui/base.
   "+mojo/public",
+  "+ui/base/dragdrop/drag_drop_types.h",
+  "+ui/base/dragdrop/os_exchange_data.h",
+  "+ui/base/dragdrop/os_exchange_data_provider_aura.h",
 ]
-
diff --git a/ui/ozone/platform/wayland/fake_server.cc b/ui/ozone/platform/wayland/fake_server.cc
index 6a63f3a..2bd0f9c 100644
--- a/ui/ozone/platform/wayland/fake_server.cc
+++ b/ui/ozone/platform/wayland/fake_server.cc
@@ -290,6 +290,15 @@
 
 // wl_data_device
 
+void DataDeviceStartDrag(wl_client* client,
+                         wl_resource* resource,
+                         wl_resource* source,
+                         wl_resource* origin,
+                         wl_resource* icon,
+                         uint32_t serial) {
+  NOTIMPLEMENTED();
+}
+
 void DataDeviceSetSelection(wl_client* client,
                             wl_resource* resource,
                             wl_resource* data_source,
@@ -304,8 +313,7 @@
 }
 
 const struct wl_data_device_interface data_device_impl = {
-    nullptr /*data_device_start_drag*/, &DataDeviceSetSelection,
-    &DataDeviceRelease};
+    &DataDeviceStartDrag, &DataDeviceSetSelection, &DataDeviceRelease};
 
 // wl_data_device_manager
 
@@ -347,6 +355,13 @@
 
 // wl_data_offer
 
+void DataOfferAccept(wl_client* client,
+                     wl_resource* resource,
+                     uint32_t serial,
+                     const char* mime_type) {
+  NOTIMPLEMENTED();
+}
+
 void DataOfferReceive(wl_client* client,
                       wl_resource* resource,
                       const char* mime_type,
@@ -359,10 +374,20 @@
   wl_resource_destroy(resource);
 }
 
+void DataOfferFinish(wl_client* client, wl_resource* resource) {
+  NOTIMPLEMENTED();
+}
+
+void DataOfferSetActions(wl_client* client,
+                         wl_resource* resource,
+                         uint32_t dnd_actions,
+                         uint32_t preferred_action) {
+  NOTIMPLEMENTED();
+}
+
 const struct wl_data_offer_interface data_offer_impl = {
-    nullptr /* data_offer_accept*/, DataOfferReceive,
-    nullptr /*data_offer_finish*/, DataOfferDestroy,
-    nullptr /*data_offer_set_actions*/};
+    DataOfferAccept, DataOfferReceive, DataOfferDestroy, DataOfferFinish,
+    DataOfferSetActions};
 
 // wl_data_source
 
@@ -376,8 +401,14 @@
   wl_resource_destroy(resource);
 }
 
+void SetActions(wl_client* client,
+                wl_resource* resource,
+                uint32_t dnd_actions) {
+  NOTIMPLEMENTED();
+}
+
 const struct wl_data_source_interface data_source_impl = {
-    DataSourceOffer, DataSourceDestroy, nullptr /*data_source_set_actions*/};
+    DataSourceOffer, DataSourceDestroy, SetActions};
 
 // wl_seat
 
@@ -784,10 +815,15 @@
 
 void MockDataOffer::Receive(const std::string& mime_type, base::ScopedFD fd) {
   DCHECK(fd.is_valid());
-  std::string text_utf8(kSampleClipboardText);
+  std::string text_data;
+  if (mime_type == kTextMimeTypeUtf8)
+    text_data = kSampleClipboardText;
+  else if (mime_type == kTextMimeTypeText)
+    text_data = kSampleTextForDragAndDrop;
+
   io_thread_.task_runner()->PostTask(
       FROM_HERE,
-      base::BindOnce(&WriteDataOnWorkerThread, std::move(fd), text_utf8));
+      base::BindOnce(&WriteDataOnWorkerThread, std::move(fd), text_data));
 }
 
 void MockDataOffer::OnOffer(const std::string& mime_type) {
@@ -816,6 +852,27 @@
   return GetUserDataAs<MockDataOffer>(data_offer_resource);
 }
 
+void MockDataDevice::OnEnter(uint32_t serial,
+                             wl_resource* surface,
+                             wl_fixed_t x,
+                             wl_fixed_t y,
+                             MockDataOffer& data_offer) {
+  wl_data_device_send_enter(resource(), serial, surface, x, y,
+                            data_offer.resource());
+}
+
+void MockDataDevice::OnLeave() {
+  wl_data_device_send_leave(resource());
+}
+
+void MockDataDevice::OnMotion(uint32_t time, wl_fixed_t x, wl_fixed_t y) {
+  wl_data_device_send_motion(resource(), time, x, y);
+}
+
+void MockDataDevice::OnDrop() {
+  wl_data_device_send_drop(resource());
+}
+
 void MockDataDevice::OnSelection(MockDataOffer& data_offer) {
   wl_data_device_send_selection(resource(), data_offer.resource());
 }
diff --git a/ui/ozone/platform/wayland/fake_server.h b/ui/ozone/platform/wayland/fake_server.h
index 7739e6d..4ee8348 100644
--- a/ui/ozone/platform/wayland/fake_server.h
+++ b/ui/ozone/platform/wayland/fake_server.h
@@ -24,7 +24,10 @@
 namespace wl {
 
 constexpr char kTextMimeTypeUtf8[] = "text/plain;charset=utf-8";
+constexpr char kTextMimeTypeText[] = "text/plain";
 constexpr char kSampleClipboardText[] = "This is a sample text for clipboard.";
+constexpr char kSampleTextForDragAndDrop[] =
+    "This is a sample text for drag-and-drop.";
 
 // Base class for managing the life cycle of server objects.
 class ServerObject {
@@ -222,6 +225,14 @@
   void SetSelection(MockDataSource* data_source, uint32_t serial);
 
   MockDataOffer* OnDataOffer();
+  void OnEnter(uint32_t serial,
+               wl_resource* surface,
+               wl_fixed_t x,
+               wl_fixed_t y,
+               MockDataOffer& data_offer);
+  void OnLeave();
+  void OnMotion(uint32_t time, wl_fixed_t x, wl_fixed_t y);
+  void OnDrop();
   void OnSelection(MockDataOffer& data_offer);
 
  private:
diff --git a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
index 5403739..2e2e352 100644
--- a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
+++ b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
@@ -69,8 +69,7 @@
 }
 
 bool GbmSurfacelessWayland::SupportsPostSubBuffer() {
-  // TODO(msisov): figure out how to enable subbuffers with wayland/dmabuf.
-  return false;
+  return true;
 }
 
 gfx::SwapResult GbmSurfacelessWayland::PostSubBuffer(
@@ -135,8 +134,10 @@
     int height,
     const SwapCompletionCallback& completion_callback,
     const PresentationCallback& presentation_callback) {
-  // See the comment in SupportsPostSubBuffer.
-  NOTREACHED();
+  PendingFrame* frame = unsubmitted_frames_.back().get();
+  frame->damage_region_ = gfx::Rect(x, y, width, height);
+
+  SwapBuffersAsync(completion_callback, presentation_callback);
 }
 
 EGLConfig GbmSurfacelessWayland::GetConfig() {
@@ -198,16 +199,13 @@
       return;
     }
 
+    auto callback =
+        base::BindOnce(&GbmSurfacelessWayland::OnScheduleBufferSwapDone,
+                       weak_factory_.GetWeakPtr());
     uint32_t buffer_id = planes_.back().pixmap->GetUniqueId();
-    surface_factory_->ScheduleBufferSwap(widget_, buffer_id);
-
-    // Check comment in ::SupportsPresentationCallback.
-    OnSubmission(gfx::SwapResult::SWAP_ACK, nullptr);
-    OnPresentation(
-        gfx::PresentationFeedback(base::TimeTicks::Now(), base::TimeDelta(),
-                                  gfx::PresentationFeedback::kZeroCopy));
-
-    planes_.clear();
+    surface_factory_->ScheduleBufferSwap(widget_, buffer_id,
+                                         submitted_frame_->damage_region_,
+                                         std::move(callback));
   }
 }
 
@@ -224,6 +222,14 @@
   SubmitFrame();
 }
 
+void GbmSurfacelessWayland::OnScheduleBufferSwapDone(
+    gfx::SwapResult result,
+    const gfx::PresentationFeedback& feedback) {
+  OnSubmission(result, nullptr);
+  OnPresentation(feedback);
+  planes_.clear();
+}
+
 void GbmSurfacelessWayland::OnSubmission(
     gfx::SwapResult result,
     std::unique_ptr<gfx::GpuFence> out_fence) {
diff --git a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h
index cc660f3..bfb8c9f9 100644
--- a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h
+++ b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h
@@ -68,6 +68,10 @@
 
     bool ready = false;
     gfx::SwapResult swap_result = gfx::SwapResult::SWAP_FAILED;
+    // A region of the updated content in a corresponding frame. It's used to
+    // advice Wayland which part of a buffer is going to be updated. Passing {0,
+    // 0, 0, 0} results in a whole buffer update on the Wayland compositor side.
+    gfx::Rect damage_region_ = gfx::Rect();
     std::vector<gl::GLSurfaceOverlay> overlays;
     SwapCompletionCallback completion_callback;
     PresentationCallback presentation_callback;
@@ -78,6 +82,8 @@
   EGLSyncKHR InsertFence(bool implicit);
   void FenceRetired(PendingFrame* frame);
 
+  void OnScheduleBufferSwapDone(gfx::SwapResult result,
+                                const gfx::PresentationFeedback& feedback);
   void OnSubmission(gfx::SwapResult result,
                     std::unique_ptr<gfx::GpuFence> out_fence);
   void OnPresentation(const gfx::PresentationFeedback& feedback);
diff --git a/ui/ozone/platform/wayland/gpu/wayland_connection_proxy.cc b/ui/ozone/platform/wayland/gpu/wayland_connection_proxy.cc
index 88b9515..226f6d4 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_connection_proxy.cc
+++ b/ui/ozone/platform/wayland/gpu/wayland_connection_proxy.cc
@@ -86,11 +86,15 @@
   wc_ptr_->DestroyZwpLinuxDmabuf(buffer_id);
 }
 
-void WaylandConnectionProxy::ScheduleBufferSwap(gfx::AcceleratedWidget widget,
-                                                uint32_t buffer_id) {
+void WaylandConnectionProxy::ScheduleBufferSwap(
+    gfx::AcceleratedWidget widget,
+    uint32_t buffer_id,
+    const gfx::Rect& damage_region,
+    wl::BufferSwapCallback callback) {
   DCHECK(gpu_thread_runner_->BelongsToCurrentThread());
   DCHECK(wc_ptr_);
-  wc_ptr_->ScheduleBufferSwap(widget, buffer_id);
+  wc_ptr_->ScheduleBufferSwap(widget, buffer_id, damage_region,
+                              std::move(callback));
 }
 
 WaylandWindow* WaylandConnectionProxy::GetWindow(
diff --git a/ui/ozone/platform/wayland/gpu/wayland_connection_proxy.h b/ui/ozone/platform/wayland/gpu/wayland_connection_proxy.h
index 9126716..f06f9b9a 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_connection_proxy.h
+++ b/ui/ozone/platform/wayland/gpu/wayland_connection_proxy.h
@@ -11,6 +11,7 @@
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/ozone/platform/wayland/wayland_connection.h"
+#include "ui/ozone/platform/wayland/wayland_util.h"
 #include "ui/ozone/public/interfaces/wayland/wayland_connection.mojom.h"
 
 #if defined(WAYLAND_GBM)
@@ -19,6 +20,11 @@
 
 struct wl_shm;
 
+namespace gfx {
+enum class SwapResult;
+class Rect;
+}  // namespace gfx
+
 namespace ui {
 
 class WaylandConnection;
@@ -58,7 +64,12 @@
 
   // Asks Wayland to find a wl_buffer with the |buffer_id| and schedule a
   // buffer swap for a WaylandWindow, which backs the following |widget|.
-  void ScheduleBufferSwap(gfx::AcceleratedWidget widget, uint32_t buffer_id);
+  // The |callback| is called once a frame callback from the Wayland server
+  // is received.
+  void ScheduleBufferSwap(gfx::AcceleratedWidget widget,
+                          uint32_t buffer_id,
+                          const gfx::Rect& damage_region,
+                          wl::BufferSwapCallback callback);
 
 #if defined(WAYLAND_GBM)
   // Returns a gbm_device based on a DRM render node.
diff --git a/ui/ozone/platform/wayland/wayland_buffer_manager.cc b/ui/ozone/platform/wayland/wayland_buffer_manager.cc
index 71e71d06..2c9a02f 100644
--- a/ui/ozone/platform/wayland/wayland_buffer_manager.cc
+++ b/ui/ozone/platform/wayland/wayland_buffer_manager.cc
@@ -5,8 +5,8 @@
 #include "ui/ozone/platform/wayland/wayland_buffer_manager.h"
 
 #include <drm_fourcc.h>
-
 #include <linux-dmabuf-unstable-v1-client-protocol.h>
+#include <presentation-time-client-protocol.h>
 
 #include "base/trace_event/trace_event.h"
 #include "ui/ozone/common/linux/drm_util_linux.h"
@@ -15,6 +15,33 @@
 
 namespace ui {
 
+namespace {
+
+uint32_t GetPresentationKindFlags(uint32_t flags) {
+  uint32_t presentation_flags = 0;
+  if (flags & WP_PRESENTATION_FEEDBACK_KIND_VSYNC)
+    presentation_flags |= gfx::PresentationFeedback::kVSync;
+  if (flags & WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK)
+    presentation_flags |= gfx::PresentationFeedback::kHWClock;
+  if (flags & WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION)
+    presentation_flags |= gfx::PresentationFeedback::kHWCompletion;
+  if (flags & WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY)
+    presentation_flags |= gfx::PresentationFeedback::kZeroCopy;
+
+  return presentation_flags;
+}
+
+base::TimeTicks GetPresentationFeedbackTimeStamp(uint32_t tv_sec_hi,
+                                                 uint32_t tv_sec_lo,
+                                                 uint32_t tv_nsec) {
+  const int64_t seconds = (static_cast<int64_t>(tv_sec_hi) << 32) + tv_sec_lo;
+  const int64_t microseconds = seconds * base::Time::kMicrosecondsPerSecond +
+                               tv_nsec / base::Time::kNanosecondsPerMicrosecond;
+  return base::TimeTicks() + base::TimeDelta::FromMicroseconds(microseconds);
+}
+
+}  // namespace
+
 WaylandBufferManager::Buffer::Buffer() = default;
 WaylandBufferManager::Buffer::Buffer(uint32_t id,
                                      zwp_linux_buffer_params_v1* zwp_params)
@@ -89,9 +116,11 @@
 
 // TODO(msisov): handle buffer swap failure or success.
 bool WaylandBufferManager::ScheduleBufferSwap(gfx::AcceleratedWidget widget,
-                                              uint32_t buffer_id) {
-  TRACE_EVENT1("Wayland", "WaylandBufferManager::SwapBuffer", "Buffer id",
-               buffer_id);
+                                              uint32_t buffer_id,
+                                              const gfx::Rect& damage_region,
+                                              wl::BufferSwapCallback callback) {
+  TRACE_EVENT1("Wayland", "WaylandBufferManager::ScheduleSwapBuffer",
+               "Buffer id", buffer_id);
 
   if (!ValidateDataFromGpu(widget, buffer_id))
     return false;
@@ -109,6 +138,8 @@
   // Assign a widget to this buffer, which is used to find a corresponding
   // WaylandWindow.
   buffer->widget = widget;
+  buffer->buffer_swap_callback = std::move(callback);
+  buffer->damage_region = damage_region;
 
   if (buffer->wl_buffer) {
     // A wl_buffer might not exist by this time. Silently return.
@@ -127,6 +158,15 @@
     error_message_ = "Trying to destroy non-existing buffer";
     return false;
   }
+  // It can happen that a buffer is destroyed before a frame callback comes.
+  // Thus, just mark this as a successful swap, which is ok to do.
+  Buffer* buffer = it->second.get();
+  if (!buffer->buffer_swap_callback.is_null()) {
+    std::move(buffer->buffer_swap_callback)
+        .Run(gfx::SwapResult::SWAP_ACK,
+             gfx::PresentationFeedback(base::TimeTicks::Now(),
+                                       base::TimeDelta(), 0));
+  }
   buffers_.erase(it);
 
   connection_->ScheduleFlush();
@@ -139,20 +179,40 @@
 
 // TODO(msisov): handle buffer swap failure or success.
 bool WaylandBufferManager::SwapBuffer(Buffer* buffer) {
+  TRACE_EVENT1("Wayland", "WaylandBufferManager::SwapBuffer", "Buffer id",
+               buffer->buffer_id);
+
   WaylandWindow* window = connection_->GetWindow(buffer->widget);
   if (!window) {
     error_message_ = "A WaylandWindow with current widget does not exist";
     return false;
   }
 
-  // TODO(msisov): it would be beneficial to use real damage regions to improve
-  // performance.
-  //
-  // TODO(msisov): also start using wl_surface_frame callbacks for better
-  // performance.
-  wl_surface_damage(window->surface(), 0, 0, window->GetBounds().width(),
-                    window->GetBounds().height());
+  wl_surface_damage(window->surface(), buffer->damage_region.x(),
+                    buffer->damage_region.y(), buffer->damage_region.width(),
+                    buffer->damage_region.height());
   wl_surface_attach(window->surface(), buffer->wl_buffer.get(), 0, 0);
+
+  static const wl_callback_listener frame_listener = {
+      WaylandBufferManager::FrameCallbackDone};
+  DCHECK(!buffer->wl_frame_callback);
+  buffer->wl_frame_callback.reset(wl_surface_frame(window->surface()));
+  wl_callback_add_listener(buffer->wl_frame_callback.get(), &frame_listener,
+                           this);
+
+  // Set up presentation feedback.
+  static const wp_presentation_feedback_listener feedback_listener = {
+      WaylandBufferManager::FeedbackSyncOutput,
+      WaylandBufferManager::FeedbackPresented,
+      WaylandBufferManager::FeedbackDiscarded};
+  if (connection_->presentation()) {
+    DCHECK(!buffer->wp_presentation_feedback);
+    buffer->wp_presentation_feedback.reset(wp_presentation_feedback(
+        connection_->presentation(), window->surface()));
+    wp_presentation_feedback_add_listener(
+        buffer->wp_presentation_feedback.get(), &feedback_listener, this);
+  }
+
   wl_surface_commit(window->surface());
 
   connection_->ScheduleFlush();
@@ -250,7 +310,13 @@
   zwp_linux_buffer_params_v1_destroy(params);
 
   if (buffer->widget != gfx::kNullAcceleratedWidget)
-    ScheduleBufferSwap(buffer->widget, buffer->buffer_id);
+    SwapBuffer(buffer);
+}
+
+void WaylandBufferManager::OnBufferSwapped(Buffer* buffer) {
+  DCHECK(!buffer->buffer_swap_callback.is_null());
+  std::move(buffer->buffer_swap_callback)
+      .Run(buffer->swap_result, std::move(buffer->feedback));
 }
 
 // static
@@ -294,4 +360,95 @@
   LOG(FATAL) << "zwp_linux_buffer_params.create failed";
 }
 
+// static
+void WaylandBufferManager::FrameCallbackDone(void* data,
+                                             wl_callback* callback,
+                                             uint32_t time) {
+  WaylandBufferManager* self = static_cast<WaylandBufferManager*>(data);
+  DCHECK(self);
+  for (auto& item : self->buffers_) {
+    Buffer* buffer = item.second.get();
+    if (buffer->wl_frame_callback.get() == callback) {
+      buffer->swap_result = gfx::SwapResult::SWAP_ACK;
+      buffer->wl_frame_callback.reset();
+
+      // If presentation feedback is not supported, use fake feedback and
+      // trigger the callback.
+      if (!self->connection_->presentation()) {
+        buffer->feedback = gfx::PresentationFeedback(base::TimeTicks::Now(),
+                                                     base::TimeDelta(), 0);
+        self->OnBufferSwapped(buffer);
+      }
+      return;
+    }
+  }
+
+  NOTREACHED();
+}
+
+// static
+void WaylandBufferManager::FeedbackSyncOutput(
+    void* data,
+    struct wp_presentation_feedback* wp_presentation_feedback,
+    struct wl_output* output) {
+  NOTIMPLEMENTED_LOG_ONCE();
+}
+
+// static
+void WaylandBufferManager::FeedbackPresented(
+    void* data,
+    struct wp_presentation_feedback* wp_presentation_feedback,
+    uint32_t tv_sec_hi,
+    uint32_t tv_sec_lo,
+    uint32_t tv_nsec,
+    uint32_t refresh,
+    uint32_t seq_hi,
+    uint32_t seq_lo,
+    uint32_t flags) {
+  WaylandBufferManager* self = static_cast<WaylandBufferManager*>(data);
+  DCHECK(self);
+
+  for (auto& item : self->buffers_) {
+    Buffer* buffer = item.second.get();
+    if (buffer->wp_presentation_feedback.get() == wp_presentation_feedback) {
+      // Frame callback must come before a feedback is presented.
+      DCHECK(!buffer->wl_frame_callback);
+
+      buffer->feedback = gfx::PresentationFeedback(
+          GetPresentationFeedbackTimeStamp(tv_sec_hi, tv_sec_lo, tv_nsec),
+          base::TimeDelta::FromNanoseconds(refresh),
+          GetPresentationKindFlags(flags));
+
+      buffer->wp_presentation_feedback.reset();
+      self->OnBufferSwapped(buffer);
+      return;
+    }
+  }
+
+  NOTREACHED();
+}
+
+// static
+void WaylandBufferManager::FeedbackDiscarded(
+    void* data,
+    struct wp_presentation_feedback* wp_presentation_feedback) {
+  WaylandBufferManager* self = static_cast<WaylandBufferManager*>(data);
+  DCHECK(self);
+
+  for (auto& item : self->buffers_) {
+    Buffer* buffer = item.second.get();
+    if (buffer->wp_presentation_feedback.get() == wp_presentation_feedback) {
+      // Frame callback must come before a feedback is presented.
+      DCHECK(!buffer->wl_frame_callback);
+      buffer->feedback = gfx::PresentationFeedback::Failure();
+
+      buffer->wp_presentation_feedback.reset();
+      self->OnBufferSwapped(buffer);
+      return;
+    }
+  }
+
+  NOTREACHED();
+}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/wayland_buffer_manager.h b/ui/ozone/platform/wayland/wayland_buffer_manager.h
index d52d508..cf2d9720 100644
--- a/ui/ozone/platform/wayland/wayland_buffer_manager.h
+++ b/ui/ozone/platform/wayland/wayland_buffer_manager.h
@@ -11,11 +11,16 @@
 #include "base/containers/flat_map.h"
 #include "base/files/file.h"
 #include "base/macros.h"
+#include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/presentation_feedback.h"
+#include "ui/gfx/swap_result.h"
 #include "ui/ozone/platform/wayland/wayland_object.h"
+#include "ui/ozone/platform/wayland/wayland_util.h"
 
 struct zwp_linux_dmabuf_v1;
 struct zwp_linux_buffer_params_v1;
+struct wp_presentation_feedback;
 
 namespace gfx {
 enum class BufferFormat;
@@ -52,8 +57,13 @@
                     uint32_t buffer_id);
 
   // Assigns a wl_buffer with |buffer_id| to a window with the same |widget|. On
-  // error, false is returned and |error_message_| is set.
-  bool ScheduleBufferSwap(gfx::AcceleratedWidget widget, uint32_t buffer_id);
+  // error, false is returned and |error_message_| is set. A |damage_region|
+  // identifies which part of the buffer is updated. If an empty region is
+  // provided, the whole buffer is updated.
+  bool ScheduleBufferSwap(gfx::AcceleratedWidget widget,
+                          uint32_t buffer_id,
+                          const gfx::Rect& damage_region,
+                          wl::BufferSwapCallback callback);
 
   // Destroys a buffer with |buffer_id| in |buffers_|. On error, false is
   // returned and |error_message_| is set.
@@ -81,12 +91,37 @@
     // Widget to attached/being attach WaylandWindow.
     gfx::AcceleratedWidget widget = gfx::kNullAcceleratedWidget;
 
+    // Describes the region where the pending buffer is different from the
+    // current surface contents, and where the surface therefore needs to be
+    // repainted.
+    gfx::Rect damage_region;
+
+    // A buffer swap result once the buffer is committed.
+    gfx::SwapResult swap_result;
+
+    // A feedback, which is received if a presentation feedback protocol is
+    // supported.
+    gfx::PresentationFeedback feedback;
+
     // Params that are used to create a wl_buffer.
     zwp_linux_buffer_params_v1* params = nullptr;
 
     // A wl_buffer backed by a dmabuf created on the GPU side.
     wl::Object<wl_buffer> wl_buffer;
 
+    // A callback, which is called once the |wl_frame_callback| from the server
+    // is received.
+    wl::BufferSwapCallback buffer_swap_callback;
+
+    // A Wayland callback, which is triggered once wl_buffer has been committed
+    // and it is right time to notify the GPU that it can start a new drawing
+    // operation.
+    wl::Object<wl_callback> wl_frame_callback;
+
+    // A presentation feedback provided by the Wayland server once frame is
+    // shown.
+    wl::Object<wp_presentation_feedback> wp_presentation_feedback;
+
     DISALLOW_COPY_AND_ASSIGN(Buffer);
   };
 
@@ -109,6 +144,8 @@
   void CreateSucceededInternal(struct zwp_linux_buffer_params_v1* params,
                                struct wl_buffer* new_buffer);
 
+  void OnBufferSwapped(Buffer* buffer);
+
   // zwp_linux_dmabuf_v1_listener
   static void Modifiers(void* data,
                         struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf,
@@ -126,6 +163,30 @@
   static void CreateFailed(void* data,
                            struct zwp_linux_buffer_params_v1* params);
 
+  // wl_callback_listener
+  static void FrameCallbackDone(void* data,
+                                wl_callback* callback,
+                                uint32_t time);
+
+  // wp_presentation_feedback_listener
+  static void FeedbackSyncOutput(
+      void* data,
+      struct wp_presentation_feedback* wp_presentation_feedback,
+      struct wl_output* output);
+  static void FeedbackPresented(
+      void* data,
+      struct wp_presentation_feedback* wp_presentation_feedback,
+      uint32_t tv_sec_hi,
+      uint32_t tv_sec_lo,
+      uint32_t tv_nsec,
+      uint32_t refresh,
+      uint32_t seq_hi,
+      uint32_t seq_lo,
+      uint32_t flags);
+  static void FeedbackDiscarded(
+      void* data,
+      struct wp_presentation_feedback* wp_presentation_feedback);
+
   // Stores announced buffer formats supported by the compositor.
   std::vector<gfx::BufferFormat> supported_buffer_formats_;
 
diff --git a/ui/ozone/platform/wayland/wayland_connection.cc b/ui/ozone/platform/wayland/wayland_connection.cc
index 2f836c76..b0cd50e3 100644
--- a/ui/ozone/platform/wayland/wayland_connection.cc
+++ b/ui/ozone/platform/wayland/wayland_connection.cc
@@ -14,6 +14,7 @@
 #include "base/message_loop/message_loop_current.h"
 #include "base/strings/string_util.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "ui/gfx/swap_result.h"
 #include "ui/ozone/platform/wayland/wayland_buffer_manager.h"
 #include "ui/ozone/platform/wayland/wayland_object.h"
 #include "ui/ozone/platform/wayland/wayland_window.h"
@@ -23,11 +24,20 @@
 namespace ui {
 
 namespace {
-const uint32_t kMaxCompositorVersion = 4;
-const uint32_t kMaxLinuxDmabufVersion = 1;
-const uint32_t kMaxSeatVersion = 4;
-const uint32_t kMaxShmVersion = 1;
-const uint32_t kMaxXdgShellVersion = 1;
+constexpr uint32_t kMaxCompositorVersion = 4;
+constexpr uint32_t kMaxLinuxDmabufVersion = 1;
+constexpr uint32_t kMaxSeatVersion = 4;
+constexpr uint32_t kMaxShmVersion = 1;
+constexpr uint32_t kMaxXdgShellVersion = 1;
+constexpr uint32_t kMaxDeviceManagerVersion = 3;
+constexpr uint32_t kMaxWpPresentationVersion = 1;
+
+std::unique_ptr<WaylandDataSource> CreateWaylandDataSource(
+    WaylandDataDeviceManager* data_device_manager,
+    WaylandConnection* connection) {
+  wl_data_source* data_source = data_device_manager->CreateSource();
+  return std::make_unique<WaylandDataSource>(data_source, connection);
+}
 }  // namespace
 
 WaylandConnection::WaylandConnection()
@@ -174,10 +184,14 @@
   }
 }
 
-void WaylandConnection::ScheduleBufferSwap(gfx::AcceleratedWidget widget,
-                                           uint32_t buffer_id) {
+void WaylandConnection::ScheduleBufferSwap(
+    gfx::AcceleratedWidget widget,
+    uint32_t buffer_id,
+    const gfx::Rect& damage_region,
+    ScheduleBufferSwapCallback callback) {
   DCHECK(base::MessageLoopForUI::IsCurrent());
-  if (!buffer_manager_->ScheduleBufferSwap(widget, buffer_id)) {
+  if (!buffer_manager_->ScheduleBufferSwap(widget, buffer_id, damage_region,
+                                           std::move(callback))) {
     TerminateGpuProcess(buffer_manager_->error_message());
   }
 }
@@ -190,9 +204,7 @@
     const ClipboardDelegate::DataMap& data_map,
     ClipboardDelegate::OfferDataClosure callback) {
   if (!data_source_) {
-    wl_data_source* data_source = data_device_manager_->CreateSource();
-    data_source_.reset(new WaylandDataSource(data_source));
-    data_source_->set_connection(this);
+    data_source_ = CreateWaylandDataSource(data_device_manager_.get(), this);
     data_source_->WriteToClipboard(data_map);
   }
   data_source_->UpdataDataMap(data_map);
@@ -234,6 +246,36 @@
   terminate_gpu_cb_ = std::move(terminate_callback);
 }
 
+void WaylandConnection::StartDrag(const ui::OSExchangeData& data,
+                                  int operation) {
+  if (!drag_data_source_) {
+    drag_data_source_ =
+        CreateWaylandDataSource(data_device_manager_.get(), this);
+  }
+  drag_data_source_->Offer(data);
+  drag_data_source_->SetAction(operation);
+  data_device_->StartDrag(*(drag_data_source_->data_source()), data);
+}
+
+void WaylandConnection::FinishDragSession(uint32_t dnd_action,
+                                          WaylandWindow* source_window) {
+  if (source_window)
+    source_window->OnDragSessionClose(dnd_action);
+  data_device_->ResetSourceData();
+  drag_data_source_.reset();
+}
+
+void WaylandConnection::DeliverDragData(const std::string& mime_type,
+                                        std::string* buffer) {
+  data_device_->DeliverDragData(mime_type, buffer);
+}
+
+void WaylandConnection::RequestDragData(
+    const std::string& mime_type,
+    base::OnceCallback<void(const std::string&)> callback) {
+  data_device_->RequestDragData(mime_type, std::move(callback));
+}
+
 void WaylandConnection::GetAvailableMimeTypes(
     ClipboardDelegate::GetMimeTypesClosure callback) {
   std::move(callback).Run(data_device_->GetAvailableMimeTypes());
@@ -383,7 +425,8 @@
   } else if (!connection->data_device_manager_ &&
              strcmp(interface, "wl_data_device_manager") == 0) {
     wl::Object<wl_data_device_manager> data_device_manager =
-        wl::Bind<wl_data_device_manager>(registry, name, 1);
+        wl::Bind<wl_data_device_manager>(
+            registry, name, std::min(version, kMaxDeviceManagerVersion));
     if (!data_device_manager) {
       LOG(ERROR) << "Failed to bind to wl_data_device_manager global";
       return;
@@ -398,6 +441,10 @@
             registry, name, std::min(version, kMaxLinuxDmabufVersion));
     connection->buffer_manager_.reset(
         new WaylandBufferManager(zwp_linux_dmabuf.release(), connection));
+  } else if (!connection->presentation_ &&
+             (strcmp(interface, "wp_presentation") == 0)) {
+    connection->presentation_ =
+        wl::Bind<wp_presentation>(registry, name, kMaxWpPresentationVersion);
   }
 
   connection->ScheduleFlush();
diff --git a/ui/ozone/platform/wayland/wayland_connection.h b/ui/ozone/platform/wayland/wayland_connection.h
index 521f957..961bf6fa5 100644
--- a/ui/ozone/platform/wayland/wayland_connection.h
+++ b/ui/ozone/platform/wayland/wayland_connection.h
@@ -60,7 +60,9 @@
   // Called by the GPU and asks to attach a wl_buffer with a |buffer_id| to a
   // WaylandWindow with the specified |widget|.
   void ScheduleBufferSwap(gfx::AcceleratedWidget widget,
-                          uint32_t buffer_id) override;
+                          uint32_t buffer_id,
+                          const gfx::Rect& damage_region,
+                          ScheduleBufferSwapCallback callback) override;
 
   // Schedules a flush of the Wayland connection.
   void ScheduleFlush();
@@ -73,6 +75,7 @@
   zxdg_shell_v6* shell_v6() { return shell_v6_.get(); }
   wl_seat* seat() { return seat_.get(); }
   wl_data_device* data_device() { return data_device_->data_device(); }
+  wp_presentation* presentation() const { return presentation_.get(); }
 
   WaylandWindow* GetWindow(gfx::AcceleratedWidget widget);
   WaylandWindow* GetCurrentFocusedWindow();
@@ -94,6 +97,8 @@
   // Returns the current pointer, which may be null.
   WaylandPointer* pointer() { return pointer_.get(); }
 
+  WaylandDataSource* drag_data_source() { return drag_data_source_.get(); }
+
   // Clipboard implementation.
   ClipboardDelegate* GetClipboardDelegate();
   void DataSourceCancelled();
@@ -120,6 +125,23 @@
   void SetTerminateGpuCallback(
       base::OnceCallback<void(std::string)> terminate_gpu_cb);
 
+  // Starts drag with |data| to be delivered, |operation| supported by the
+  // source side initiated the dragging.
+  void StartDrag(const ui::OSExchangeData& data, int operation);
+  // Finishes drag and drop session. It happens when WaylandDataSource gets
+  // 'OnDnDFinished' or 'OnCancel', which means the drop is performed or
+  // canceled on others.
+  void FinishDragSession(uint32_t dnd_action, WaylandWindow* source_window);
+  // Delivers the data owned by Chromium which initiates drag-and-drop. |buffer|
+  // is an output parameter and it should be filled with the data corresponding
+  // to mime_type.
+  void DeliverDragData(const std::string& mime_type, std::string* buffer);
+  // Requests the data to the platform when Chromium gets drag-and-drop started
+  // by others. Once reading the data from platform is done, |callback| should
+  // be called with the data.
+  void RequestDragData(const std::string& mime_type,
+                       base::OnceCallback<void(const std::string&)> callback);
+
  private:
   void Flush();
   void DispatchUiEvent(Event* event);
@@ -162,10 +184,12 @@
   wl::Object<wl_shm> shm_;
   wl::Object<xdg_shell> shell_;
   wl::Object<zxdg_shell_v6> shell_v6_;
+  wl::Object<wp_presentation> presentation_;
 
   std::unique_ptr<WaylandDataDeviceManager> data_device_manager_;
   std::unique_ptr<WaylandDataDevice> data_device_;
   std::unique_ptr<WaylandDataSource> data_source_;
+  std::unique_ptr<WaylandDataSource> drag_data_source_;
   std::unique_ptr<WaylandPointer> pointer_;
   std::unique_ptr<WaylandKeyboard> keyboard_;
   std::unique_ptr<WaylandTouch> touch_;
diff --git a/ui/ozone/platform/wayland/wayland_data_device.cc b/ui/ozone/platform/wayland/wayland_data_device.cc
index f0f144c..0677426 100644
--- a/ui/ozone/platform/wayland/wayland_data_device.cc
+++ b/ui/ozone/platform/wayland/wayland_data_device.cc
@@ -5,10 +5,67 @@
 #include "ui/ozone/platform/wayland/wayland_data_device.h"
 
 #include "base/bind.h"
+#include "base/memory/shared_memory.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/base/dragdrop/os_exchange_data_provider_aura.h"
 #include "ui/ozone/platform/wayland/wayland_connection.h"
+#include "ui/ozone/platform/wayland/wayland_util.h"
+#include "ui/ozone/platform/wayland/wayland_window.h"
 
 namespace ui {
 
+namespace {
+
+constexpr char kMimeTypeText[] = "text/plain";
+constexpr char kMimeTypeTextUTF8[] = "text/plain;charset=utf-8";
+
+int GetOperation(uint32_t source_actions, uint32_t dnd_action) {
+  uint32_t action = dnd_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE
+                        ? dnd_action
+                        : source_actions;
+
+  int operation = DragDropTypes::DRAG_NONE;
+  if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
+    operation |= DragDropTypes::DRAG_COPY;
+  if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
+    operation |= DragDropTypes::DRAG_MOVE;
+  // TODO(jkim): Implement branch for WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK
+  if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)
+    operation |= DragDropTypes::DRAG_COPY;
+  return operation;
+}
+
+void AddStringToOSExchangeData(const std::string& data,
+                               OSExchangeData* os_exchange_data) {
+  DCHECK(os_exchange_data);
+  if (data.empty())
+    return;
+
+  base::string16 string16 = base::UTF8ToUTF16(data);
+  os_exchange_data->SetString(string16);
+}
+
+void AddToOSExchangeData(const std::string& data,
+                         const std::string& mime_type,
+                         OSExchangeData* os_exchange_data) {
+  DCHECK(os_exchange_data);
+  if ((mime_type == kMimeTypeText || mime_type == kMimeTypeTextUTF8)) {
+    DCHECK(!os_exchange_data->HasString());
+    AddStringToOSExchangeData(data, os_exchange_data);
+    return;
+  }
+
+  // TODO(jkim): Handle other mime types as well.
+  NOTREACHED();
+}
+
+}  // namespace
+
 // static
 const wl_callback_listener WaylandDataDevice::callback_listener_ = {
     WaylandDataDevice::SyncCallback,
@@ -16,18 +73,22 @@
 
 WaylandDataDevice::WaylandDataDevice(WaylandConnection* connection,
                                      wl_data_device* data_device)
-    : data_device_(data_device), connection_(connection) {
+    : data_device_(data_device),
+      connection_(connection),
+      shared_memory_(new base::SharedMemory()) {
   static const struct wl_data_device_listener kDataDeviceListener = {
-      WaylandDataDevice::OnDataOffer,
-      nullptr /*OnEnter*/,
-      nullptr /*OnLeave*/,
-      nullptr /*OnMotion*/,
-      nullptr /*OnDrop*/,
-      WaylandDataDevice::OnSelection};
+      WaylandDataDevice::OnDataOffer, WaylandDataDevice::OnEnter,
+      WaylandDataDevice::OnLeave,     WaylandDataDevice::OnMotion,
+      WaylandDataDevice::OnDrop,      WaylandDataDevice::OnSelection};
   wl_data_device_add_listener(data_device_.get(), &kDataDeviceListener, this);
 }
 
-WaylandDataDevice::~WaylandDataDevice() {}
+WaylandDataDevice::~WaylandDataDevice() {
+  if (!shared_memory_->handle().GetHandle())
+    return;
+  shared_memory_->Unmap();
+  shared_memory_->Close();
+}
 
 void WaylandDataDevice::RequestSelectionData(const std::string& mime_type) {
   base::ScopedFD fd = selection_offer_->Receive(mime_type);
@@ -44,6 +105,81 @@
   RegisterSyncCallback();
 }
 
+void WaylandDataDevice::RequestDragData(
+    const std::string& mime_type,
+    base::OnceCallback<void(const std::string&)> callback) {
+  base::ScopedFD fd = drag_offer_->Receive(mime_type);
+  if (!fd.is_valid()) {
+    LOG(ERROR) << "Failed to open file descriptor.";
+    return;
+  }
+
+  // Ensure there is not pending operation to be performed by the compositor,
+  // otherwise read(..) can block awaiting data to be sent to pipe.
+  read_from_fd_closure_ = base::BindOnce(&WaylandDataDevice::ReadDragDataFromFD,
+                                         base::Unretained(this), std::move(fd),
+                                         std::move(callback));
+  RegisterSyncCallback();
+}
+
+void WaylandDataDevice::DeliverDragData(const std::string& mime_type,
+                                        std::string* buffer) {
+  DCHECK(buffer);
+  DCHECK(source_data_);
+
+  if (mime_type != kMimeTypeText && mime_type != kMimeTypeTextUTF8)
+    return;
+
+  const OSExchangeData::FilenameToURLPolicy policy =
+      OSExchangeData::FilenameToURLPolicy::DO_NOT_CONVERT_FILENAMES;
+  // TODO(jkim): Handle other data format as well.
+  if (source_data_->HasURL(policy)) {
+    GURL url;
+    base::string16 title;
+    source_data_->GetURLAndTitle(policy, &url, &title);
+    buffer->append(url.spec());
+    return;
+  }
+
+  if (source_data_->HasString()) {
+    base::string16 data;
+    source_data_->GetString(&data);
+    buffer->append(base::UTF16ToUTF8(data));
+    return;
+  }
+}
+
+void WaylandDataDevice::StartDrag(const wl_data_source& data_source,
+                                  const ui::OSExchangeData& data) {
+  WaylandWindow* window = connection_->GetCurrentFocusedWindow();
+  if (!window) {
+    LOG(ERROR) << "Failed to get focused window.";
+    return;
+  }
+
+  wl_surface* surface = window->surface();
+  const SkBitmap* icon = data.provider().GetDragImage().bitmap();
+  if (icon && !icon->empty())
+    CreateDragImage(icon);
+
+  source_data_ = std::make_unique<ui::OSExchangeData>(data.provider().Clone());
+  wl_data_device_start_drag(data_device_.get(),
+                            const_cast<wl_data_source*>(&data_source), surface,
+                            icon_surface_.get(), connection_->serial());
+  connection_->ScheduleFlush();
+}
+
+void WaylandDataDevice::ResetSourceData() {
+  source_data_.reset();
+}
+
+std::vector<std::string> WaylandDataDevice::GetAvailableMimeTypes() {
+  if (selection_offer_)
+    return selection_offer_->GetAvailableMimeTypes();
+
+  return std::vector<std::string>();
+}
+
 void WaylandDataDevice::ReadClipboardDataFromFD(base::ScopedFD fd,
                                                 const std::string& mime_type) {
   std::string contents;
@@ -51,6 +187,14 @@
   connection_->SetClipboardData(contents, mime_type);
 }
 
+void WaylandDataDevice::ReadDragDataFromFD(
+    base::ScopedFD fd,
+    base::OnceCallback<void(const std::string&)> callback) {
+  std::string contents;
+  ReadDataFromFD(std::move(fd), &contents);
+  std::move(callback).Run(contents);
+}
+
 void WaylandDataDevice::RegisterSyncCallback() {
   DCHECK(!sync_callback_);
   sync_callback_.reset(wl_display_sync(connection_->display()));
@@ -67,11 +211,11 @@
     contents->append(buffer, length);
 }
 
-std::vector<std::string> WaylandDataDevice::GetAvailableMimeTypes() {
-  if (selection_offer_)
-    return selection_offer_->GetAvailableMimeTypes();
+void WaylandDataDevice::HandleDeferredLeaveIfNeeded() {
+  if (!is_leaving_)
+    return;
 
-  return std::vector<std::string>();
+  OnLeave(this, data_device_.get());
 }
 
 // static
@@ -84,6 +228,116 @@
   self->new_offer_.reset(new WaylandDataOffer(offer));
 }
 
+void WaylandDataDevice::OnEnter(void* data,
+                                wl_data_device* data_device,
+                                uint32_t serial,
+                                wl_surface* surface,
+                                wl_fixed_t x,
+                                wl_fixed_t y,
+                                wl_data_offer* offer) {
+  WaylandWindow* window =
+      static_cast<WaylandWindow*>(wl_surface_get_user_data(surface));
+  if (!window) {
+    LOG(ERROR) << "Failed to get window.";
+    return;
+  }
+
+  auto* self = static_cast<WaylandDataDevice*>(data);
+  DCHECK(self->new_offer_);
+  DCHECK(!self->drag_offer_);
+  self->drag_offer_ = std::move(self->new_offer_);
+  self->window_ = window;
+
+  // TODO(jkim): Set mime type the client can accept. Now it sets all mime types
+  // offered because current implementation doesn't decide action based on mime
+  // type.
+  const std::vector<std::string>& mime_types =
+      self->drag_offer_->GetAvailableMimeTypes();
+  for (auto mime : mime_types)
+    self->drag_offer_->Accept(serial, mime);
+
+  std::copy(mime_types.begin(), mime_types.end(),
+            std::insert_iterator<std::list<std::string>>(
+                self->unprocessed_mime_types_,
+                self->unprocessed_mime_types_.begin()));
+
+  int operation = GetOperation(self->drag_offer_->source_actions(),
+                               self->drag_offer_->dnd_action());
+  gfx::PointF point(wl_fixed_to_double(x), wl_fixed_to_double(y));
+
+  // If it has |source_data_|, it means that the dragging is started from the
+  // same window and it doesn't need to read the data through Wayland.
+  if (self->source_data_) {
+    std::unique_ptr<OSExchangeData> data = std::make_unique<OSExchangeData>(
+        self->source_data_->provider().Clone());
+    self->window_->OnDragEnter(point, std::move(data), operation);
+    return;
+  }
+
+  self->window_->OnDragEnter(point, nullptr, operation);
+}
+
+void WaylandDataDevice::OnMotion(void* data,
+                                 wl_data_device* data_device,
+                                 uint32_t time,
+                                 wl_fixed_t x,
+                                 wl_fixed_t y) {
+  auto* self = static_cast<WaylandDataDevice*>(data);
+  if (!self->window_) {
+    LOG(ERROR) << "Failed to get window.";
+    return;
+  }
+
+  int operation = GetOperation(self->drag_offer_->source_actions(),
+                               self->drag_offer_->dnd_action());
+  gfx::PointF point(wl_fixed_to_double(x), wl_fixed_to_double(y));
+  int client_operation = self->window_->OnDragMotion(point, time, operation);
+  self->SetOperation(client_operation);
+}
+
+void WaylandDataDevice::OnDrop(void* data, wl_data_device* data_device) {
+  auto* self = static_cast<WaylandDataDevice*>(data);
+  if (!self->window_) {
+    LOG(ERROR) << "Failed to get window.";
+    return;
+  }
+
+  // Creates buffer to receive data from Wayland.
+  self->received_data_ = std::make_unique<OSExchangeData>(
+      std::make_unique<OSExchangeDataProviderAura>());
+
+  // Starts to read the data on Drop event because read(..) API blocks
+  // awaiting data to be sent to pipe if we try to read the data on OnEnter.
+  // 'Weston' also reads data on OnDrop event and other examples do as well.
+  self->HandleNextMimeType();
+
+  // In order to guarantee all data received, it sets
+  // |is_handling_dropped_data_| and defers OnLeave event handling if it gets
+  // OnLeave event before completing to read the data.
+  self->is_handling_dropped_data_ = true;
+}
+
+void WaylandDataDevice::OnLeave(void* data, wl_data_device* data_device) {
+  // While reading data, it could get OnLeave event. We don't handle OnLeave
+  // event directly if |is_handling_dropped_data_| is set.
+  auto* self = static_cast<WaylandDataDevice*>(data);
+  if (!self->window_) {
+    LOG(ERROR) << "Failed to get window.";
+    return;
+  }
+
+  if (self->is_handling_dropped_data_) {
+    self->is_leaving_ = true;
+    return;
+  }
+
+  self->window_->OnDragLeave();
+  self->window_ = nullptr;
+  self->drag_offer_.reset();
+  self->is_handling_dropped_data_ = false;
+  self->is_leaving_ = false;
+}
+
 // static
 void WaylandDataDevice::OnSelection(void* data,
                                     wl_data_device* data_device,
@@ -118,4 +372,88 @@
   data_device->sync_callback_.reset();
 }
 
+void WaylandDataDevice::CreateDragImage(const SkBitmap* bitmap) {
+  DCHECK(bitmap);
+  gfx::Size size(bitmap->width(), bitmap->height());
+
+  if (size != icon_buffer_size_) {
+    wl_buffer* buffer =
+        wl::CreateSHMBuffer(size, shared_memory_.get(), connection_->shm());
+    if (!buffer)
+      return;
+
+    buffer_.reset(buffer);
+    icon_buffer_size_ = size;
+  }
+  wl::DrawBitmapToSHMB(icon_buffer_size_, *shared_memory_, *bitmap);
+
+  icon_surface_.reset(wl_compositor_create_surface(connection_->compositor()));
+  wl_surface_attach(icon_surface_.get(), buffer_.get(), 0, 0);
+  wl_surface_damage(icon_surface_.get(), 0, 0, icon_buffer_size_.width(),
+                    icon_buffer_size_.height());
+  wl_surface_commit(icon_surface_.get());
+}
+
+void WaylandDataDevice::OnDragDataReceived(const std::string& contents) {
+  if (!contents.empty()) {
+    AddToOSExchangeData(contents, unprocessed_mime_types_.front(),
+                        received_data_.get());
+  }
+
+  unprocessed_mime_types_.erase(unprocessed_mime_types_.begin());
+
+  // Read next data corresponding to the mime type.
+  HandleNextMimeType();
+}
+
+void WaylandDataDevice::OnDragDataCollected() {
+  unprocessed_mime_types_.clear();
+  window_->OnDragDrop(std::move(received_data_));
+  drag_offer_->FinishOffer();
+  is_handling_dropped_data_ = false;
+
+  HandleDeferredLeaveIfNeeded();
+}
+
+std::string WaylandDataDevice::SelectNextMimeType() {
+  while (!unprocessed_mime_types_.empty()) {
+    std::string& mime_type = unprocessed_mime_types_.front();
+    if ((mime_type == kMimeTypeText || mime_type == kMimeTypeTextUTF8) &&
+        !received_data_->HasString()) {
+      return mime_type;
+    }
+    // TODO(jkim): Handle other mime types as well.
+    unprocessed_mime_types_.erase(unprocessed_mime_types_.begin());
+  }
+  return std::string();
+}
+
+void WaylandDataDevice::HandleNextMimeType() {
+  std::string mime_type = SelectNextMimeType();
+  if (!mime_type.empty()) {
+    RequestDragData(mime_type,
+                    base::BindOnce(&WaylandDataDevice::OnDragDataReceived,
+                                   base::Unretained(this)));
+  } else {
+    OnDragDataCollected();
+  }
+}
+
+void WaylandDataDevice::SetOperation(const int operation) {
+  uint32_t dnd_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
+  uint32_t preferred_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
+
+  if (operation & DragDropTypes::DRAG_COPY) {
+    dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+    preferred_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+  }
+
+  if (operation & DragDropTypes::DRAG_MOVE) {
+    dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
+    if (preferred_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE)
+      preferred_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
+  }
+  drag_offer_->SetAction(dnd_actions, preferred_action);
+}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/wayland_data_device.h b/ui/ozone/platform/wayland/wayland_data_device.h
index 3d1eecd..f098670 100644
--- a/ui/ozone/platform/wayland/wayland_data_device.h
+++ b/ui/ozone/platform/wayland/wayland_data_device.h
@@ -6,23 +6,31 @@
 #define UI_OZONE_PLATFORM_WAYLAND_WAYLAND_DATA_DEVICE_H_
 
 #include <wayland-client.h>
+#include <list>
 #include <string>
 
 #include "base/callback.h"
 #include "base/files/scoped_file.h"
 #include "base/macros.h"
+#include "ui/gfx/geometry/size.h"
 #include "ui/ozone/platform/wayland/wayland_data_offer.h"
 #include "ui/ozone/platform/wayland/wayland_object.h"
 
+class SkBitmap;
+
+namespace base {
+class SharedMemory;
+}
+
 namespace ui {
 
+class OSExchangeData;
 class WaylandDataOffer;
 class WaylandConnection;
+class WaylandWindow;
 
 // This class provides access to inter-client data transfer mechanisms
 // such as copy-and-paste and drag-and-drop mechanisms.
-//
-// TODO(tonikitoo,msisov): Add drag&drop support.
 class WaylandDataDevice {
  public:
   WaylandDataDevice(WaylandConnection* connection, wl_data_device* data_device);
@@ -30,23 +38,66 @@
 
   void RequestSelectionData(const std::string& mime_type);
 
-  wl_data_device* data_device() { return data_device_.get(); }
+  // Requests the data to the platform when Chromium gets drag-and-drop started
+  // by others. Once reading the data from platform is done, |callback| should
+  // be called with the data.
+  void RequestDragData(const std::string& mime_type,
+                       base::OnceCallback<void(const std::string&)> callback);
+  // Delivers the data owned by Chromium which initiates drag-and-drop. |buffer|
+  // is an output parameter and it should be filled with the data corresponding
+  // to mime_type.
+  void DeliverDragData(const std::string& mime_type, std::string* buffer);
+  // Starts drag with |data| to be delivered, |operation| supported by the
+  // source side initiated the dragging.
+  void StartDrag(const wl_data_source& data_source,
+                 const ui::OSExchangeData& data);
+  // Resets |source_data_| when the dragging is finished.
+  void ResetSourceData();
 
   std::vector<std::string> GetAvailableMimeTypes();
 
+  wl_data_device* data_device() const { return data_device_.get(); }
+
  private:
   void ReadClipboardDataFromFD(base::ScopedFD fd, const std::string& mime_type);
 
+  void ReadDragDataFromFD(
+      base::ScopedFD fd,
+      base::OnceCallback<void(const std::string&)> callback);
+
   // Registers display sync callback. Once it's called, it's reset.
   void RegisterSyncCallback();
 
   // Helper function to read data from fd.
   void ReadDataFromFD(base::ScopedFD fd, std::string* contents);
 
+  // If OnLeave event occurs while it's reading drag data, it defers handling
+  // it. Once reading data is completed, it's handled.
+  void HandleDeferredLeaveIfNeeded();
+
   // wl_data_device_listener callbacks
   static void OnDataOffer(void* data,
                           wl_data_device* data_device,
                           wl_data_offer* id);
+
+  static void OnEnter(void* data,
+                      wl_data_device* data_device,
+                      uint32_t serial,
+                      wl_surface* surface,
+                      wl_fixed_t x,
+                      wl_fixed_t y,
+                      wl_data_offer* offer);
+
+  static void OnMotion(void* data,
+                       struct wl_data_device* data_device,
+                       uint32_t time,
+                       wl_fixed_t x,
+                       wl_fixed_t y);
+
+  static void OnDrop(void* data, struct wl_data_device* data_device);
+
+  static void OnLeave(void* data, struct wl_data_device* data_device);
+
   // Called by the compositor when the window gets pointer or keyboard focus,
   // or clipboard content changes behind the scenes.
   //
@@ -57,6 +108,22 @@
 
   static void SyncCallback(void* data, struct wl_callback* cb, uint32_t time);
 
+  bool CreateSHMBuffer(const gfx::Size& size);
+  void CreateDragImage(const SkBitmap* bitmap);
+
+  void OnDragDataReceived(const std::string& contents);
+  void OnDragDataCollected();
+
+  // Returns the next MIME type to be received from the source process, or an
+  // empty string if there are no more interesting MIME types left to process.
+  std::string SelectNextMimeType();
+  // If it has |unprocessed_mime_types_|, it takes the mime type in front and
+  // requests the data corresponding to the mime type to wayland.
+  void HandleNextMimeType();
+
+  // Set drag operation decided by client.
+  void SetOperation(const int operation);
+
   // The wl_data_device wrapped by this WaylandDataDevice.
   wl::Object<wl_data_device> data_device_;
 
@@ -75,11 +142,36 @@
   // clipboard data is available.
   std::unique_ptr<WaylandDataOffer> selection_offer_;
 
+  // Offer to receive data from another process via drag-and-drop, or null if no
+  // drag-and-drop from another process is in progress.
+  std::unique_ptr<WaylandDataOffer> drag_offer_;
+
+  WaylandWindow* window_ = nullptr;
+
   // Make sure server has written data on the pipe, before block on read().
   static const wl_callback_listener callback_listener_;
   base::OnceClosure read_from_fd_closure_;
   wl::Object<wl_callback> sync_callback_;
 
+  bool is_handling_dropped_data_ = false;
+  bool is_leaving_ = false;
+
+  std::unique_ptr<base::SharedMemory> shared_memory_;
+
+  wl::Object<wl_buffer> buffer_;
+  wl::Object<wl_surface> icon_surface_;
+  gfx::Size icon_buffer_size_;
+
+  // Mime types to be handled.
+  std::list<std::string> unprocessed_mime_types_;
+
+  // The data delivered from Wayland
+  std::unique_ptr<ui::OSExchangeData> received_data_;
+
+  // When dragging is started from Chromium, |source_data_| is forwarded to
+  // Wayland when they are ready to get the data.
+  std::unique_ptr<ui::OSExchangeData> source_data_;
+
   DISALLOW_COPY_AND_ASSIGN(WaylandDataDevice);
 };
 
diff --git a/ui/ozone/platform/wayland/wayland_data_device_unittest.cc b/ui/ozone/platform/wayland/wayland_data_device_unittest.cc
index 03c567e..f8fe5ab 100644
--- a/ui/ozone/platform/wayland/wayland_data_device_unittest.cc
+++ b/ui/ozone/platform/wayland/wayland_data_device_unittest.cc
@@ -5,6 +5,9 @@
 #include <wayland-server.h>
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/events/base_event_utils.h"
 #include "ui/ozone/platform/wayland/fake_server.h"
 #include "ui/ozone/platform/wayland/wayland_test.h"
 #include "ui/ozone/public/clipboard_delegate.h"
@@ -131,6 +134,63 @@
   ASSERT_FALSE(clipboard_client_->IsSelectionOwner());
 }
 
+TEST_P(WaylandDataDeviceManagerTest, StartDrag) {
+  bool restored_focus = window_->has_pointer_focus();
+  window_->set_pointer_focus(true);
+
+  // The client starts dragging.
+  std::unique_ptr<OSExchangeData> os_exchange_data =
+      std::make_unique<OSExchangeData>();
+  int operation = DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE;
+  connection_->StartDrag(*os_exchange_data, operation);
+
+  WaylandDataSource::DragDataMap data;
+  data[wl::kTextMimeTypeText] = wl::kSampleTextForDragAndDrop;
+  connection_->drag_data_source()->SetDragData(data);
+
+  Sync();
+  // The server reads the data and the callback gets it.
+  data_device_manager_->data_source()->ReadData(
+      base::BindOnce([](const std::vector<uint8_t>& data) {
+        std::string string_data(data.begin(), data.end());
+        EXPECT_EQ(wl::kSampleTextForDragAndDrop, string_data);
+      }));
+
+  window_->set_pointer_focus(restored_focus);
+}
+
+TEST_P(WaylandDataDeviceManagerTest, ReceiveDrag) {
+  auto* data_offer = data_device_manager_->data_device()->OnDataOffer();
+  data_offer->OnOffer(wl::kTextMimeTypeText);
+
+  gfx::Point entered_point(10, 10);
+  // The server sends an enter event.
+  data_device_manager_->data_device()->OnEnter(
+      1002, surface_->resource(), wl_fixed_from_int(entered_point.x()),
+      wl_fixed_from_int(entered_point.y()), *data_offer);
+
+  int64_t time =
+      (ui::EventTimeForNow() - base::TimeTicks()).InMilliseconds() & UINT32_MAX;
+  gfx::Point motion_point(11, 11);
+
+  // The server sends an motion event.
+  data_device_manager_->data_device()->OnMotion(
+      time, wl_fixed_from_int(motion_point.x()),
+      wl_fixed_from_int(motion_point.y()));
+
+  Sync();
+
+  auto callback = base::BindOnce([](const std::string& contents) {
+    EXPECT_EQ(wl::kSampleTextForDragAndDrop, contents);
+  });
+
+  // The client requests the data and gets callback with it.
+  connection_->RequestDragData(wl::kTextMimeTypeText, std::move(callback));
+  Sync();
+
+  data_device_manager_->data_device()->OnLeave();
+}
+
 INSTANTIATE_TEST_CASE_P(XdgVersionV5Test,
                         WaylandDataDeviceManagerTest,
                         ::testing::Values(kXdgShellV5));
diff --git a/ui/ozone/platform/wayland/wayland_data_offer.cc b/ui/ozone/platform/wayland/wayland_data_offer.cc
index e68554ef..a3900dd 100644
--- a/ui/ozone/platform/wayland/wayland_data_offer.cc
+++ b/ui/ozone/platform/wayland/wayland_data_offer.cc
@@ -30,9 +30,12 @@
 }  // namespace
 
 WaylandDataOffer::WaylandDataOffer(wl_data_offer* data_offer)
-    : data_offer_(data_offer) {
+    : data_offer_(data_offer),
+      source_actions_(WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE),
+      dnd_action_(WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) {
   static const struct wl_data_offer_listener kDataOfferListener = {
-      WaylandDataOffer::OnOffer};
+      WaylandDataOffer::OnOffer, WaylandDataOffer::OnSourceAction,
+      WaylandDataOffer::OnAction};
   wl_data_offer_add_listener(data_offer, &kDataOfferListener, this);
 }
 
@@ -40,6 +43,23 @@
   data_offer_.reset();
 }
 
+void WaylandDataOffer::SetAction(uint32_t dnd_actions,
+                                 uint32_t preferred_action) {
+  if (wl_data_offer_get_version(data_offer_.get()) >=
+      WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION) {
+    wl_data_offer_set_actions(data_offer_.get(), dnd_actions, preferred_action);
+  }
+}
+
+void WaylandDataOffer::Accept(uint32_t serial, const std::string& mime_type) {
+  wl_data_offer_accept(data_offer_.get(), serial, mime_type.c_str());
+}
+
+void WaylandDataOffer::Reject(uint32_t serial) {
+  // Passing a null MIME type means "reject."
+  wl_data_offer_accept(data_offer_.get(), serial, nullptr);
+}
+
 void WaylandDataOffer::EnsureTextMimeTypeIfNeeded() {
   if (base::ContainsValue(mime_types_, kTextPlain))
     return;
@@ -76,6 +96,20 @@
   return read_fd;
 }
 
+void WaylandDataOffer::FinishOffer() {
+  if (wl_data_offer_get_version(data_offer_.get()) >=
+      WL_DATA_OFFER_FINISH_SINCE_VERSION)
+    wl_data_offer_finish(data_offer_.get());
+}
+
+uint32_t WaylandDataOffer::source_actions() const {
+  return source_actions_;
+}
+
+uint32_t WaylandDataOffer::dnd_action() const {
+  return dnd_action_;
+}
+
 // static
 void WaylandDataOffer::OnOffer(void* data,
                                wl_data_offer* data_offer,
@@ -84,4 +118,18 @@
   self->mime_types_.push_back(mime_type);
 }
 
+void WaylandDataOffer::OnSourceAction(void* data,
+                                      wl_data_offer* offer,
+                                      uint32_t source_actions) {
+  auto* self = static_cast<WaylandDataOffer*>(data);
+  self->source_actions_ = source_actions;
+}
+
+void WaylandDataOffer::OnAction(void* data,
+                                wl_data_offer* offer,
+                                uint32_t dnd_action) {
+  auto* self = static_cast<WaylandDataOffer*>(data);
+  self->dnd_action_ = dnd_action;
+}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/wayland_data_offer.h b/ui/ozone/platform/wayland/wayland_data_offer.h
index 3b9e06f..a2e8170 100644
--- a/ui/ozone/platform/wayland/wayland_data_offer.h
+++ b/ui/ozone/platform/wayland/wayland_data_offer.h
@@ -23,8 +23,6 @@
 // The offer describes the different mime types that the data can be
 // converted to and provides the mechanism for transferring the data
 // directly from the source client.
-//
-// TODO(tonikitoo,msisov): Add drag&drop support.
 class WaylandDataOffer {
  public:
   // Takes ownership of data_offer.
@@ -42,20 +40,36 @@
   // list of provided mime types so that Chrome clipboard's machinery
   // works fine.
   void EnsureTextMimeTypeIfNeeded();
+  void SetAction(uint32_t dnd_actions, uint32_t preferred_action);
+  void Accept(uint32_t serial, const std::string& mime_type);
+  void Reject(uint32_t serial);
 
   // Creates a pipe (read & write FDs), passing the write-end of to pipe
   // to the compositor (via wl_data_offer_receive) and returning the
   // read-end to the pipe.
   base::ScopedFD Receive(const std::string& mime_type);
+  void FinishOffer();
+  uint32_t source_actions() const;
+  uint32_t dnd_action() const;
 
  private:
   // wl_data_offer_listener callbacks.
   static void OnOffer(void* data,
                       wl_data_offer* data_offer,
                       const char* mime_type);
+  // Notifies the source-side available actions
+  static void OnSourceAction(void* data,
+                             wl_data_offer* offer,
+                             uint32_t source_actions);
+  // Notifies the selected action
+  static void OnAction(void* data, wl_data_offer* offer, uint32_t dnd_action);
 
   wl::Object<wl_data_offer> data_offer_;
   std::vector<std::string> mime_types_;
+  // Actions offered by the data source
+  uint32_t source_actions_;
+  // Action selected by the compositor
+  uint32_t dnd_action_;
 
   bool text_plain_mime_type_inserted_ = false;
 
diff --git a/ui/ozone/platform/wayland/wayland_data_source.cc b/ui/ozone/platform/wayland/wayland_data_source.cc
index 7b61ad2..e1fe23b6 100644
--- a/ui/ozone/platform/wayland/wayland_data_source.cc
+++ b/ui/ozone/platform/wayland/wayland_data_source.cc
@@ -5,17 +5,22 @@
 #include "ui/ozone/platform/wayland/wayland_data_source.h"
 
 #include "base/files/file_util.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
 #include "ui/ozone/platform/wayland/wayland_connection.h"
+#include "ui/ozone/platform/wayland/wayland_window.h"
 
 namespace ui {
 
+constexpr char kTextMimeType[] = "text/plain";
 constexpr char kTextMimeTypeUtf8[] = "text/plain;charset=utf-8";
 
-WaylandDataSource::WaylandDataSource(wl_data_source* data_source)
-    : data_source_(data_source) {
+WaylandDataSource::WaylandDataSource(wl_data_source* data_source,
+                                     WaylandConnection* connection)
+    : data_source_(data_source), connection_(connection) {
   static const struct wl_data_source_listener kDataSourceListener = {
-      WaylandDataSource::OnTarget, WaylandDataSource::OnSend,
-      WaylandDataSource::OnCancel};
+      WaylandDataSource::OnTarget,      WaylandDataSource::OnSend,
+      WaylandDataSource::OnCancel,      WaylandDataSource::OnDnDDropPerformed,
+      WaylandDataSource::OnDnDFinished, WaylandDataSource::OnAction};
   wl_data_source_add_listener(data_source, &kDataSourceListener, this);
 }
 
@@ -25,7 +30,7 @@
     const ClipboardDelegate::DataMap& data_map) {
   for (const auto& data : data_map) {
     wl_data_source_offer(data_source_.get(), data.first.c_str());
-    if (strcmp(data.first.c_str(), "text/plain") == 0)
+    if (strcmp(data.first.c_str(), kTextMimeType) == 0)
       wl_data_source_offer(data_source_.get(), kTextMimeTypeUtf8);
   }
   wl_data_device_set_selection(connection_->data_device(), data_source_.get(),
@@ -39,11 +44,39 @@
   data_map_ = data_map;
 }
 
+void WaylandDataSource::Offer(const ui::OSExchangeData& data) {
+  // TODO(jkim): Handle mime types based on data.
+  std::vector<std::string> mime_types;
+  mime_types.push_back(kTextMimeType);
+  mime_types.push_back(kTextMimeTypeUtf8);
+
+  source_window_ = connection_->GetCurrentFocusedWindow();
+  for (auto& mime_type : mime_types)
+    wl_data_source_offer(data_source_.get(), mime_type.data());
+}
+
+void WaylandDataSource::SetDragData(const DragDataMap& data_map) {
+  DCHECK(drag_data_map_.empty());
+  drag_data_map_ = data_map;
+}
+
+void WaylandDataSource::SetAction(int operation) {
+  if (wl_data_source_get_version(data_source_.get()) >=
+      WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION) {
+    uint32_t dnd_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
+    if (operation & DragDropTypes::DRAG_COPY)
+      dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+    if (operation & DragDropTypes::DRAG_MOVE)
+      dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
+    wl_data_source_set_actions(data_source_.get(), dnd_actions);
+  }
+}
+
 // static
 void WaylandDataSource::OnTarget(void* data,
                                  wl_data_source* source,
                                  const char* mime_type) {
-  NOTIMPLEMENTED();
+  NOTIMPLEMENTED_LOG_ONCE();
 }
 
 // static
@@ -52,12 +85,18 @@
                                const char* mime_type,
                                int32_t fd) {
   WaylandDataSource* self = static_cast<WaylandDataSource*>(data);
-  base::Optional<std::vector<uint8_t>> mime_data;
-  self->GetClipboardData(mime_type, &mime_data);
-  if (!mime_data.has_value() && strcmp(mime_type, kTextMimeTypeUtf8) == 0)
-    self->GetClipboardData("text/plain", &mime_data);
-
-  std::string contents(mime_data->begin(), mime_data->end());
+  std::string contents;
+  if (self->source_window_) {
+    // If |source_window_| is valid when OnSend() is called, it means that DnD
+    // is working.
+    self->GetDragData(mime_type, &contents);
+  } else {
+    base::Optional<std::vector<uint8_t>> mime_data;
+    self->GetClipboardData(mime_type, &mime_data);
+    if (!mime_data.has_value() && strcmp(mime_type, kTextMimeTypeUtf8) == 0)
+      self->GetClipboardData(kTextMimeType, &mime_data);
+    contents.assign(mime_data->begin(), mime_data->end());
+  }
   bool result =
       base::WriteFileDescriptor(fd, contents.data(), contents.length());
   DCHECK(result);
@@ -67,7 +106,30 @@
 // static
 void WaylandDataSource::OnCancel(void* data, wl_data_source* source) {
   WaylandDataSource* self = static_cast<WaylandDataSource*>(data);
-  self->connection_->DataSourceCancelled();
+  if (self->source_window_) {
+    // If it has |source_window_|, it is in the middle of 'drag and drop'. it
+    // cancels 'drag and drop'.
+    self->connection_->FinishDragSession(self->dnd_action_,
+                                         self->source_window_);
+  } else {
+    self->connection_->DataSourceCancelled();
+  }
+}
+
+void WaylandDataSource::OnDnDDropPerformed(void* data, wl_data_source* source) {
+  NOTIMPLEMENTED_LOG_ONCE();
+}
+
+void WaylandDataSource::OnDnDFinished(void* data, wl_data_source* source) {
+  WaylandDataSource* self = static_cast<WaylandDataSource*>(data);
+  self->connection_->FinishDragSession(self->dnd_action_, self->source_window_);
+}
+
+void WaylandDataSource::OnAction(void* data,
+                                 wl_data_source* source,
+                                 uint32_t dnd_action) {
+  WaylandDataSource* self = static_cast<WaylandDataSource*>(data);
+  self->dnd_action_ = dnd_action;
 }
 
 void WaylandDataSource::GetClipboardData(
@@ -81,4 +143,15 @@
   }
 }
 
+void WaylandDataSource::GetDragData(const std::string& mime_type,
+                                    std::string* contents) {
+  auto it = drag_data_map_.find(mime_type);
+  if (it != drag_data_map_.end()) {
+    *contents = it->second;
+    return;
+  }
+
+  connection_->DeliverDragData(mime_type, contents);
+}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/wayland_data_source.h b/ui/ozone/platform/wayland/wayland_data_source.h
index 9ee3397..33dfeb2 100644
--- a/ui/ozone/platform/wayland/wayland_data_source.h
+++ b/ui/ozone/platform/wayland/wayland_data_source.h
@@ -7,8 +7,8 @@
 
 #include <wayland-client.h>
 
+#include <map>
 #include <string>
-#include <unordered_map>
 #include <vector>
 
 #include "base/logging.h"
@@ -19,7 +19,9 @@
 
 namespace ui {
 
+class OSExchangeData;
 class WaylandConnection;
+class WaylandWindow;
 
 // The WaylandDataSource object represents the source side of a
 // WaylandDataOffer. It is created by the source client in a data
@@ -28,8 +30,11 @@
 // transfer the data (OnSend listener).
 class WaylandDataSource {
  public:
+  using DragDataMap = std::map<std::string, std::string>;
+
   // Takes ownership of data_source.
-  explicit WaylandDataSource(wl_data_source* data_source);
+  explicit WaylandDataSource(wl_data_source* data_source,
+                             WaylandConnection* connection);
   ~WaylandDataSource();
 
   void set_connection(WaylandConnection* connection) {
@@ -39,6 +44,11 @@
 
   void WriteToClipboard(const ClipboardDelegate::DataMap& data_map);
   void UpdataDataMap(const ClipboardDelegate::DataMap& data_map);
+  void Offer(const ui::OSExchangeData& data);
+  void SetAction(int operation);
+  void SetDragData(const DragDataMap& data_map);
+
+  const wl_data_source* data_source() const { return data_source_.get(); }
 
  private:
   static void OnTarget(void* data,
@@ -49,14 +59,22 @@
                      const char* mime_type,
                      int32_t fd);
   static void OnCancel(void* data, wl_data_source* source);
+  static void OnDnDDropPerformed(void* data, wl_data_source* source);
+  static void OnDnDFinished(void* data, wl_data_source* source);
+  static void OnAction(void* data, wl_data_source* source, uint32_t dnd_action);
 
   void GetClipboardData(const std::string& mime_type,
                         base::Optional<std::vector<uint8_t>>* data);
+  void GetDragData(const std::string& mime_type, std::string* contents);
 
   wl::Object<wl_data_source> data_source_;
   WaylandConnection* connection_ = nullptr;
+  WaylandWindow* source_window_ = nullptr;
 
   ClipboardDelegate::DataMap data_map_;
+  DragDataMap drag_data_map_;
+  // Action selected by the compositor
+  uint32_t dnd_action_;
 
   DISALLOW_COPY_AND_ASSIGN(WaylandDataSource);
 };
diff --git a/ui/ozone/platform/wayland/wayland_object.cc b/ui/ozone/platform/wayland/wayland_object.cc
index ec41d32..c3cbb08 100644
--- a/ui/ozone/platform/wayland/wayland_object.cc
+++ b/ui/ozone/platform/wayland/wayland_object.cc
@@ -5,6 +5,7 @@
 #include "ui/ozone/platform/wayland/wayland_object.h"
 
 #include <linux-dmabuf-unstable-v1-client-protocol.h>
+#include <presentation-time-client-protocol.h>
 #include <wayland-client.h>
 #include <xdg-shell-unstable-v5-client-protocol.h>
 #include <xdg-shell-unstable-v6-client-protocol.h>
@@ -40,6 +41,15 @@
     wl_touch_destroy(touch);
 }
 
+void delete_data_device(wl_data_device* data_device) {
+  if (wl_data_device_get_version(data_device) >=
+      WL_DATA_DEVICE_RELEASE_SINCE_VERSION) {
+    wl_data_device_release(data_device);
+  } else {
+    wl_data_device_destroy(data_device);
+  }
+}
+
 }  // namespace
 
 const wl_interface* ObjectTraits<wl_buffer>::interface = &wl_buffer_interface;
@@ -62,7 +72,7 @@
 const wl_interface* ObjectTraits<wl_data_device>::interface =
     &wl_data_device_interface;
 void (*ObjectTraits<wl_data_device>::deleter)(wl_data_device*) =
-    &wl_data_device_destroy;
+    &delete_data_device;
 
 const wl_interface* ObjectTraits<wl_data_offer>::interface =
     &wl_data_offer_interface;
@@ -117,6 +127,16 @@
 const wl_interface* ObjectTraits<wl_touch>::interface = &wl_touch_interface;
 void (*ObjectTraits<wl_touch>::deleter)(wl_touch*) = &delete_touch;
 
+const wl_interface* ObjectTraits<wp_presentation>::interface =
+    &wp_presentation_interface;
+void (*ObjectTraits<wp_presentation>::deleter)(wp_presentation*) =
+    &wp_presentation_destroy;
+
+const wl_interface* ObjectTraits<struct wp_presentation_feedback>::interface =
+    &wp_presentation_feedback_interface;
+void (*ObjectTraits<struct wp_presentation_feedback>::deleter)(
+    struct wp_presentation_feedback*) = &wp_presentation_feedback_destroy;
+
 const wl_interface* ObjectTraits<xdg_shell>::interface = &xdg_shell_interface;
 void (*ObjectTraits<xdg_shell>::deleter)(xdg_shell*) = &xdg_shell_destroy;
 
diff --git a/ui/ozone/platform/wayland/wayland_object.h b/ui/ozone/platform/wayland/wayland_object.h
index df3f16e..62890452 100644
--- a/ui/ozone/platform/wayland/wayland_object.h
+++ b/ui/ozone/platform/wayland/wayland_object.h
@@ -27,6 +27,8 @@
 struct wl_subsurface;
 struct wl_surface;
 struct wl_touch;
+struct wp_presentation;
+struct wp_presentation_feedback;
 struct xdg_shell;
 struct xdg_surface;
 struct xdg_popup;
@@ -157,6 +159,18 @@
 };
 
 template <>
+struct ObjectTraits<wp_presentation> {
+  static const wl_interface* interface;
+  static void (*deleter)(wp_presentation*);
+};
+
+template <>
+struct ObjectTraits<wp_presentation_feedback> {
+  static const wl_interface* interface;
+  static void (*deleter)(wp_presentation_feedback*);
+};
+
+template <>
 struct ObjectTraits<xdg_shell> {
   static const wl_interface* interface;
   static void (*deleter)(xdg_shell*);
diff --git a/ui/ozone/platform/wayland/wayland_surface_factory.cc b/ui/ozone/platform/wayland/wayland_surface_factory.cc
index 751d7c2..a5b1dec0 100644
--- a/ui/ozone/platform/wayland/wayland_surface_factory.cc
+++ b/ui/ozone/platform/wayland/wayland_surface_factory.cc
@@ -231,9 +231,13 @@
   return it->second;
 }
 
-void WaylandSurfaceFactory::ScheduleBufferSwap(gfx::AcceleratedWidget widget,
-                                               uint32_t buffer_id) {
-  connection_->ScheduleBufferSwap(widget, buffer_id);
+void WaylandSurfaceFactory::ScheduleBufferSwap(
+    gfx::AcceleratedWidget widget,
+    uint32_t buffer_id,
+    const gfx::Rect& damage_region,
+    wl::BufferSwapCallback callback) {
+  connection_->ScheduleBufferSwap(widget, buffer_id, damage_region,
+                                  std::move(callback));
 }
 
 std::unique_ptr<SurfaceOzoneCanvas>
diff --git a/ui/ozone/platform/wayland/wayland_surface_factory.h b/ui/ozone/platform/wayland/wayland_surface_factory.h
index 3b569890..d20c3f90 100644
--- a/ui/ozone/platform/wayland/wayland_surface_factory.h
+++ b/ui/ozone/platform/wayland/wayland_surface_factory.h
@@ -7,17 +7,21 @@
 
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "ui/gl/gl_surface.h"
-#include "ui/ozone/public/surface_factory_ozone.h"
-
 #include "base/posix/eintr_wrapper.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/sequenced_task_runner_handle.h"
+#include "ui/gl/gl_surface.h"
+#include "ui/ozone/platform/wayland/wayland_util.h"
+#include "ui/ozone/public/surface_factory_ozone.h"
+
+namespace gfx {
+class Rect;
+}  // namespace gfx
 
 namespace ui {
 
-class WaylandConnectionProxy;
 class GbmSurfacelessWayland;
+class WaylandConnectionProxy;
 
 class WaylandSurfaceFactory : public SurfaceFactoryOzone {
  public:
@@ -25,7 +29,10 @@
   ~WaylandSurfaceFactory() override;
 
   // These methods are used, when a dmabuf based approach is used.
-  void ScheduleBufferSwap(gfx::AcceleratedWidget widget, uint32_t buffer_id);
+  void ScheduleBufferSwap(gfx::AcceleratedWidget widget,
+                          uint32_t buffer_id,
+                          const gfx::Rect& damage_region_,
+                          wl::BufferSwapCallback callback);
   void RegisterSurface(gfx::AcceleratedWidget widget,
                        GbmSurfacelessWayland* surface);
   void UnregisterSurface(gfx::AcceleratedWidget widget);
diff --git a/ui/ozone/platform/wayland/wayland_util.h b/ui/ozone/platform/wayland/wayland_util.h
index 0b77426..d104fc0 100644
--- a/ui/ozone/platform/wayland/wayland_util.h
+++ b/ui/ozone/platform/wayland/wayland_util.h
@@ -7,6 +7,7 @@
 
 #include <wayland-client.h>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "ui/ozone/platform/wayland/wayland_object.h"
 
@@ -18,10 +19,16 @@
 
 namespace gfx {
 class Size;
+enum class SwapResult;
+struct PresentationFeedback;
 }
 
 namespace wl {
 
+// Corresponds to mojom::WaylandConnection::ScheduleBufferSwapCallback.
+using BufferSwapCallback =
+    base::OnceCallback<void(gfx::SwapResult, const gfx::PresentationFeedback&)>;
+
 wl_buffer* CreateSHMBuffer(const gfx::Size& size,
                            base::SharedMemory* shared_memory,
                            wl_shm* shm);
diff --git a/ui/ozone/platform/wayland/wayland_window.cc b/ui/ozone/platform/wayland/wayland_window.cc
index c0d8847a0..9b251587 100644
--- a/ui/ozone/platform/wayland/wayland_window.cc
+++ b/ui/ozone/platform/wayland/wayland_window.cc
@@ -8,9 +8,11 @@
 
 #include "base/bind.h"
 #include "ui/base/cursor/ozone/bitmap_cursor_factory_ozone.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
 #include "ui/events/event.h"
 #include "ui/events/event_utils.h"
 #include "ui/events/ozone/events_ozone.h"
+#include "ui/gfx/geometry/point_f.h"
 #include "ui/ozone/platform/wayland/wayland_connection.h"
 #include "ui/ozone/platform/wayland/wayland_pointer.h"
 #include "ui/ozone/platform/wayland/xdg_popup_wrapper_v5.h"
@@ -510,6 +512,31 @@
   delegate_->OnCloseRequest();
 }
 
+void WaylandWindow::OnDragEnter(const gfx::PointF& point,
+                                std::unique_ptr<OSExchangeData> data,
+                                int operation) {
+  NOTIMPLEMENTED_LOG_ONCE();
+}
+
+int WaylandWindow::OnDragMotion(const gfx::PointF& point,
+                                uint32_t time,
+                                int operation) {
+  NOTIMPLEMENTED_LOG_ONCE();
+  return 0;
+}
+
+void WaylandWindow::OnDragDrop(std::unique_ptr<OSExchangeData> data) {
+  NOTIMPLEMENTED_LOG_ONCE();
+}
+
+void WaylandWindow::OnDragLeave() {
+  NOTIMPLEMENTED_LOG_ONCE();
+}
+
+void WaylandWindow::OnDragSessionClose(uint32_t dnd_action) {
+  NOTIMPLEMENTED_LOG_ONCE();
+}
+
 bool WaylandWindow::IsMinimized() const {
   return state_ == PlatformWindowState::PLATFORM_WINDOW_STATE_MINIMIZED;
 }
diff --git a/ui/ozone/platform/wayland/wayland_window.h b/ui/ozone/platform/wayland/wayland_window.h
index d652e896a..4cd69ff 100644
--- a/ui/ozone/platform/wayland/wayland_window.h
+++ b/ui/ozone/platform/wayland/wayland_window.h
@@ -13,9 +13,14 @@
 #include "ui/platform_window/platform_window.h"
 #include "ui/platform_window/platform_window_delegate.h"
 
+namespace gfx {
+class PointF;
+}
+
 namespace ui {
 
 class BitmapCursorOzone;
+class OSExchangeData;
 class PlatformWindowDelegate;
 class WaylandConnection;
 class XDGPopupWrapper;
@@ -101,6 +106,14 @@
 
   void OnCloseRequest();
 
+  void OnDragEnter(const gfx::PointF& point,
+                   std::unique_ptr<OSExchangeData> data,
+                   int operation);
+  int OnDragMotion(const gfx::PointF& point, uint32_t time, int operation);
+  void OnDragDrop(std::unique_ptr<OSExchangeData> data);
+  void OnDragLeave();
+  void OnDragSessionClose(uint32_t dnd_action);
+
  private:
   bool IsMinimized() const;
   bool IsMaximized() const;
diff --git a/ui/ozone/public/interfaces/wayland/BUILD.gn b/ui/ozone/public/interfaces/wayland/BUILD.gn
index c6f2360d..9ee493e 100644
--- a/ui/ozone/public/interfaces/wayland/BUILD.gn
+++ b/ui/ozone/public/interfaces/wayland/BUILD.gn
@@ -11,6 +11,7 @@
 
   public_deps = [
     "//mojo/public/mojom/base",
+    "//ui/gfx/geometry/mojo",
     "//ui/gfx/mojo",
   ]
 }
diff --git a/ui/ozone/public/interfaces/wayland/wayland_connection.mojom b/ui/ozone/public/interfaces/wayland/wayland_connection.mojom
index fde5b12..a3993ccb 100644
--- a/ui/ozone/public/interfaces/wayland/wayland_connection.mojom
+++ b/ui/ozone/public/interfaces/wayland/wayland_connection.mojom
@@ -6,7 +6,10 @@
 
 import "mojo/public/mojom/base/file.mojom";
 import "mojo/public/mojom/base/file_path.mojom";
+import "ui/gfx/geometry/mojo/geometry.mojom";
 import "ui/gfx/mojo/accelerated_widget.mojom";
+import "ui/gfx/mojo/presentation_feedback.mojom";
+import "ui/gfx/mojo/swap_result.mojom";
 
 // Used by the GPU for communication with a WaylandConnection on the browser
 // process.
@@ -26,7 +29,10 @@
   DestroyZwpLinuxDmabuf(uint32 buffer_id);
 
   // Swaps wl_buffers for a WaylandWindow with the following |widget|.
-  ScheduleBufferSwap(gfx.mojom.AcceleratedWidget widget, uint32 buffer_id);
+  ScheduleBufferSwap(gfx.mojom.AcceleratedWidget widget, uint32 buffer_id,
+                     gfx.mojom.Rect damage_region)
+      => (gfx.mojom.SwapResult swap_result,
+          gfx.mojom.PresentationFeedback feedback);
 };
 
 // Used by the browser process to provide the GPU process with a mojo ptr to a
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.cc b/ui/views/bubble/bubble_dialog_delegate_view.cc
index 6349e776..84d9ade9 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.cc
+++ b/ui/views/bubble/bubble_dialog_delegate_view.cc
@@ -202,6 +202,19 @@
   return anchor_view_tracker_->view();
 }
 
+void BubbleDialogDelegateView::set_arrow(BubbleBorder::Arrow arrow) {
+  if (arrow_ == arrow)
+    return;
+  arrow_ = arrow;
+
+  // If set_arrow() is called before CreateWidget(), there's no need to update
+  // the BubbleFrameView.
+  if (GetBubbleFrameView()) {
+    GetBubbleFrameView()->bubble_border()->set_arrow(arrow);
+    SizeToContents();
+  }
+}
+
 gfx::Rect BubbleDialogDelegateView::GetAnchorRect() const {
   if (!GetAnchorView())
     return anchor_rect_;
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.h b/ui/views/bubble/bubble_dialog_delegate_view.h
index 8b83610..51a95a0 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.h
+++ b/ui/views/bubble/bubble_dialog_delegate_view.h
@@ -72,7 +72,7 @@
   const gfx::Rect& anchor_rect() const { return anchor_rect_; }
 
   BubbleBorder::Arrow arrow() const { return arrow_; }
-  void set_arrow(BubbleBorder::Arrow arrow) { arrow_ = arrow; }
+  void set_arrow(BubbleBorder::Arrow arrow);
 
   void set_mirror_arrow_in_rtl(bool mirror) { mirror_arrow_in_rtl_ = mirror; }
 
diff --git a/ui/views/cocoa/bridged_native_widget.h b/ui/views/cocoa/bridged_native_widget.h
index c5bc55a..f259bb7 100644
--- a/ui/views/cocoa/bridged_native_widget.h
+++ b/ui/views/cocoa/bridged_native_widget.h
@@ -67,8 +67,12 @@
   static gfx::Size GetWindowSizeForClientSize(NSWindow* window,
                                               const gfx::Size& size);
 
+  // Retrieve a BridgedNativeWidgetImpl* from its id.
+  static BridgedNativeWidgetImpl* GetFromId(uint64_t bridged_native_widget_id);
+
   // Creates one side of the bridge. |host| and |parent| must not be NULL.
-  BridgedNativeWidgetImpl(BridgedNativeWidgetHost* host,
+  BridgedNativeWidgetImpl(uint64_t bridged_native_widget_id,
+                          BridgedNativeWidgetHost* host,
                           BridgedNativeWidgetHostHelper* host_helper,
                           NativeWidgetMac* parent);
   ~BridgedNativeWidgetImpl() override;
@@ -291,6 +295,7 @@
   bool IsVisibleParent() const override;
   void RemoveChildWindow(BridgedNativeWidgetImpl* child) override;
 
+  const uint64_t id_;
   BridgedNativeWidgetHost* const host_;               // Weak. Owns this.
   BridgedNativeWidgetHostHelper* const host_helper_;  // Weak, owned by |host_|.
   NativeWidgetMac* const native_widget_mac_;  // Weak. Owns |host_|.
diff --git a/ui/views/cocoa/bridged_native_widget.mm b/ui/views/cocoa/bridged_native_widget.mm
index 8f9dd7c..0d352f6 100644
--- a/ui/views/cocoa/bridged_native_widget.mm
+++ b/ui/views/cocoa/bridged_native_widget.mm
@@ -13,6 +13,7 @@
 #import "base/mac/foundation_util.h"
 #include "base/mac/mac_util.h"
 #import "base/mac/sdk_forward_declarations.h"
+#include "base/no_destructor.h"
 #include "base/single_thread_task_runner.h"
 #include "base/strings/sys_string_conversions.h"
 #include "ui/base/cocoa/cocoa_base_utils.h"
@@ -137,6 +138,8 @@
 }
 @end
 
+namespace views {
+
 namespace {
 
 using RankMap = std::map<NSView*, int>;
@@ -178,8 +181,8 @@
   return gfx::Size([window contentRectForFrameRect:frame_rect].size);
 }
 
-void RankNSViews(views::View* view,
-                 const views::BridgedNativeWidgetImpl::AssociatedViews& hosts,
+void RankNSViews(View* view,
+                 const BridgedNativeWidgetImpl::AssociatedViews& hosts,
                  RankMap* rank) {
   auto it = hosts.find(view);
   if (it != hosts.end())
@@ -223,9 +226,10 @@
   return count;
 }
 
-}  // namespace
+static base::NoDestructor<std::map<uint64_t, BridgedNativeWidgetImpl*>>
+    g_id_to_impl_map;
 
-namespace views {
+}  // namespace
 
 // static
 gfx::Size BridgedNativeWidgetImpl::GetWindowSizeForClientSize(
@@ -237,18 +241,36 @@
   return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
 }
 
+// static
+BridgedNativeWidgetImpl* BridgedNativeWidgetImpl::GetFromId(
+    uint64_t bridged_native_widget_id) {
+  auto found = g_id_to_impl_map.get()->find(bridged_native_widget_id);
+  if (found == g_id_to_impl_map.get()->end())
+    return nullptr;
+  return found->second;
+}
+
 BridgedNativeWidgetImpl::BridgedNativeWidgetImpl(
+    uint64_t bridged_native_widget_id,
     BridgedNativeWidgetHost* host,
     BridgedNativeWidgetHostHelper* host_helper,
     NativeWidgetMac* parent)
-    : host_(host), host_helper_(host_helper), native_widget_mac_(parent) {
+    : id_(bridged_native_widget_id),
+      host_(host),
+      host_helper_(host_helper),
+      native_widget_mac_(parent) {
+  DCHECK(g_id_to_impl_map.get()->find(id_) == g_id_to_impl_map.get()->end());
+  g_id_to_impl_map.get()->insert(std::make_pair(id_, this));
   DCHECK(parent);
-  window_delegate_.reset(
-      [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
-  ui::CATransactionCoordinator::Get().AddPreCommitObserver(this);
 }
 
 BridgedNativeWidgetImpl::~BridgedNativeWidgetImpl() {
+  // Ensure that |this| cannot be reached by its id while it is being destroyed.
+  auto found = g_id_to_impl_map.get()->find(id_);
+  DCHECK(found != g_id_to_impl_map.get()->end());
+  DCHECK_EQ(found->second, this);
+  g_id_to_impl_map.get()->erase(found);
+
   // The delegate should be cleared already. Note this enforces the precondition
   // that -[NSWindow close] is invoked on the hosted window before the
   // destructor is called.
@@ -263,9 +285,13 @@
 void BridgedNativeWidgetImpl::SetWindow(
     base::scoped_nsobject<NativeWidgetMacNSWindow> window) {
   DCHECK(!window_);
+  window_delegate_.reset(
+      [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
   window_ = std::move(window);
+  [window_ setBridgedNativeWidgetId:id_];
   [window_ setReleasedWhenClosed:NO];  // Owned by scoped_nsobject.
   [window_ setDelegate:window_delegate_];
+  ui::CATransactionCoordinator::Get().AddPreCommitObserver(this);
 }
 
 void BridgedNativeWidgetImpl::SetParent(NSView* new_parent) {
@@ -426,7 +452,7 @@
   [window_ setContentView:bridged_view_];
 }
 
-void BridgedNativeWidgetImpl::CreateDragDropClient(views::View* view) {
+void BridgedNativeWidgetImpl::CreateDragDropClient(View* view) {
   drag_drop_client_.reset(new DragDropClientMac(this, view));
 }
 
@@ -880,14 +906,14 @@
   UpdateWindowGeometry();
 }
 
-void BridgedNativeWidgetImpl::SetAssociationForView(const views::View* view,
+void BridgedNativeWidgetImpl::SetAssociationForView(const View* view,
                                                     NSView* native_view) {
   DCHECK_EQ(0u, associated_views_.count(view));
   associated_views_[view] = native_view;
   native_widget_mac_->GetWidget()->ReorderNativeViews();
 }
 
-void BridgedNativeWidgetImpl::ClearAssociationForView(const views::View* view) {
+void BridgedNativeWidgetImpl::ClearAssociationForView(const View* view) {
   auto it = associated_views_.find(view);
   DCHECK(it != associated_views_.end());
   associated_views_.erase(it);
diff --git a/ui/views/cocoa/bridged_native_widget_host.h b/ui/views/cocoa/bridged_native_widget_host.h
index 83a89158..cdf6536 100644
--- a/ui/views/cocoa/bridged_native_widget_host.h
+++ b/ui/views/cocoa/bridged_native_widget_host.h
@@ -48,6 +48,12 @@
                          bool* found_word,
                          gfx::DecoratedText* decorated_word,
                          gfx::Point* baseline_point) = 0;
+
+  // Returns the vertical position that sheets should be anchored, in pixels
+  // from the bottom of the window.
+  // TODO(ccameron): This should be either moved to the mojo interface or
+  // separated out in such a way as to avoid needing to go through mojo.
+  virtual double SheetPositionY() = 0;
 };
 
 }  // namespace views
diff --git a/ui/views/cocoa/bridged_native_widget_host_impl.h b/ui/views/cocoa/bridged_native_widget_host_impl.h
index a912623..628da32 100644
--- a/ui/views/cocoa/bridged_native_widget_host_impl.h
+++ b/ui/views/cocoa/bridged_native_widget_host_impl.h
@@ -46,15 +46,30 @@
       public ui::LayerOwner,
       public ui::AcceleratedWidgetMacNSView {
  public:
+  // Unique integer id handles are used to bridge between the
+  // BridgedNativeWidgetHostImpl in one process and the BridgedNativeWidgetHost
+  // potentially in another.
+  static BridgedNativeWidgetHostImpl* GetFromId(
+      uint64_t bridged_native_widget_id);
+  uint64_t bridged_native_widget_id() const { return id_; }
+
   // Creates one side of the bridge. |parent| must not be NULL.
   explicit BridgedNativeWidgetHostImpl(NativeWidgetMac* parent);
   ~BridgedNativeWidgetHostImpl() override;
 
-  // Provide direct access to the BridgedNativeWidgetImpl that this is hosting.
+  // The NativeWidgetMac that owns |this|.
+  views::NativeWidgetMac* native_widget_mac() const {
+    return native_widget_mac_;
+  }
+
+  // The mojo interface through which to communicate with the underlying
+  // NSWindow and NSView.
+  views_bridge_mac::mojom::BridgedNativeWidget* bridge() const;
+
+  // Direct access to the BridgedNativeWidgetImpl that this is hosting.
   // TODO(ccameron): Remove all accesses to this member, and replace them
   // with methods that may be sent across processes.
   BridgedNativeWidgetImpl* bridge_impl() const { return bridge_impl_.get(); }
-  views_bridge_mac::mojom::BridgedNativeWidget* bridge() const;
 
   TooltipManager* tooltip_manager() { return tooltip_manager_.get(); }
 
@@ -132,6 +147,7 @@
                  bool* found_word,
                  gfx::DecoratedText* decorated_word,
                  gfx::Point* baseline_point) override;
+  double SheetPositionY() override;
 
   // views_bridge_mac::mojom::BridgedNativeWidgetHost:
   void OnVisibilityChanged(bool visible) override;
@@ -174,6 +190,10 @@
                            bool* is_button_enabled,
                            bool* is_button_default) override;
   bool GetDoDialogButtonsExist(bool* buttons_exist) override;
+  bool GetShouldShowWindowTitle(bool* should_show_window_title) override;
+  bool GetCanWindowBecomeKey(bool* can_window_become_key) override;
+  bool GetAlwaysRenderWindowAsKey(bool* always_render_as_key) override;
+  bool GetCanWindowClose(bool* can_window_close) override;
 
   // views_bridge_mac::mojom::BridgedNativeWidgetHost, synchronous callbacks:
   void DispatchKeyEventRemote(std::unique_ptr<ui::Event> event,
@@ -194,6 +214,12 @@
                            GetDialogButtonInfoCallback callback) override;
   void GetDoDialogButtonsExist(
       GetDoDialogButtonsExistCallback callback) override;
+  void GetShouldShowWindowTitle(
+      GetShouldShowWindowTitleCallback callback) override;
+  void GetCanWindowBecomeKey(GetCanWindowBecomeKeyCallback callback) override;
+  void GetAlwaysRenderWindowAsKey(
+      GetAlwaysRenderWindowAsKeyCallback callback) override;
+  void GetCanWindowClose(GetCanWindowCloseCallback callback) override;
 
   // DialogObserver:
   void OnDialogModelChanged() override;
@@ -213,6 +239,7 @@
   // ui::AcceleratedWidgetMacNSView:
   void AcceleratedWidgetCALayerParamsUpdated() override;
 
+  const uint64_t id_;
   views::NativeWidgetMac* const native_widget_mac_;  // Weak. Owns |this_|.
 
   Widget::InitParams::Type widget_type_ = Widget::InitParams::TYPE_WINDOW;
diff --git a/ui/views/cocoa/bridged_native_widget_host_impl.mm b/ui/views/cocoa/bridged_native_widget_host_impl.mm
index 8a9ffdf3..0f455852 100644
--- a/ui/views/cocoa/bridged_native_widget_host_impl.mm
+++ b/ui/views/cocoa/bridged_native_widget_host_impl.mm
@@ -41,14 +41,40 @@
   return widget && widget->is_top_level();
 }
 
+base::NoDestructor<std::map<uint64_t, BridgedNativeWidgetHostImpl*>>
+    g_id_to_host_impl_map;
+
+uint64_t g_last_bridged_native_widget_id = 0;
+
 }  // namespace
 
+// static
+BridgedNativeWidgetHostImpl* BridgedNativeWidgetHostImpl::GetFromId(
+    uint64_t bridged_native_widget_id) {
+  auto found = g_id_to_host_impl_map.get()->find(bridged_native_widget_id);
+  if (found == g_id_to_host_impl_map.get()->end())
+    return nullptr;
+  return found->second;
+}
+
 BridgedNativeWidgetHostImpl::BridgedNativeWidgetHostImpl(
     NativeWidgetMac* parent)
-    : native_widget_mac_(parent),
-      bridge_impl_(new BridgedNativeWidgetImpl(this, this, parent)) {}
+    : id_(++g_last_bridged_native_widget_id),
+      native_widget_mac_(parent),
+      bridge_impl_(new BridgedNativeWidgetImpl(id_, this, this, parent)) {
+  DCHECK(g_id_to_host_impl_map.get()->find(id_) ==
+         g_id_to_host_impl_map.get()->end());
+  g_id_to_host_impl_map.get()->insert(std::make_pair(id_, this));
+  DCHECK(parent);
+}
 
 BridgedNativeWidgetHostImpl::~BridgedNativeWidgetHostImpl() {
+  // Ensure that |this| cannot be reached by its id while it is being destroyed.
+  auto found = g_id_to_host_impl_map.get()->find(id_);
+  DCHECK(found != g_id_to_host_impl_map.get()->end());
+  DCHECK_EQ(found->second, this);
+  g_id_to_host_impl_map.get()->erase(found);
+
   // Destroy the bridge first to prevent any calls back into this during
   // destruction.
   // TODO(ccameron): When all communication from |bridge_| to this goes through
@@ -292,6 +318,10 @@
   return false;
 }
 
+double BridgedNativeWidgetHostImpl::SheetPositionY() {
+  return native_widget_mac_->SheetPositionY();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // BridgedNativeWidgetHostImpl,
 // views_bridge_mac::mojom::BridgedNativeWidgetHost:
@@ -583,6 +613,38 @@
   return true;
 }
 
+bool BridgedNativeWidgetHostImpl::GetShouldShowWindowTitle(
+    bool* should_show_window_title) {
+  *should_show_window_title =
+      root_view_
+          ? root_view_->GetWidget()->widget_delegate()->ShouldShowWindowTitle()
+          : true;
+  return true;
+}
+
+bool BridgedNativeWidgetHostImpl::GetCanWindowBecomeKey(
+    bool* can_window_become_key) {
+  *can_window_become_key =
+      root_view_ ? root_view_->GetWidget()->CanActivate() : false;
+  return true;
+}
+
+bool BridgedNativeWidgetHostImpl::GetAlwaysRenderWindowAsKey(
+    bool* always_render_as_key) {
+  *always_render_as_key =
+      root_view_ ? root_view_->GetWidget()->IsAlwaysRenderAsActive() : false;
+  return true;
+}
+
+bool BridgedNativeWidgetHostImpl::GetCanWindowClose(bool* can_window_close) {
+  *can_window_close = true;
+  views::NonClientView* non_client_view =
+      root_view_ ? root_view_->GetWidget()->non_client_view() : nullptr;
+  if (non_client_view)
+    *can_window_close = non_client_view->CanClose();
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // BridgedNativeWidgetHostImpl,
 // views_bridge_mac::mojom::BridgedNativeWidgetHost synchronous callbacks:
@@ -658,6 +720,34 @@
   std::move(callback).Run(buttons_exist);
 }
 
+void BridgedNativeWidgetHostImpl::GetShouldShowWindowTitle(
+    GetShouldShowWindowTitleCallback callback) {
+  bool should_show_window_title = false;
+  GetShouldShowWindowTitle(&should_show_window_title);
+  std::move(callback).Run(should_show_window_title);
+}
+
+void BridgedNativeWidgetHostImpl::GetCanWindowBecomeKey(
+    GetCanWindowBecomeKeyCallback callback) {
+  bool can_window_become_key = false;
+  GetCanWindowBecomeKey(&can_window_become_key);
+  std::move(callback).Run(can_window_become_key);
+}
+
+void BridgedNativeWidgetHostImpl::GetAlwaysRenderWindowAsKey(
+    GetAlwaysRenderWindowAsKeyCallback callback) {
+  bool always_render_as_key = false;
+  GetAlwaysRenderWindowAsKey(&always_render_as_key);
+  std::move(callback).Run(always_render_as_key);
+}
+
+void BridgedNativeWidgetHostImpl::GetCanWindowClose(
+    GetCanWindowCloseCallback callback) {
+  bool can_window_close = false;
+  GetCanWindowClose(&can_window_close);
+  std::move(callback).Run(can_window_close);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // BridgedNativeWidgetHostImpl, DialogObserver:
 
@@ -682,7 +772,10 @@
     // Sanity check: When focus moves away from the widget (i.e. |focused_now|
     // is nil), then the textInputClient will be cleared.
     DCHECK(!!focused_now || !input_client);
-    bridge_impl_->SetTextInputClient(input_client);
+    // TODO(ccameron): TextInputClient is not handled across process borders
+    // yet.
+    if (bridge_impl_)
+      bridge_impl_->SetTextInputClient(input_client);
   }
 }
 
diff --git a/ui/views/cocoa/native_widget_mac_nswindow.h b/ui/views/cocoa/native_widget_mac_nswindow.h
index 8a0f51b7..8686a4f 100644
--- a/ui/views/cocoa/native_widget_mac_nswindow.h
+++ b/ui/views/cocoa/native_widget_mac_nswindow.h
@@ -47,6 +47,10 @@
 // create one.
 - (void)setWindowTouchBarDelegate:(id<WindowTouchBarDelegate>)delegate;
 
+// Identifier for the NativeWidgetMac from which this window was created. This
+// may be used to look up the BridgedNativeWidgetHostImpl in the browser process
+// or the BridgedNativeWidgetImpl in a display process.
+@property(assign, nonatomic) uint64_t bridgedNativeWidgetId;
 @end
 
 #endif  // UI_VIEWS_COCOA_NATIVE_WIDGET_MAC_NSWINDOW_H_
diff --git a/ui/views/cocoa/native_widget_mac_nswindow.mm b/ui/views/cocoa/native_widget_mac_nswindow.mm
index 9801529..df740b1 100644
--- a/ui/views/cocoa/native_widget_mac_nswindow.mm
+++ b/ui/views/cocoa/native_widget_mac_nswindow.mm
@@ -9,11 +9,11 @@
 #import "ui/base/cocoa/user_interface_item_command_handler.h"
 #import "ui/base/cocoa/window_size_constants.h"
 #import "ui/views/cocoa/bridged_native_widget.h"
+#import "ui/views/cocoa/bridged_native_widget_host.h"
 #import "ui/views/cocoa/views_nswindow_delegate.h"
 #import "ui/views/cocoa/window_touch_bar_delegate.h"
 #include "ui/views/controls/menu/menu_controller.h"
-#include "ui/views/widget/native_widget_mac.h"
-#include "ui/views/widget/widget_delegate.h"
+#include "ui/views_bridge_mac/mojo/bridged_native_widget_host.mojom.h"
 
 @interface NSWindow (Private)
 + (Class)frameViewClassForStyleMask:(NSWindowStyleMask)windowStyle;
@@ -27,13 +27,15 @@
 
 @interface NativeWidgetMacNSWindow ()
 - (ViewsNSWindowDelegate*)viewsNSWindowDelegate;
-- (views::Widget*)viewsWidget;
 - (BOOL)hasViewsMenuActive;
 - (id)rootAccessibilityObject;
 
 // Private API on NSWindow, determines whether the title is drawn on the title
 // bar. The title is still visible in menus, Expose, etc.
 - (BOOL)_isTitleHidden;
+
+// Retrieve the corresponding views::BridgedNativeWidgetImpl in this process.
+- (views::BridgedNativeWidgetImpl*)bridgeImpl;
 @end
 
 // Use this category to implement mouseDown: on multiple frame view classes
@@ -83,7 +85,9 @@
   base::scoped_nsobject<CommandDispatcher> commandDispatcher_;
   base::scoped_nsprotocol<id<UserInterfaceItemCommandHandler>> commandHandler_;
   id<WindowTouchBarDelegate> touchBarDelegate_;  // Weak.
+  uint64_t bridgedNativeWidgetId_;
 }
+@synthesize bridgedNativeWidgetId = bridgedNativeWidgetId_;
 
 - (instancetype)initWithContentRect:(NSRect)contentRect
                           styleMask:(NSUInteger)windowStyle
@@ -132,19 +136,18 @@
   return base::mac::ObjCCastStrict<ViewsNSWindowDelegate>([self delegate]);
 }
 
-- (views::Widget*)viewsWidget {
-  return [[self viewsNSWindowDelegate] nativeWidgetMac]->GetWidget();
+- (views::BridgedNativeWidgetImpl*)bridgeImpl {
+  return views::BridgedNativeWidgetImpl::GetFromId(bridgedNativeWidgetId_);
 }
 
 - (BOOL)hasViewsMenuActive {
-  views::MenuController* menuController =
-      views::MenuController::GetActiveInstance();
-  return menuController && menuController->owner() == [self viewsWidget];
+  bool hasMenuController = false;
+  [self bridgeImpl]->host()->GetHasMenuController(&hasMenuController);
+  return hasMenuController;
 }
 
 - (id)rootAccessibilityObject {
-  views::Widget* widget = [self viewsWidget];
-  return widget ? widget->GetRootView()->GetNativeViewAccessible() : nil;
+  return [self bridgeImpl]->host_helper()->GetNativeViewAccessible();
 }
 
 // NSWindow overrides.
@@ -161,10 +164,10 @@
 }
 
 - (BOOL)_isTitleHidden {
-  if (![self delegate])
-    return NO;
-
-  return ![self viewsWidget]->widget_delegate()->ShouldShowWindowTitle();
+  bool shouldShowWindowTitle = YES;
+  if ([self bridgeImpl])
+    [self bridgeImpl]->host()->GetShouldShowWindowTitle(&shouldShowWindowTitle);
+  return !shouldShowWindowTitle;
 }
 
 // The base implementation returns YES if the window's frame view is a custom
@@ -179,23 +182,36 @@
 // Note these can be called via -[NSWindow close] while the widget is being torn
 // down, so check for a delegate.
 - (BOOL)canBecomeKeyWindow {
-  return [self delegate] && [self viewsWidget]->CanActivate();
+  bool canBecomeKey = NO;
+  if ([self bridgeImpl])
+    [self bridgeImpl]->host()->GetCanWindowBecomeKey(&canBecomeKey);
+  return canBecomeKey;
 }
 
 - (BOOL)canBecomeMainWindow {
-  if (![self delegate])
+  views::BridgedNativeWidgetImpl* bridgeImpl = [self bridgeImpl];
+  if (!bridgeImpl)
     return NO;
 
   // Dialogs and bubbles shouldn't take large shadows away from their parent.
-  views::Widget* widget = [self viewsWidget];
-  return widget->CanActivate() &&
-         !views::NativeWidgetMac::GetBridgeImplForNativeWindow(self)->parent();
+  if (bridgeImpl->parent())
+    return NO;
+
+  bool canBecomeKey = NO;
+  if (bridgeImpl)
+    bridgeImpl->host()->GetCanWindowBecomeKey(&canBecomeKey);
+  return canBecomeKey;
 }
 
 // Lets the traffic light buttons on the parent window keep their active state.
 - (BOOL)hasKeyAppearance {
-  if ([self delegate] && [self viewsWidget]->IsAlwaysRenderAsActive())
-    return YES;
+  views::BridgedNativeWidgetImpl* bridgeImpl = [self bridgeImpl];
+  if (bridgeImpl) {
+    bool isAlwaysRenderWindowAsKey = NO;
+    bridgeImpl->host()->GetAlwaysRenderWindowAsKey(&isAlwaysRenderWindowAsKey);
+    if (isAlwaysRenderWindowAsKey)
+      return YES;
+  }
   return [super hasKeyAppearance];
 }
 
@@ -312,12 +328,12 @@
   // Additionally, if we don't do this, VoiceOver reads out the partial a11y
   // properties on the NSWindow and repeats them when focusing an item in the
   // RootView's a11y group. See http://crbug.com/748221.
-  views::Widget* widget = [self viewsWidget];
   id superFocus = [super accessibilityFocusedUIElement];
-  if (!widget || superFocus != self)
+  views::BridgedNativeWidgetImpl* bridgeImpl = [self bridgeImpl];
+  if (!bridgeImpl || superFocus != self)
     return superFocus;
 
-  return widget->GetRootView()->GetNativeViewAccessible();
+  return bridgeImpl->host_helper()->GetNativeViewAccessible();
 }
 
 - (id)accessibilityAttributeValue:(NSString*)attribute {
diff --git a/ui/views/cocoa/views_nswindow_delegate.h b/ui/views/cocoa/views_nswindow_delegate.h
index 245eb66..98b41f3 100644
--- a/ui/views/cocoa/views_nswindow_delegate.h
+++ b/ui/views/cocoa/views_nswindow_delegate.h
@@ -11,7 +11,6 @@
 #include "ui/views/views_export.h"
 
 namespace views {
-class NativeWidgetMac;
 class BridgedNativeWidgetImpl;
 }
 
@@ -24,10 +23,6 @@
   base::scoped_nsobject<NSCursor> cursor_;
 }
 
-// The NativeWidgetMac that created the window this is attached to. Returns
-// NULL if not created by NativeWidgetMac.
-@property(nonatomic, readonly) views::NativeWidgetMac* nativeWidgetMac;
-
 // If set, the cursor set in -[NSResponder updateCursor:] when the window is
 // reached along the responder chain.
 @property(retain, nonatomic) NSCursor* cursor;
diff --git a/ui/views/cocoa/views_nswindow_delegate.mm b/ui/views/cocoa/views_nswindow_delegate.mm
index b8bd9292..3615c0b 100644
--- a/ui/views/cocoa/views_nswindow_delegate.mm
+++ b/ui/views/cocoa/views_nswindow_delegate.mm
@@ -10,7 +10,6 @@
 #import "ui/views/cocoa/bridged_content_view.h"
 #import "ui/views/cocoa/bridged_native_widget.h"
 #include "ui/views/cocoa/bridged_native_widget_host.h"
-#include "ui/views/widget/native_widget_mac.h"
 #include "ui/views_bridge_mac/mojo/bridged_native_widget_host.mojom.h"
 
 @implementation ViewsNSWindowDelegate
@@ -23,10 +22,6 @@
   return self;
 }
 
-- (views::NativeWidgetMac*)nativeWidgetMac {
-  return parent_->native_widget_mac();
-}
-
 - (NSCursor*)cursor {
   return cursor_.get();
 }
@@ -124,9 +119,9 @@
 }
 
 - (BOOL)windowShouldClose:(id)sender {
-  views::NonClientView* nonClientView =
-      [self nativeWidgetMac]->GetWidget()->non_client_view();
-  return !nonClientView || nonClientView->CanClose();
+  bool canWindowClose = true;
+  parent_->host()->GetCanWindowClose(&canWindowClose);
+  return canWindowClose;
 }
 
 - (void)windowWillClose:(NSNotification*)notification {
@@ -200,12 +195,15 @@
 - (NSRect)window:(NSWindow*)window
     willPositionSheet:(NSWindow*)sheet
             usingRect:(NSRect)defaultSheetLocation {
+  // TODO(ccameron): This should go through the BridgedNativeWidgetHost
+  // interface.
+  CGFloat sheetPositionY = parent_->host_helper()->SheetPositionY();
+
   // As per NSWindowDelegate documentation, the origin indicates the top left
   // point of the host frame in window coordinates. The width changes the
   // animation from vertical to trapezoid if it is smaller than the width of the
   // dialog. The height is ignored but should be set to zero.
-  return NSMakeRect(0, [self nativeWidgetMac]->SheetPositionY(),
-                    NSWidth(defaultSheetLocation), 0);
+  return NSMakeRect(0, sheetPositionY, NSWidth(defaultSheetLocation), 0);
 }
 
 @end
diff --git a/ui/views/widget/native_widget_mac.mm b/ui/views/widget/native_widget_mac.mm
index 952220b..964b018 100644
--- a/ui/views/widget/native_widget_mac.mm
+++ b/ui/views/widget/native_widget_mac.mm
@@ -44,17 +44,6 @@
 base::LazyInstance<ui::GestureRecognizerImplMac>::Leaky
     g_gesture_recognizer_instance = LAZY_INSTANCE_INITIALIZER;
 
-NativeWidgetMac* GetNativeWidgetMacForNativeWindow(
-    gfx::NativeWindow native_window) {
-  id<NSWindowDelegate> window_delegate = [native_window delegate];
-  if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) {
-    ViewsNSWindowDelegate* delegate =
-        base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate);
-    return [delegate nativeWidgetMac];
-  }
-  return nullptr;  // Not created by NativeWidgetMac.
-}
-
 NSInteger StyleMaskForParams(const Widget::InitParams& params) {
   // If the Widget is modal, it will be displayed as a sheet. This works best if
   // it has NSTitledWindowMask. For example, with NSBorderlessWindowMask, the
@@ -95,16 +84,22 @@
 // static
 BridgedNativeWidgetImpl* NativeWidgetMac::GetBridgeImplForNativeWindow(
     gfx::NativeWindow window) {
-  if (NativeWidgetMac* widget = GetNativeWidgetMacForNativeWindow(window))
-    return widget->bridge_impl();
+  if (NativeWidgetMacNSWindow* widget_window =
+          base::mac::ObjCCast<NativeWidgetMacNSWindow>(window)) {
+    return BridgedNativeWidgetImpl::GetFromId(
+        [widget_window bridgedNativeWidgetId]);
+  }
   return nullptr;  // Not created by NativeWidgetMac.
 }
 
 // static
 BridgedNativeWidgetHostImpl* NativeWidgetMac::GetBridgeHostImplForNativeWindow(
     gfx::NativeWindow window) {
-  if (NativeWidgetMac* widget = GetNativeWidgetMacForNativeWindow(window))
-    return widget->bridge_host_.get();
+  if (NativeWidgetMacNSWindow* widget_window =
+          base::mac::ObjCCast<NativeWidgetMacNSWindow>(window)) {
+    return BridgedNativeWidgetHostImpl::GetFromId(
+        [widget_window bridgedNativeWidgetId]);
+  }
   return nullptr;  // Not created by NativeWidgetMac.
 }
 
@@ -152,7 +147,8 @@
   DCHECK(GetWidget()->GetRootView());
   bridge_host_->SetRootView(GetWidget()->GetRootView());
   bridge()->CreateContentView(GetWidget()->GetRootView()->bounds());
-  bridge_impl()->CreateDragDropClient(GetWidget()->GetRootView());
+  if (bridge_impl())
+    bridge_impl()->CreateDragDropClient(GetWidget()->GetRootView());
   if (auto* focus_manager = GetWidget()->GetFocusManager()) {
     bridge()->MakeFirstResponder();
     bridge_host_->SetFocusManager(focus_manager);
@@ -697,8 +693,10 @@
 // static
 NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow(
     gfx::NativeWindow window) {
-  if (NativeWidgetMac* widget = GetNativeWidgetMacForNativeWindow(window))
-    return widget;
+  if (BridgedNativeWidgetHostImpl* bridge_host_impl =
+          NativeWidgetMac::GetBridgeHostImplForNativeWindow(window)) {
+    return bridge_host_impl->native_widget_mac();
+  }
   return nullptr;  // Not created by NativeWidgetMac.
 }
 
diff --git a/ui/views_bridge_mac/mojo/bridged_native_widget_host.mojom b/ui/views_bridge_mac/mojo/bridged_native_widget_host.mojom
index 352e1d8..6434f74 100644
--- a/ui/views_bridge_mac/mojo/bridged_native_widget_host.mojom
+++ b/ui/views_bridge_mac/mojo/bridged_native_widget_host.mojom
@@ -134,4 +134,23 @@
   // for the current dialog.
   [Sync]
   GetDoDialogButtonsExist() => (bool buttons_exist);
+
+  // Synchronously query if the NSWindow should display its title.
+  [Sync]
+  GetShouldShowWindowTitle() => (bool should_show_window_title);
+
+  // Synchronously query if the NSWindow can become key (activate, in views
+  // terminology).
+  [Sync]
+  GetCanWindowBecomeKey() => (bool can_window_become_key);
+
+  // Synchronously query if the NSWindow should always render as if it is
+  // the key window (is active, in views terminology).
+  [Sync]
+  GetAlwaysRenderWindowAsKey() => (bool always_render_as_key);
+
+  // Synchronously query if the NSWindow should always render as if it is
+  // the key window (is active, in views terminology).
+  [Sync]
+  GetCanWindowClose() => (bool can_window_close);
 };
diff --git a/ui/web_dialogs/BUILD.gn b/ui/web_dialogs/BUILD.gn
index d5d48e49..d09fa063 100644
--- a/ui/web_dialogs/BUILD.gn
+++ b/ui/web_dialogs/BUILD.gn
@@ -33,7 +33,7 @@
   }
 }
 
-static_library("test_support") {
+jumbo_static_library("test_support") {
   sources = [
     "test/test_web_contents_handler.cc",
     "test/test_web_contents_handler.h",
diff --git a/ui/wm/BUILD.gn b/ui/wm/BUILD.gn
index e7295a2f..261603db 100644
--- a/ui/wm/BUILD.gn
+++ b/ui/wm/BUILD.gn
@@ -98,7 +98,7 @@
   }
 }
 
-static_library("test_support") {
+jumbo_static_library("test_support") {
   testonly = true
   sources = [
     "test/testing_cursor_client_observer.cc",