diff --git a/BUILD.gn b/BUILD.gn
index 7f12f0a..ce3ab61f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -216,7 +216,10 @@
   } else if (is_ios) {
     deps += [ "//ios:all" ]
   } else if (is_fuchsia) {
-    deps += [ ":d8_fuchsia" ]
+    deps += [
+      ":d8_fuchsia",
+      "tools/fuchsia/fidlgen_js:fidlgen_js_unittests",
+    ]
   }
 
   deps += root_extra_deps
diff --git a/DEPS b/DEPS
index fd69032..3e32d82d 100644
--- a/DEPS
+++ b/DEPS
@@ -105,7 +105,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '49fdd7ad1875f0a26317921367f6e21a60493ca8',
+  'skia_revision': '9e0efe3189e0212c6936a9d3103f222ebe46bc1f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # 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': 'c3bef3e7b0280aa367187921fdecf059796e7b65',
+  'angle_revision': 'c2116cd3af9ff11f783aaf80e8a48c3f089ae8ea',
   # 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.
@@ -129,7 +129,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'a1c846c4cf3f8c08edfffa1cc6b60860c011000b',
+  'pdfium_revision': '2ff6cd661c0203dcdcc09135bce8bba141037574',
   # 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.
@@ -181,7 +181,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'feed_revision': '3c5a5efeacbb25d349b36c3482c5794c5229e869',
+  'feed_revision': 'e291161061d18d222bc1b546225fe0567386cbf1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling android_sdk_build-tools_version
   # and whatever else without interference from each other.
@@ -213,7 +213,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_tools_revision': 'd73b9d8dfbf7761e3fde323af00ec18ebfc0020c',
+  'spv_tools_revision': '19c07731fce3073ea85e4f3e8759d9662b606f56',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -610,7 +610,7 @@
 
   # For Linux and Chromium OS.
   'src/third_party/cros_system_api': {
-      'url': Var('chromium_git') + '/chromiumos/platform/system_api.git' + '@' + '536735c009117dc648808dfe47e55284deca8fa0',
+      'url': Var('chromium_git') + '/chromiumos/platform/system_api.git' + '@' + 'f63dc2e08f0c619b185cd48de3dcf758bcd9d109',
       'condition': 'checkout_linux',
   },
 
@@ -620,7 +620,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '22300e1fb562291b55eb702fe73b164cb1a2317d',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '684313d6a319a33b8aef29cd0600e2dcbe26f8db',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -954,7 +954,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'cd9050823b919817b45ef6f104d0c95b61ec2a43',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '723e82b649de19b925c286b5af838c3f45037c56',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1106,7 +1106,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '6d2f3f4cb8bac1f7c4a945c73d07a33df74f22f9',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'f9d38f2e4e5d87f3a1be0711b385d9809c9281f8',
+    Var('webrtc_git') + '/src.git' + '@' + '59021ba4e1dcd6b5dddf966591a4d0ebc6862705',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1137,7 +1137,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@457799c2a3affc68076fabbd04c4a56f6b452ce2',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@18c0a2b447779c4f5942d25be801168b8343d333',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_variations_service_client.cc b/android_webview/browser/aw_variations_service_client.cc
index e096879..7781ad84 100644
--- a/android_webview/browser/aw_variations_service_client.cc
+++ b/android_webview/browser/aw_variations_service_client.cc
@@ -54,6 +54,10 @@
   return android_webview::GetChannelOrStable();
 }
 
+bool AwVariationsServiceClient::GetSupportsPermanentConsistency() {
+  return false;
+}
+
 bool AwVariationsServiceClient::OverridesRestrictParameter(
     std::string* parameter) {
   return false;
diff --git a/android_webview/browser/aw_variations_service_client.h b/android_webview/browser/aw_variations_service_client.h
index afcac6b..0caf0fd 100644
--- a/android_webview/browser/aw_variations_service_client.h
+++ b/android_webview/browser/aw_variations_service_client.h
@@ -31,6 +31,7 @@
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override;
   network_time::NetworkTimeTracker* GetNetworkTimeTracker() override;
   version_info::Channel GetChannel() override;
+  bool GetSupportsPermanentConsistency() override;
   bool OverridesRestrictParameter(std::string* parameter) override;
 
   DISALLOW_COPY_AND_ASSIGN(AwVariationsServiceClient);
diff --git a/android_webview/glue/BUILD.gn b/android_webview/glue/BUILD.gn
index 9b8958e..e97103b 100644
--- a/android_webview/glue/BUILD.gn
+++ b/android_webview/glue/BUILD.gn
@@ -43,6 +43,7 @@
     "java/src/com/android/webview/chromium/ServiceWorkerSettingsAdapter.java",
     "java/src/com/android/webview/chromium/SharedStatics.java",
     "java/src/com/android/webview/chromium/SharedTracingControllerAdapter.java",
+    "java/src/com/android/webview/chromium/SplitApkWorkaround.java",
     "java/src/com/android/webview/chromium/TokenBindingManagerAdapter.java",
     "java/src/com/android/webview/chromium/TracingControllerAdapter.java",
     "java/src/com/android/webview/chromium/WebBackForwardListChromium.java",
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/SplitApkWorkaround.java b/android_webview/glue/java/src/com/android/webview/chromium/SplitApkWorkaround.java
new file mode 100644
index 0000000..b403474
--- /dev/null
+++ b/android_webview/glue/java/src/com/android/webview/chromium/SplitApkWorkaround.java
@@ -0,0 +1,155 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package com.android.webview.chromium;
+
+import android.support.annotation.IntDef;
+
+import dalvik.system.BaseDexClassLoader;
+
+import org.chromium.base.Log;
+
+import java.io.File;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * WebView-side workaround for the Android O framework bug described in https://crbug.com/889954
+ * which affects us if we are the current WebView provider and we were installed as a split APK.
+ */
+public class SplitApkWorkaround {
+    private static final String TAG = "SplitApkWorkaround";
+
+    @IntDef({Result.NOT_RUN, Result.NO_ENTRIES, Result.ONE_ENTRY, Result.MULTIPLE_ENTRIES,
+            Result.TOPLEVEL_EXCEPTION, Result.LOOP_EXCEPTION})
+    @interface Result {
+        int NOT_RUN = 0;
+        int NO_ENTRIES = 1;
+        int ONE_ENTRY = 2;
+        int MULTIPLE_ENTRIES = 3;
+        int TOPLEVEL_EXCEPTION = 4;
+        int LOOP_EXCEPTION = 5;
+    }
+
+    /**
+     * There is a framework bug in O that causes an incorrect classloader cache entry to be created
+     * when the WebView provider is installed as multiple split APKs.
+     * Use reflection to correct the cache entry during WebView zygote startup.
+     * This function runs in the WebView zygote, which cannot make any binder calls to the framework
+     * and is a very restricted environment.
+     *
+     * @param dryRun If true, don't actually change any state in the framework; just verify that
+     *               the reflection succeeds.
+     * @return a value from Result describing what happened.
+     */
+    @SuppressWarnings("unchecked")
+    public static @Result int apply(boolean dryRun) {
+        int matchingEntries = 0;
+        int exceptionEntries = 0;
+        try {
+            // Retrieve all the required classes and fields first, such that if any of the lookups
+            // fail, we won't have done anything yet.
+            Class<?> alClass = Class.forName("android.app.ApplicationLoaders");
+            Method getDefaultMethod = alClass.getDeclaredMethod("getDefault");
+            Field mLoadersField = alClass.getDeclaredField("mLoaders");
+            mLoadersField.setAccessible(true);
+            Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
+            pathListField.setAccessible(true);
+            Class<?> dplClass = Class.forName("dalvik.system.DexPathList");
+            Field dexElementsField = dplClass.getDeclaredField("dexElements");
+            dexElementsField.setAccessible(true);
+            Class<?> elClass = Class.forName("dalvik.system.DexPathList$Element");
+            Field pathField = elClass.getDeclaredField("path");
+            pathField.setAccessible(true);
+
+            // Retrieve the ApplicationLoaders singleton and get the cache from inside it.
+            Object alInstance = getDefaultMethod.invoke(null);
+            Object rawLoaders = mLoadersField.get(alInstance);
+            Map<String, ClassLoader> loaders = (Map<String, ClassLoader>) rawLoaders;
+
+            // Synchronize on the map while trying to update it, as the framework does.
+            synchronized (loaders) {
+                for (Map.Entry<String, ClassLoader> entry : loaders.entrySet()) {
+                    try {
+                        if (!(entry.getValue() instanceof BaseDexClassLoader)) {
+                            // If it's some other type it can't be the right one.
+                            continue;
+                        }
+                        String cacheKey = entry.getKey();
+                        BaseDexClassLoader cl = (BaseDexClassLoader) entry.getValue();
+
+                        // Get the list of files that this classloader uses as its classpath.
+                        Object pathList = pathListField.get(cl);
+                        Object dexElements = dexElementsField.get(pathList);
+
+                        int elementCount = Array.getLength(dexElements);
+                        if (elementCount <= 1) {
+                            // If there's only one file, then this classloader cannot be affected by
+                            // the bug, so ignore it.
+                            continue;
+                        }
+
+                        // If there's more than one file, get the first file in the path.
+                        // If it's the same as our cache key, then the cache key must, by
+                        // definition, be incomplete - listing only one file when it should list
+                        // all of them.
+                        Object firstElement = Array.get(dexElements, 0);
+                        File firstPath = (File) pathField.get(firstElement);
+                        if (cacheKey.equals(firstPath.getPath())) {
+                            // Build a new, correct cache key by concatenating all the files in the
+                            // list together in order, separated by colons.
+                            String newCacheKey = cacheKey;
+                            for (int i = 1; i < elementCount; i++) {
+                                Object element = Array.get(dexElements, i);
+                                File path = (File) pathField.get(element);
+                                newCacheKey += ":" + path.getPath();
+                            }
+
+                            matchingEntries++;
+                            if (!dryRun) {
+                                // Add a new entry to the cache which maps the new, correct key to
+                                // the same classloader object. We do not remove the previous entry
+                                // from the cache, in case something attempts to look it up by the
+                                // old key for some reason - it shouldn't cause a problem for there
+                                // to be multiple entries mapping to the same classloader.
+                                loaders.put(newCacheKey, cl);
+                                Log.i(TAG, "Fixed classloader cache entry for " + newCacheKey);
+                            }
+                        }
+                    } catch (Exception e) {
+                        // We log and ignore it here so we can continue looping through the cache,
+                        // in the hope that the one that threw an exception wasn't the one we
+                        // were looking for.
+                        exceptionEntries++;
+                        Log.w(TAG, "Caught exception while attempting to fix classloader cache", e);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            // If we got an exception at this point we assume that we failed to fix it, since we
+            // didn't get as far as iterating over the cache entries.
+            Log.w(TAG, "Caught exception while attempting to fix classloader cache", e);
+            return Result.TOPLEVEL_EXCEPTION;
+        }
+
+        // If we found at least one matching entry, then don't worry about exceptions that happened
+        // during the loop; we likely found the correct classloader, and the one that triggered an
+        // exception was probably not relevant. Distinguish one vs multiple entries, though,
+        // because multiple matches is unexpected (only one case in the code is supposed to create
+        // this situation).
+        if (matchingEntries == 1) return Result.ONE_ENTRY;
+        if (matchingEntries > 1) return Result.MULTIPLE_ENTRIES;
+
+        // If we didn't find any matching entries, but did get an exception during the loop, then
+        // report this, as we might have taken the exception while trying to access the entry we
+        // needed to fix.
+        if (exceptionEntries > 0) return Result.LOOP_EXCEPTION;
+
+        // Otherwise, we just didn't find any entries at all, which is probably fine; not all
+        // configurations actually trigger the bug.
+        return Result.NO_ENTRIES;
+    }
+}
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
index 4243e08..75ad5153 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
@@ -13,7 +13,6 @@
 import android.os.Build;
 import android.os.SystemClock;
 import android.provider.Settings;
-import android.util.Log;
 import android.view.ViewGroup;
 import android.webkit.CookieManager;
 import android.webkit.GeolocationPermissions;
@@ -40,6 +39,7 @@
 import org.chromium.base.BuildInfo;
 import org.chromium.base.CommandLine;
 import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
 import org.chromium.base.PackageUtils;
 import org.chromium.base.PathUtils;
 import org.chromium.base.StrictModeContext;
@@ -62,7 +62,7 @@
  */
 @SuppressWarnings("deprecation")
 public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
-    private static final String TAG = "WebViewChromiumFactoryProvider";
+    private static final String TAG = "WVCFactoryProvider";
 
     private static final String CHROMIUM_PREFS_NAME = "WebViewChromiumPrefs";
     private static final String VERSION_CODE_PREF = "lastVersionCodeUsed";
@@ -362,7 +362,19 @@
         }
     }
 
+    private static @SplitApkWorkaround.Result int sSplitApkWorkaroundResult =
+            SplitApkWorkaround.Result.NOT_RUN;
+
     public static boolean preloadInZygote() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+                && Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+            // If we're on O, where the split APK handling bug exists, then go through the motions
+            // of applying the workaround - don't actually change anything, but do the reflection
+            // to check for compatibility issues. The result will be logged to UMA later, because
+            // we can't do very much in the restricted environment of the WebView zygote process.
+            sSplitApkWorkaroundResult = SplitApkWorkaround.apply(/* dryRun */ true);
+        }
+
         for (String library : NativeLibraries.LIBRARIES) {
             System.loadLibrary(library);
         }
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index bd69a39f..b044bbc 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -242,8 +242,6 @@
     "cancel_mode.h",
     "cast_config_controller.cc",
     "cast_config_controller.h",
-    "client_image_registry.cc",
-    "client_image_registry.h",
     "dbus/ash_dbus_services.cc",
     "dbus/ash_dbus_services.h",
     "dbus/display_service_provider.cc",
diff --git a/ash/accelerators/accelerator_controller.cc b/ash/accelerators/accelerator_controller.cc
index 67548770c..7080726 100644
--- a/ash/accelerators/accelerator_controller.cc
+++ b/ash/accelerators/accelerator_controller.cc
@@ -31,6 +31,7 @@
 #include "ash/new_window_controller.h"
 #include "ash/public/cpp/app_list/app_list_constants.h"
 #include "ash/public/cpp/ash_features.h"
+#include "ash/public/interfaces/accessibility_controller.mojom.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/root_window_controller.h"
 #include "ash/rotator/window_rotation.h"
@@ -812,9 +813,8 @@
 
 void HandleToggleDictation() {
   base::RecordAction(UserMetricsAction("Accel_Toggle_Dictation"));
-  UserMetricsRecorder::RecordUserToggleDictation(
-      DictationToggleMethod::kToggleByKeyboard);
-  Shell::Get()->accessibility_controller()->ToggleDictation();
+  Shell::Get()->accessibility_controller()->ToggleDictationFromSource(
+      mojom::DictationToggleSource::kKeyboard);
 }
 
 bool CanHandleToggleDockedMagnifier() {
diff --git a/ash/accessibility/accessibility_controller.cc b/ash/accessibility/accessibility_controller.cc
index 86fde7b..5524fd8 100644
--- a/ash/accessibility/accessibility_controller.cc
+++ b/ash/accessibility/accessibility_controller.cc
@@ -27,6 +27,7 @@
 #include "ash/system/power/scoped_backlights_forced_off.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/command_line.h"
+#include "base/metrics/user_metrics.h"
 #include "base/strings/string16.h"
 #include "chromeos/audio/cras_audio_handler.h"
 #include "components/pref_registry/pref_registry_syncable.h"
@@ -648,6 +649,14 @@
   }
 }
 
+void AccessibilityController::ToggleDictationFromSource(
+    mojom::DictationToggleSource source) {
+  base::RecordAction(base::UserMetricsAction("Accel_Toggle_Dictation"));
+  UserMetricsRecorder::RecordUserToggleDictation(source);
+
+  ToggleDictation();
+}
+
 void AccessibilityController::SilenceSpokenFeedback() {
   if (client_)
     client_->SilenceSpokenFeedback();
diff --git a/ash/accessibility/accessibility_controller.h b/ash/accessibility/accessibility_controller.h
index 1548b80..2da44e5 100644
--- a/ash/accessibility/accessibility_controller.h
+++ b/ash/accessibility/accessibility_controller.h
@@ -167,6 +167,7 @@
   void SetSelectToSpeakState(mojom::SelectToSpeakState state) override;
   void SetSelectToSpeakEventHandlerDelegate(
       mojom::SelectToSpeakEventHandlerDelegatePtr delegate) override;
+  void ToggleDictationFromSource(mojom::DictationToggleSource source) override;
 
   // SessionObserver:
   void OnSigninScreenPrefServiceInitialized(PrefService* prefs) override;
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index 8200dd1..7ac04ef0 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -33,16 +33,6 @@
 #include "ui/base/ui_base_features.h"
 #include "ui/display/screen.h"
 
-namespace {
-
-int64_t GetDisplayIdToShowAppListOn() {
-  return display::Screen::GetScreen()
-      ->GetDisplayNearestWindow(ash::Shell::GetRootWindowForNewWindows())
-      .id();
-}
-
-}  // namespace
-
 namespace ash {
 
 AppListControllerImpl::AppListControllerImpl(ws::WindowService* window_service)
@@ -387,15 +377,14 @@
 
 void AppListControllerImpl::OnActiveUserPrefServiceChanged(
     PrefService* /* pref_service */) {
-  if (!IsHomeLauncherEnabledInTabletMode() ||
-      !display::Display::HasInternalDisplay()) {
+  if (!IsHomeLauncherEnabledInTabletMode()) {
     DismissAppList();
     return;
   }
 
   // Show the app list after signing in in tablet mode.
-  Show(display::Display::InternalDisplayId(),
-       app_list::AppListShowSource::kTabletMode, base::TimeTicks());
+  Show(GetDisplayIdToShowAppListOn(), app_list::AppListShowSource::kTabletMode,
+       base::TimeTicks());
 
   // The app list is not dismissed before switching user, suggestion chips will
   // not be shown. So reset app list state and trigger an initial search here to
@@ -536,7 +525,7 @@
     presenter_.GetView()->OnTabletModeChanged(true);
   }
 
-  if (!is_home_launcher_enabled_ || !display::Display::HasInternalDisplay())
+  if (!is_home_launcher_enabled_)
     return;
 
   SessionController const* session_controller =
@@ -545,8 +534,7 @@
     return;
 
   // Show the app list if the tablet mode starts.
-  Show(display::Display::InternalDisplayId(), app_list::kTabletMode,
-       base::TimeTicks());
+  Show(GetDisplayIdToShowAppListOn(), app_list::kTabletMode, base::TimeTicks());
   UpdateHomeLauncherVisibility();
   Shelf::ForWindow(presenter_.GetWindow())->MaybeUpdateShelfBackground();
 }
@@ -865,4 +853,16 @@
       controller->allowed_state() == mojom::AssistantAllowedState::ALLOWED);
 }
 
+int64_t AppListControllerImpl::GetDisplayIdToShowAppListOn() {
+  if (IsHomeLauncherEnabledInTabletMode()) {
+    return display::Display::HasInternalDisplay()
+               ? display::Display::InternalDisplayId()
+               : display::Screen::GetScreen()->GetPrimaryDisplay().id();
+  }
+
+  return display::Screen::GetScreen()
+      ->GetDisplayNearestWindow(ash::Shell::GetRootWindowForNewWindows())
+      .id();
+}
+
 }  // namespace ash
diff --git a/ash/app_list/app_list_controller_impl.h b/ash/app_list/app_list_controller_impl.h
index 35d3000..ff3de81 100644
--- a/ash/app_list/app_list_controller_impl.h
+++ b/ash/app_list/app_list_controller_impl.h
@@ -235,6 +235,8 @@
   // Update the visibility of Assistant functionality.
   void UpdateAssistantVisibility();
 
+  int64_t GetDisplayIdToShowAppListOn();
+
   ws::WindowService* window_service_;
 
   base::string16 last_raw_query_;
diff --git a/ash/app_list/app_list_presenter_delegate_unittest.cc b/ash/app_list/app_list_presenter_delegate_unittest.cc
index e82449f4..0ef342a 100644
--- a/ash/app_list/app_list_presenter_delegate_unittest.cc
+++ b/ash/app_list/app_list_presenter_delegate_unittest.cc
@@ -1124,9 +1124,6 @@
          app_list_features::kEnableBackgroundBlur},
         {});
     AppListPresenterDelegateTest::SetUp();
-    // Home launcher is only enabled on internal display.
-    display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
-        .SetFirstDisplayAsInternalDisplay();
     GetAppListTestHelper()->WaitUntilIdle();
   }
 
diff --git a/ash/app_list/presenter/app_list_presenter_impl.cc b/ash/app_list/presenter/app_list_presenter_impl.cc
index dbc45d7..f211bad 100644
--- a/ash/app_list/presenter/app_list_presenter_impl.cc
+++ b/ash/app_list/presenter/app_list_presenter_impl.cc
@@ -136,6 +136,7 @@
     delegate_->Init(view, display_id, current_apps_page_);
     SetView(view);
   }
+  view_->ShowWhenReady();
   delegate_->OnShown(display_id);
   NotifyTargetVisibilityChanged(GetTargetVisibility());
   NotifyVisibilityChanged(GetTargetVisibility(), display_id);
@@ -279,7 +280,6 @@
   // Sync the |onscreen_keyboard_shown_| in case |view_| is not initiated when
   // the on-screen is shown.
   view_->set_onscreen_keyboard_shown(delegate_->GetOnScreenKeyboardShown());
-  view_->ShowWhenReady();
 }
 
 void AppListPresenterImpl::ResetView() {
diff --git a/ash/client_image_registry.cc b/ash/client_image_registry.cc
deleted file mode 100644
index 2567ac1..0000000
--- a/ash/client_image_registry.cc
+++ /dev/null
@@ -1,39 +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 "ash/client_image_registry.h"
-
-namespace ash {
-
-ClientImageRegistry::ClientImageRegistry() = default;
-
-ClientImageRegistry::~ClientImageRegistry() = default;
-
-void ClientImageRegistry::BindRequest(
-    mojom::ClientImageRegistryRequest request) {
-  binding_set_.AddBinding(this, std::move(request));
-}
-
-const gfx::ImageSkia* ClientImageRegistry::GetImage(
-    const base::UnguessableToken& token) const {
-  auto iter = images_.find(token);
-  if (iter == images_.end()) {
-    // No DCHECK here as otherwise ash would crash if a bad client supplies a
-    // random value.
-    return nullptr;
-  }
-
-  return &iter->second;
-}
-
-void ClientImageRegistry::RegisterImage(const base::UnguessableToken& token,
-                                        const gfx::ImageSkia& image) {
-  images_[token] = image;
-}
-
-void ClientImageRegistry::ForgetImage(const base::UnguessableToken& token) {
-  images_.erase(token);
-}
-
-}  // namespace ash
diff --git a/ash/client_image_registry.h b/ash/client_image_registry.h
deleted file mode 100644
index d59e4d1..0000000
--- a/ash/client_image_registry.h
+++ /dev/null
@@ -1,45 +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 ASH_CLIENT_IMAGE_REGISTRY_H_
-#define ASH_CLIENT_IMAGE_REGISTRY_H_
-
-#include <map>
-
-#include "ash/public/interfaces/client_image_registry.mojom.h"
-#include "base/macros.h"
-#include "base/unguessable_token.h"
-#include "mojo/public/cpp/bindings/binding_set.h"
-#include "ui/gfx/image/image_skia.h"
-
-namespace ash {
-
-// ClientImageRegistry holds onto images that clients provide until it's told to
-// drop them. This allows reuse of an image with making multiple copies in the
-// Ash process or repeated serialization/deserialization.
-class ClientImageRegistry : public mojom::ClientImageRegistry {
- public:
-  ClientImageRegistry();
-  ~ClientImageRegistry() override;
-
-  void BindRequest(mojom::ClientImageRegistryRequest request);
-
-  const gfx::ImageSkia* GetImage(const base::UnguessableToken& token) const;
-
-  // mojom::ClientImageRegistry:
-  void RegisterImage(const base::UnguessableToken& token,
-                     const gfx::ImageSkia& image) override;
-  void ForgetImage(const base::UnguessableToken& token) override;
-
- private:
-  std::map<base::UnguessableToken, gfx::ImageSkia> images_;
-
-  mojo::BindingSet<mojom::ClientImageRegistry> binding_set_;
-
-  DISALLOW_COPY_AND_ASSIGN(ClientImageRegistry);
-};
-
-}  // namespace ash
-
-#endif  // ASH_CLIENT_IMAGE_REGISTRY_H_
diff --git a/ash/drag_drop/drag_drop_controller.cc b/ash/drag_drop/drag_drop_controller.cc
index 531a72f3..88bb980 100644
--- a/ash/drag_drop/drag_drop_controller.cc
+++ b/ash/drag_drop/drag_drop_controller.cc
@@ -179,7 +179,7 @@
     // capture, it still gets a valid gesture state.
     Shell::Get()->aura_env()->gesture_recognizer()->TransferEventsTo(
         source_window, tracker->capture_window(),
-        ui::GestureRecognizer::ShouldCancelTouches::Cancel);
+        ui::TransferTouchesBehavior::kCancel);
     // We also send a gesture end to the source window so it can clear state.
     // TODO(varunjain): Remove this whole block when gesture sequence
     // transferring is properly done in the GR (http://crbug.com/160558)
diff --git a/ash/frame/header_view.cc b/ash/frame/header_view.cc
index d08ac5bc..1942e68d 100644
--- a/ash/frame/header_view.cc
+++ b/ash/frame/header_view.cc
@@ -6,7 +6,6 @@
 
 #include <memory>
 
-#include "ash/client_image_registry.h"
 #include "ash/frame/non_client_frame_view_ash.h"
 #include "ash/public/cpp/caption_buttons/caption_button_model.h"
 #include "ash/public/cpp/caption_buttons/frame_back_button.h"
@@ -326,12 +325,7 @@
   if (!target_widget_)
     return;
 
-  caption_button_container_->SetVisible(
-      should_paint_ && !(Shell::Get()
-                             ->tablet_mode_controller()
-                             ->IsTabletModeWindowManagerEnabled() &&
-                         target_widget_->GetNativeWindow()->GetProperty(
-                             ash::kHideCaptionButtonsInTabletModeKey)));
+  caption_button_container_->SetVisible(should_paint_);
 }
 
 }  // namespace ash
diff --git a/ash/frame/non_client_frame_view_ash_unittest.cc b/ash/frame/non_client_frame_view_ash_unittest.cc
index e8349a96..8753d54f 100644
--- a/ash/frame/non_client_frame_view_ash_unittest.cc
+++ b/ash/frame/non_client_frame_view_ash_unittest.cc
@@ -9,7 +9,6 @@
 #include "ash/accelerators/accelerator_controller.h"
 #include "ash/frame/header_view.h"
 #include "ash/frame/wide_frame_view.h"
-#include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/ash_layout_constants.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/caption_buttons/frame_caption_button.h"
@@ -29,7 +28,6 @@
 #include "ash/wm/wm_event.h"
 #include "base/command_line.h"
 #include "base/containers/flat_set.h"
-#include "base/test/scoped_feature_list.h"
 #include "services/ws/public/mojom/window_tree_constants.mojom.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
@@ -927,81 +925,4 @@
 // Run frame color tests with and without custom wm::WindowStateDelegate.
 INSTANTIATE_TEST_CASE_P(, NonClientFrameViewAshFrameColorTest, testing::Bool());
 
-class HomeLauncherNonClientFrameViewAshTest : public AshTestBase {
- public:
-  HomeLauncherNonClientFrameViewAshTest() = default;
-  ~HomeLauncherNonClientFrameViewAshTest() override = default;
-
-  void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(
-        app_list_features::kEnableHomeLauncher);
-    AshTestBase::SetUp();
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-
-  DISALLOW_COPY_AND_ASSIGN(HomeLauncherNonClientFrameViewAshTest);
-};
-
-// Tests the visibility of the caption button container when
-// kHideCaptionButtonsInTabletModeKey is set.
-TEST_F(HomeLauncherNonClientFrameViewAshTest,
-       TabletModeBrowserCaptionButtonVisibility) {
-  auto* delegate = new NonClientFrameViewAshTestWidgetDelegate();
-  std::unique_ptr<views::Widget> widget = CreateTestWidget(
-      delegate, kShellWindowId_DefaultContainer, gfx::Rect(100, 0, 400, 500));
-  widget->GetNativeWindow()->SetProperty(kHideCaptionButtonsInTabletModeKey,
-                                         true);
-
-  FrameCaptionButtonContainerView* caption_buttons =
-      delegate->non_client_frame_view()
-          ->GetHeaderView()
-          ->caption_button_container();
-
-  EXPECT_TRUE(caption_buttons->visible());
-  ash::Shell* shell = ash::Shell::Get();
-  ash::TabletModeController* tablet_mode_controller =
-      shell->tablet_mode_controller();
-  tablet_mode_controller->EnableTabletModeWindowManager(true);
-  EXPECT_FALSE(caption_buttons->visible());
-
-  shell->window_selector_controller()->ToggleOverview();
-  EXPECT_FALSE(caption_buttons->visible());
-  shell->window_selector_controller()->ToggleOverview();
-  EXPECT_FALSE(caption_buttons->visible());
-
-  tablet_mode_controller->EnableTabletModeWindowManager(false);
-  EXPECT_TRUE(caption_buttons->visible());
-}
-
-// Tests the visibility of the caption button container when
-// kHideCaptionButtonsInTabletModeKey is not set.
-TEST_F(HomeLauncherNonClientFrameViewAshTest,
-       TabletModeAppCaptionButtonVisibility) {
-  auto* delegate = new NonClientFrameViewAshTestWidgetDelegate();
-  std::unique_ptr<views::Widget> widget = CreateTestWidget(
-      delegate, kShellWindowId_DefaultContainer, gfx::Rect(100, 0, 400, 500));
-
-  FrameCaptionButtonContainerView* caption_buttons =
-      delegate->non_client_frame_view()
-          ->GetHeaderView()
-          ->caption_button_container();
-
-  EXPECT_TRUE(caption_buttons->visible());
-  ash::Shell* shell = ash::Shell::Get();
-  ash::TabletModeController* tablet_mode_controller =
-      shell->tablet_mode_controller();
-  tablet_mode_controller->EnableTabletModeWindowManager(true);
-  EXPECT_TRUE(caption_buttons->visible());
-
-  shell->window_selector_controller()->ToggleOverview();
-  EXPECT_FALSE(caption_buttons->visible());
-  shell->window_selector_controller()->ToggleOverview();
-  EXPECT_TRUE(caption_buttons->visible());
-
-  tablet_mode_controller->EnableTabletModeWindowManager(false);
-  EXPECT_TRUE(caption_buttons->visible());
-}
-
 }  // namespace ash
diff --git a/ash/login/ui/lock_screen.cc b/ash/login/ui/lock_screen.cc
index caf4a68..e3e05115 100644
--- a/ash/login/ui/lock_screen.cc
+++ b/ash/login/ui/lock_screen.cc
@@ -16,7 +16,6 @@
 #include "ash/tray_action/tray_action.h"
 #include "ash/wallpaper/wallpaper_controller.h"
 #include "base/command_line.h"
-#include "base/timer/timer.h"
 #include "chromeos/chromeos_switches.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
@@ -25,9 +24,6 @@
 namespace ash {
 namespace {
 
-constexpr base::TimeDelta kShowLoginScreenTimeout =
-    base::TimeDelta::FromSeconds(5);
-
 // Global lock screen instance. There can only ever be on lock screen at a
 // time.
 LockScreen* instance_ = nullptr;
@@ -86,28 +82,18 @@
   }
 
   instance_->window_->set_data_dispatcher(std::move(data_dispatcher));
-  const base::RepeatingClosure show_screen = base::BindRepeating([]() {
-    // |instance_| may already be destroyed in tests.
-    if (!instance_ || instance_->is_shown_)
-      return;
-    instance_->is_shown_ = true;
-    instance_->window_->Show();
-  });
-  if (type == ScreenType::kLogin) {
-    // Postpone showing the login screen after the animation of the first
-    // wallpaper completes, to make the transition smooth.
-    Shell::Get()->wallpaper_controller()->AddFirstWallpaperAnimationEndCallback(
-        show_screen, instance_->window_->GetNativeView());
-    // In case the wallpaper animation takes forever to complete, set a timer to
-    // make sure the login screen is shown eventually. This should never happen,
-    // so use an extra long time-out value to raise awareness.
-    instance_->show_login_screen_fallback_timer_ =
-        std::make_unique<base::OneShotTimer>();
-    instance_->show_login_screen_fallback_timer_->Start(
-        FROM_HERE, kShowLoginScreenTimeout, show_screen);
-  } else {
-    show_screen.Run();
-  }
+  // Postpone showing the screen after the animation of the first wallpaper
+  // completes, to make the transition smooth. The callback will be dispatched
+  // immediately if the animation is already complete (e.g. kLock).
+  Shell::Get()->wallpaper_controller()->AddFirstWallpaperAnimationEndCallback(
+      base::BindOnce([]() {
+        // |instance_| may already be destroyed in tests.
+        if (!instance_ || instance_->is_shown_)
+          return;
+        instance_->is_shown_ = true;
+        instance_->window_->Show();
+      }),
+      instance_->window_->GetNativeView());
 }
 
 // static
diff --git a/ash/login/ui/lock_screen.h b/ash/login/ui/lock_screen.h
index 718044c..3377acb 100644
--- a/ash/login/ui/lock_screen.h
+++ b/ash/login/ui/lock_screen.h
@@ -5,18 +5,12 @@
 #ifndef ASH_LOGIN_UI_LOCK_SCREEN_H_
 #define ASH_LOGIN_UI_LOCK_SCREEN_H_
 
-#include <memory>
-
 #include "ash/ash_export.h"
 #include "ash/session/session_observer.h"
 #include "ash/tray_action/tray_action_observer.h"
 #include "base/macros.h"
 #include "base/scoped_observer.h"
 
-namespace base {
-class OneShotTimer;
-}
-
 namespace ash {
 
 class LockContentsView;
@@ -89,10 +83,6 @@
   // Unowned pointer to the LockContentsView hosted in lock window.
   LockContentsView* contents_view_ = nullptr;
 
-  // The fallback timer that ensures the login screen is shown in case the first
-  // wallpaper animation takes an extra long time to complete.
-  std::unique_ptr<base::OneShotTimer> show_login_screen_fallback_timer_;
-
   bool is_shown_ = false;
 
   ScopedObserver<TrayAction, TrayActionObserver> tray_action_observer_{this};
diff --git a/ash/login/ui/login_keyboard_test_base.cc b/ash/login/ui/login_keyboard_test_base.cc
index 2ab29e3..680e38b9 100644
--- a/ash/login/ui/login_keyboard_test_base.cc
+++ b/ash/login/ui/login_keyboard_test_base.cc
@@ -73,6 +73,8 @@
 void LoginKeyboardTestBase::ShowLockScreen() {
   GetSessionControllerClient()->SetSessionState(
       session_manager::SessionState::LOCKED);
+  // The lock screen can't be shown without a wallpaper.
+  Shell::Get()->wallpaper_controller()->ShowDefaultWallpaperForTesting();
 
   base::Optional<bool> result;
   login_controller_->ShowLockScreen(base::BindOnce(
diff --git a/ash/manifest.json b/ash/manifest.json
index bb10a14..fd53062 100644
--- a/ash/manifest.json
+++ b/ash/manifest.json
@@ -20,7 +20,6 @@
           "ash.mojom.AssistantController",
           "ash.mojom.AssistantVolumeControl",
           "ash.mojom.CastConfig",
-          "ash.mojom.ClientImageRegistry",
           "ash.mojom.CrosDisplayConfigController",
           "ash.mojom.DockedMagnifierController",
           "ash.mojom.EventRewriterController",
diff --git a/ash/metrics/user_metrics_recorder.cc b/ash/metrics/user_metrics_recorder.cc
index 64a4de7..44b9a93 100644
--- a/ash/metrics/user_metrics_recorder.cc
+++ b/ash/metrics/user_metrics_recorder.cc
@@ -13,6 +13,7 @@
 #include "ash/public/cpp/shelf_item.h"
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/public/cpp/shell_window_ids.h"
+#include "ash/public/interfaces/accessibility_controller.mojom-shared.h"
 #include "ash/public/interfaces/window_state_type.mojom.h"
 #include "ash/session/session_controller.h"
 #include "ash/shelf/shelf.h"
@@ -200,9 +201,9 @@
 
 // static
 void UserMetricsRecorder::RecordUserToggleDictation(
-    DictationToggleMethod method) {
+    mojom::DictationToggleSource source) {
   UMA_HISTOGRAM_ENUMERATION("Accessibility.CrosDictation.ToggleDictationMethod",
-                            method);
+                            source);
 }
 
 void UserMetricsRecorder::RecordUserMetricsAction(UserMetricsAction action) {
diff --git a/ash/metrics/user_metrics_recorder.h b/ash/metrics/user_metrics_recorder.h
index 9bb825e..4c8a4f7 100644
--- a/ash/metrics/user_metrics_recorder.h
+++ b/ash/metrics/user_metrics_recorder.h
@@ -16,19 +16,14 @@
 
 namespace ash {
 
+namespace mojom {
+enum class DictationToggleSource;
+}  // namespace mojom
+
 class DemoSessionMetricsRecorder;
 class DesktopTaskSwitchMetricRecorder;
 class PointerMetricsRecorder;
 
-// CrosDictationStartDictationMethod enum values.
-// These values are persisted to logs and should not be renumbered or re-used.
-// See tools/metrics/histograms/enums.xml.
-enum class DictationToggleMethod {
-  kToggleByKeyboard,
-  kToggleByButton,
-  kMaxValue = kToggleByButton
-};
-
 // User Metrics Recorder provides a repeating callback (RecordPeriodicMetrics)
 // on a timer to allow recording of state data over time to the UMA records.
 // Any additional states (in ash) that require monitoring can be added to
@@ -51,7 +46,7 @@
       LoginMetricsRecorder::ShelfButtonClickTarget target);
 
   // Record the method used to activate dictation.
-  static void RecordUserToggleDictation(DictationToggleMethod method);
+  static void RecordUserToggleDictation(mojom::DictationToggleSource source);
 
   // Records an Ash owned user action.
   void RecordUserMetricsAction(UserMetricsAction action);
diff --git a/ash/mojo_interface_factory.cc b/ash/mojo_interface_factory.cc
index 8769de8..a0afe39 100644
--- a/ash/mojo_interface_factory.cc
+++ b/ash/mojo_interface_factory.cc
@@ -12,7 +12,6 @@
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/assistant/assistant_controller.h"
 #include "ash/cast_config_controller.h"
-#include "ash/client_image_registry.h"
 #include "ash/display/ash_display_controller.h"
 #include "ash/display/cros_display_config.h"
 #include "ash/display/display_output_protection.h"
@@ -105,11 +104,6 @@
   Shell::Get()->cast_config()->BindRequest(std::move(request));
 }
 
-void BindClientImageRegistryRequestOnMainThread(
-    mojom::ClientImageRegistryRequest request) {
-  Shell::Get()->client_image_registry()->BindRequest(std::move(request));
-}
-
 void BindDisplayOutputProtectionRequestOnMainThread(
     mojom::DisplayOutputProtectionRequest request) {
   Shell::Get()->display_output_protection()->BindRequest(std::move(request));
@@ -258,9 +252,6 @@
   registry->AddInterface(base::BindRepeating(&BindCastConfigOnMainThread),
                          main_thread_task_runner);
   registry->AddInterface(
-      base::BindRepeating(&BindClientImageRegistryRequestOnMainThread),
-      main_thread_task_runner);
-  registry->AddInterface(
       base::BindRepeating(&BindDisplayOutputProtectionRequestOnMainThread),
       main_thread_task_runner);
   if (features::IsDockedMagnifierEnabled()) {
diff --git a/ash/public/cpp/frame_header.cc b/ash/public/cpp/frame_header.cc
index 3dff51a..ca35d28 100644
--- a/ash/public/cpp/frame_header.cc
+++ b/ash/public/cpp/frame_header.cc
@@ -164,7 +164,7 @@
 void FrameHeader::SetBackButton(FrameCaptionButton* back_button) {
   back_button_ = back_button;
   if (back_button_) {
-    back_button_->SetColorMode(GetButtonColorMode());
+    back_button_->SetColorMode(button_color_mode_);
     back_button_->SetBackgroundColor(GetCurrentFrameColor());
     back_button_->SetImage(CAPTION_BUTTON_ICON_BACK,
                            FrameCaptionButton::ANIMATE_NO,
@@ -208,12 +208,10 @@
 }
 
 void FrameHeader::UpdateCaptionButtonColors() {
-  auto button_color_mode = GetButtonColorMode();
-
-  caption_button_container_->SetColorMode(button_color_mode);
+  caption_button_container_->SetColorMode(button_color_mode_);
   caption_button_container_->SetBackgroundColor(GetCurrentFrameColor());
   if (back_button_) {
-    back_button_->SetColorMode(button_color_mode);
+    back_button_->SetColorMode(button_color_mode_);
     back_button_->SetBackgroundColor(GetCurrentFrameColor());
   }
 }
@@ -303,11 +301,4 @@
                                  GetHeaderHeight());
 }
 
-FrameCaptionButton::ColorMode FrameHeader::GetButtonColorMode() {
-  return target_widget()->GetNativeWindow()->GetProperty(
-             ash::kFrameIsThemedByHostedAppKey)
-             ? FrameCaptionButton::ColorMode::kThemed
-             : FrameCaptionButton::ColorMode::kDefault;
-}
-
 }  // namespace ash
diff --git a/ash/public/cpp/frame_header.h b/ash/public/cpp/frame_header.h
index 6ea1e75..f7b9011 100644
--- a/ash/public/cpp/frame_header.h
+++ b/ash/public/cpp/frame_header.h
@@ -78,6 +78,10 @@
   // gfx::AnimationDelegate:
   void AnimationProgressed(const gfx::Animation* animation) override;
 
+  void set_button_color_mode(FrameCaptionButton::ColorMode button_color_mode) {
+    button_color_mode_ = button_color_mode;
+  }
+
  protected:
   FrameHeader(views::Widget* target_widget, views::View* view);
 
@@ -127,7 +131,8 @@
 
   gfx::Rect GetTitleBounds() const;
 
-  FrameCaptionButton::ColorMode GetButtonColorMode();
+  FrameCaptionButton::ColorMode button_color_mode_ =
+      FrameCaptionButton::ColorMode::kDefault;
 
   // The widget that the caption buttons act on. This can be different from
   // |view_|'s widget.
diff --git a/ash/public/cpp/mus_property_mirror_ash.cc b/ash/public/cpp/mus_property_mirror_ash.cc
index ced6296c..a63a5773 100644
--- a/ash/public/cpp/mus_property_mirror_ash.cc
+++ b/ash/public/cpp/mus_property_mirror_ash.cc
@@ -85,13 +85,6 @@
   } else if (key == kFrameInactiveColorKey) {
     root_window->SetProperty(kFrameInactiveColorKey,
                              window->GetProperty(kFrameInactiveColorKey));
-  } else if (key == kFrameIsThemedByHostedAppKey) {
-    root_window->SetProperty(kFrameIsThemedByHostedAppKey,
-                             window->GetProperty(kFrameIsThemedByHostedAppKey));
-  } else if (key == kHideCaptionButtonsInTabletModeKey) {
-    root_window->SetProperty(
-        kHideCaptionButtonsInTabletModeKey,
-        window->GetProperty(kHideCaptionButtonsInTabletModeKey));
   } else if (key == kImmersiveImpliedByFullscreen) {
     root_window->SetProperty(
         kImmersiveImpliedByFullscreen,
diff --git a/ash/public/cpp/window_properties.cc b/ash/public/cpp/window_properties.cc
index 910210e..5e3fee34 100644
--- a/ash/public/cpp/window_properties.cc
+++ b/ash/public/cpp/window_properties.cc
@@ -60,17 +60,10 @@
       ws::mojom::WindowManager::kFrameActiveColor_Property,
       aura::PropertyConverter::CreateAcceptAnyValueCallback());
   property_converter->RegisterPrimitiveProperty(
-      kHideCaptionButtonsInTabletModeKey,
-      mojom::kHideCaptionButtonsInTabletMode_Property,
-      aura::PropertyConverter::CreateAcceptAnyValueCallback());
-  property_converter->RegisterPrimitiveProperty(
       kFrameInactiveColorKey,
       ws::mojom::WindowManager::kFrameInactiveColor_Property,
       aura::PropertyConverter::CreateAcceptAnyValueCallback());
   property_converter->RegisterPrimitiveProperty(
-      kFrameIsThemedByHostedAppKey, mojom::kFrameIsThemedByHostedApp_Property,
-      aura::PropertyConverter::CreateAcceptAnyValueCallback());
-  property_converter->RegisterPrimitiveProperty(
       kHideShelfWhenFullscreenKey, mojom::kHideShelfWhenFullscreen_Property,
       aura::PropertyConverter::CreateAcceptAnyValueCallback());
   property_converter->RegisterPrimitiveProperty(
@@ -157,7 +150,6 @@
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kBlockedForAssistantSnapshotKey, false);
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kCanAttachToAnotherWindowKey, true);
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kCanConsumeSystemKeysKey, false);
-DEFINE_UI_CLASS_PROPERTY_KEY(bool, kHideCaptionButtonsInTabletModeKey, false);
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kHideInOverviewKey, false);
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kHideShelfWhenFullscreenKey, true);
 DEFINE_UI_CLASS_PROPERTY_KEY(bool, kImmersiveImpliedByFullscreen, true);
@@ -196,7 +188,6 @@
 DEFINE_UI_CLASS_PROPERTY_KEY(SkColor,
                              kFrameInactiveColorKey,
                              kDefaultFrameColor);
-DEFINE_UI_CLASS_PROPERTY_KEY(bool, kFrameIsThemedByHostedAppKey, false);
 DEFINE_UI_CLASS_PROPERTY_KEY(mojom::WindowPinType,
                              kWindowPinTypeKey,
                              mojom::WindowPinType::NONE);
diff --git a/ash/public/cpp/window_properties.h b/ash/public/cpp/window_properties.h
index 56faf5c..b035d499 100644
--- a/ash/public/cpp/window_properties.h
+++ b/ash/public/cpp/window_properties.h
@@ -68,11 +68,6 @@
 ASH_PUBLIC_EXPORT extern const aura::WindowProperty<bool>* const
     kCanConsumeSystemKeysKey;
 
-// A property to control the visibility of the frame captions buttons when in
-// tablet mode (when not in tablet mode, this property is ignored).
-ASH_PUBLIC_EXPORT extern const aura::WindowProperty<bool>* const
-    kHideCaptionButtonsInTabletModeKey;
-
 // A property key to indicate whether we should hide this window in overview
 // mode and Alt + Tab.
 ASH_PUBLIC_EXPORT extern const aura::WindowProperty<bool>* const
@@ -172,11 +167,6 @@
 ASH_PUBLIC_EXPORT extern const aura::WindowProperty<SkColor>* const
     kFrameInactiveColorKey;
 
-// True when the frame colors were provided by a hosted app, i.e. by a
-// progressive web app manifest.
-ASH_PUBLIC_EXPORT extern const aura::WindowProperty<bool>* const
-    kFrameIsThemedByHostedAppKey;
-
 // A property key to store ash::WindowPinType for a window.
 // When setting this property to PINNED or TRUSTED_PINNED, the window manager
 // will try to fullscreen the window and pin it on the top of the screen. If the
diff --git a/ash/public/interfaces/BUILD.gn b/ash/public/interfaces/BUILD.gn
index 90a2c4e..25c34b3 100644
--- a/ash/public/interfaces/BUILD.gn
+++ b/ash/public/interfaces/BUILD.gn
@@ -24,7 +24,6 @@
     "assistant_setup.mojom",
     "assistant_volume_control.mojom",
     "cast_config.mojom",
-    "client_image_registry.mojom",
     "constants.mojom",
     "cros_display_config.mojom",
     "display_output_protection.mojom",
diff --git a/ash/public/interfaces/accessibility_controller.mojom b/ash/public/interfaces/accessibility_controller.mojom
index 2d502ca..d34a5f8 100644
--- a/ash/public/interfaces/accessibility_controller.mojom
+++ b/ash/public/interfaces/accessibility_controller.mojom
@@ -48,6 +48,22 @@
   FULLSCREEN
 };
 
+// These values are persisted to logs and should not be renumbered or re-used.
+// See tools/metrics/histograms/enums.xml.
+enum DictationToggleSource {
+  // Toggled by the keyboard command (Search + D).
+  kKeyboard,
+
+  // Toggled by the dictation button in the tray.
+  kButton,
+
+  // Switch Access context menu button.
+  kSwitchAccess,
+
+  // Chromevox chrome extension.
+  kChromevox
+};
+
 enum SelectToSpeakState {
   // Select to Speak is not actively selecting text or speaking.
   kSelectToSpeakStateInactive,
@@ -111,6 +127,9 @@
   // Set the delegate used by the Select-to-Speak event handler.
   SetSelectToSpeakEventHandlerDelegate(
       SelectToSpeakEventHandlerDelegate delegate);
+
+  // Starts or stops dictation. Records metrics for toggling via SwitchAccess.
+  ToggleDictationFromSource(DictationToggleSource source);
 };
 
 // Interface for ash to request accessibility service from its client (e.g.
diff --git a/ash/public/interfaces/client_image_registry.mojom b/ash/public/interfaces/client_image_registry.mojom
deleted file mode 100644
index 74aa2c8..0000000
--- a/ash/public/interfaces/client_image_registry.mojom
+++ /dev/null
@@ -1,24 +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.
-
-module ash.mojom;
-
-import "mojo/public/mojom/base/unguessable_token.mojom";
-import "ui/gfx/image/mojo/image.mojom";
-
-interface ClientImageRegistry {
-  // Tells Ash about an image which the client will later refer to by |token|.
-  // This allows clients such as Chrome to repeatedly use or reference the same
-  // image without serializing/deserializing every time. If the token already
-  // references another icon, this will replace it.
-  RegisterImage(mojo_base.mojom.UnguessableToken token,
-                gfx.mojom.ImageSkia icon);
-
-  // Tells Ash that the client which registered the given token and its
-  // associated image will no longer use the image. This should be called at
-  // most once for every unique registered token. In the future, if Ash handles
-  // client restart, this will need to be called automatically for crashed
-  // clients.
-  ForgetImage(mojo_base.mojom.UnguessableToken token);
-};
diff --git a/ash/public/interfaces/window_properties.mojom b/ash/public/interfaces/window_properties.mojom
index f207357..ca01ba6 100644
--- a/ash/public/interfaces/window_properties.mojom
+++ b/ash/public/interfaces/window_properties.mojom
@@ -18,31 +18,19 @@
 const string kCanConsumeSystemKeys_Property =
   "ash:can-consume-system-keys";
 
-// A boolean that tells Ash whether the frame's colors come from a PWA manifest.
-const string kFrameIsThemedByHostedApp_Property =
-    "ash:frame-is-themed-by-hosted-app";
-
-// The color of the text drawn on the frame (i.e. the window title). Only used
-// for tabless browser windows.
-const string kFrameTextColor_Property = "ash:frame-text-color";
-
-// See ash::kHideCaptionButtonsInTabletModeKey.
-const string kHideCaptionButtonsInTabletMode_Property =
-    "ash:hide-caption-buttons-in-tablet-mode";
-
 // True if the shelf should be hidden when this window is put into fullscreen.
 // Exposed because some windows want to explicitly opt-out of this.
 const string kHideShelfWhenFullscreen_Property =
   "ash:hide-shelf-when-fullscreen";
 
-// See ash::kImmseriveImpliedByFullscreen.
+// See ash::kImmersiveImpliedByFullscreen.
 const string kImmersiveImpliedByFullscreen_Property =
   "ash:immersive-implied-by-fullscreen";
 
-// See ash::kImmseriveIsActive.
+// See ash::kImmersiveIsActive.
 const string kImmersiveIsActive_Property = "ash:immersive-is-active";
 
-// See ash::kImmseriveTopContainerBoundsInScreen.
+// See ash::kImmersiveTopContainerBoundsInScreen.
 const string kImmersiveTopContainerBoundsInScreen_Property =
     "ash:immersive-top-container-bounds-in-screen";
 
diff --git a/ash/shelf/app_list_shelf_item_delegate.cc b/ash/shelf/app_list_shelf_item_delegate.cc
index f7ce1a8..1ca93e52 100644
--- a/ash/shelf/app_list_shelf_item_delegate.cc
+++ b/ash/shelf/app_list_shelf_item_delegate.cc
@@ -75,6 +75,8 @@
 
   if (back_action)
     Shell::Get()->app_list_controller()->Back();
+
+  std::move(callback).Run(SHELF_ACTION_APP_LIST_SHOWN, base::nullopt);
 }
 
 void AppListShelfItemDelegate::ExecuteCommand(bool from_context_menu,
diff --git a/ash/shelf/shelf_view.cc b/ash/shelf/shelf_view.cc
index 55ac89de..361b0ab 100644
--- a/ash/shelf/shelf_view.cc
+++ b/ash/shelf/shelf_view.cc
@@ -598,9 +598,9 @@
   // Notify the item of its selection; handle the result in AfterItemSelected.
   model_->GetShelfItemDelegate(item.id)->ItemSelected(
       ui::Event::Clone(event), GetDisplayIdForView(this), LAUNCH_FROM_UNKNOWN,
-      base::Bind(&ShelfView::AfterItemSelected, weak_factory_.GetWeakPtr(),
-                 item, sender, base::Passed(ui::Event::Clone(event)),
-                 ink_drop));
+      base::BindOnce(&ShelfView::AfterItemSelected, weak_factory_.GetWeakPtr(),
+                     item, sender, base::Passed(ui::Event::Clone(event)),
+                     ink_drop));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/ash/shell.cc b/ash/shell.cc
index 113f7a4b..6d2d6b3 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -22,7 +22,6 @@
 #include "ash/assistant/assistant_controller.h"
 #include "ash/autoclick/autoclick_controller.h"
 #include "ash/cast_config_controller.h"
-#include "ash/client_image_registry.h"
 #include "ash/components/tap_visualizer/public/mojom/constants.mojom.h"
 #include "ash/dbus/ash_dbus_services.h"
 #include "ash/detachable_base/detachable_base_handler.h"
@@ -1177,9 +1176,6 @@
   power_button_controller_->OnDisplayModeChanged(
       display_configurator_->cached_displays());
 
-  if (::features::IsUsingWindowService())
-    client_image_registry_ = std::make_unique<ClientImageRegistry>();
-
   drag_drop_controller_ = std::make_unique<DragDropController>();
 
   // |screenshot_controller_| needs to be created (and prepended as a
diff --git a/ash/shell.h b/ash/shell.h
index 58dd502..1f7b854 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -108,7 +108,6 @@
 class BluetoothPowerController;
 class BrightnessControlDelegate;
 class CastConfigController;
-class ClientImageRegistry;
 class DisplayOutputProtection;
 class CrosDisplayConfig;
 class DetachableBaseHandler;
@@ -366,9 +365,6 @@
     return brightness_control_delegate_.get();
   }
   CastConfigController* cast_config() { return cast_config_.get(); }
-  ClientImageRegistry* client_image_registry() {
-    return client_image_registry_.get();
-  }
   service_manager::Connector* connector() { return connector_; }
   CrosDisplayConfig* cros_display_config() {
     return cros_display_config_.get();
@@ -740,7 +736,6 @@
   std::unique_ptr<BacklightsForcedOffSetter> backlights_forced_off_setter_;
   std::unique_ptr<BrightnessControlDelegate> brightness_control_delegate_;
   std::unique_ptr<CastConfigController> cast_config_;
-  std::unique_ptr<ClientImageRegistry> client_image_registry_;
   std::unique_ptr<CrosDisplayConfig> cros_display_config_;
   service_manager::Connector* const connector_;
   std::unique_ptr<DetachableBaseHandler> detachable_base_handler_;
diff --git a/ash/system/accessibility/dictation_button_tray.cc b/ash/system/accessibility/dictation_button_tray.cc
index f133342b..738a22f 100644
--- a/ash/system/accessibility/dictation_button_tray.cc
+++ b/ash/system/accessibility/dictation_button_tray.cc
@@ -6,6 +6,7 @@
 
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/metrics/user_metrics_recorder.h"
+#include "ash/public/interfaces/accessibility_controller.mojom.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shelf/shelf_constants.h"
 #include "ash/shell.h"
@@ -46,11 +47,9 @@
 }
 
 bool DictationButtonTray::PerformAction(const ui::Event& event) {
-  UserMetricsRecorder::RecordUserToggleDictation(
-      DictationToggleMethod::kToggleByButton);
+  Shell::Get()->accessibility_controller()->ToggleDictationFromSource(
+      mojom::DictationToggleSource::kButton);
 
-  Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
-      AcceleratorAction::TOGGLE_DICTATION);
   CheckDictationStatusAndUpdateIcon();
   return true;
 }
diff --git a/ash/wm/non_client_frame_controller.cc b/ash/wm/non_client_frame_controller.cc
index 6e7f171..4f07510 100644
--- a/ash/wm/non_client_frame_controller.cc
+++ b/ash/wm/non_client_frame_controller.cc
@@ -81,15 +81,13 @@
 class WmNativeWidgetAura : public views::NativeWidgetAura {
  public:
   WmNativeWidgetAura(views::internal::NativeWidgetDelegate* delegate,
-                     bool remove_standard_frame,
-                     bool should_ash_control_immersive)
+                     bool remove_standard_frame)
       // The NativeWidget is mirroring the real Widget created in client code.
       // |is_parallel_widget_in_window_manager| is used to indicate this
       : views::NativeWidgetAura(delegate,
                                 true /* is_parallel_widget_in_window_manager */,
                                 Shell::Get()->aura_env()),
-        remove_standard_frame_(remove_standard_frame),
-        should_ash_control_immersive_(should_ash_control_immersive) {}
+        remove_standard_frame_(remove_standard_frame) {}
   ~WmNativeWidgetAura() override = default;
 
   void set_cursor(const ui::Cursor& cursor) { cursor_ = cursor; }
@@ -98,12 +96,8 @@
   views::NonClientFrameView* CreateNonClientFrameView() override {
     // TODO(sky): investigate why we have this. Seems this should be the same
     // as not specifying client area insets.
-    if (remove_standard_frame_)
-      return new EmptyDraggableNonClientFrameView();
-    aura::Window* window = GetNativeView();
-
-    if (!should_ash_control_immersive_) {
-      wm::InstallResizeHandleWindowTargeterForWindow(window);
+    if (remove_standard_frame_) {
+      wm::InstallResizeHandleWindowTargeterForWindow(GetNativeWindow());
       return new EmptyDraggableNonClientFrameView();
     }
 
@@ -129,7 +123,6 @@
 
  private:
   const bool remove_standard_frame_;
-  const bool should_ash_control_immersive_;
 
   // The cursor for this widget. CompoundEventFilter will retrieve this cursor
   // via GetCursor and update the CursorManager's active cursor as appropriate
@@ -225,8 +218,7 @@
   params.opacity = views::Widget::InitParams::OPAQUE_WINDOW;
   params.layer_type = ui::LAYER_SOLID_COLOR;
   WmNativeWidgetAura* native_widget =
-      new WmNativeWidgetAura(widget_, ShouldRemoveStandardFrame(*properties),
-                             ShouldEnableImmersive(*properties));
+      new WmNativeWidgetAura(widget_, ShouldRemoveStandardFrame(*properties));
   window_ = native_widget->GetNativeView();
   window_->SetProperty(kNonClientFrameControllerKey, this);
   window_->SetProperty(kWidgetCreationTypeKey, WidgetCreationType::FOR_CLIENT);
diff --git a/ash/wm/property_util.cc b/ash/wm/property_util.cc
index 1706b18..eeaba30 100644
--- a/ash/wm/property_util.cc
+++ b/ash/wm/property_util.cc
@@ -57,12 +57,6 @@
   return iter != properties.end() && mojo::ConvertTo<bool>(iter->second);
 }
 
-bool ShouldEnableImmersive(const InitProperties& properties) {
-  auto iter =
-      properties.find(ws::mojom::WindowManager::kDisableImmersive_InitProperty);
-  return iter == properties.end() || !mojo::ConvertTo<bool>(iter->second);
-}
-
 void ApplyProperties(
     aura::Window* window,
     aura::PropertyConverter* property_converter,
diff --git a/ash/wm/property_util.h b/ash/wm/property_util.h
index 40cec91..3729c3f42c 100644
--- a/ash/wm/property_util.h
+++ b/ash/wm/property_util.h
@@ -55,8 +55,6 @@
 
 bool ShouldRemoveStandardFrame(const InitProperties& properties);
 
-bool ShouldEnableImmersive(const InitProperties& properties);
-
 // Applies |properties| to |window| using |property_converter|.
 void ApplyProperties(
     aura::Window* window,
diff --git a/ash/wm/splitview/split_view_controller_unittest.cc b/ash/wm/splitview/split_view_controller_unittest.cc
index 14c3b46f..51970b4 100644
--- a/ash/wm/splitview/split_view_controller_unittest.cc
+++ b/ash/wm/splitview/split_view_controller_unittest.cc
@@ -2814,6 +2814,9 @@
   EXPECT_TRUE(window3->IsVisible());
   EXPECT_TRUE(window4->IsVisible());
 
+  if (Shell::Get()->app_list_controller()->IsHomeLauncherEnabledInTabletMode())
+    EXPECT_TRUE(Shell::Get()->app_list_controller()->IsVisible());
+
   // 1) Start dragging |window1|. |window2| is the source window.
   std::unique_ptr<WindowResizer> resizer =
       StartDrag(window1.get(), window2.get());
diff --git a/ash/wm/toplevel_window_event_handler.cc b/ash/wm/toplevel_window_event_handler.cc
index 3501f553..9e0b984 100644
--- a/ash/wm/toplevel_window_event_handler.cc
+++ b/ash/wm/toplevel_window_event_handler.cc
@@ -52,8 +52,7 @@
                                       : ::wm::WINDOW_MOVE_SOURCE_MOUSE;
   if (gesture_target) {
     window->env()->gesture_recognizer()->TransferEventsTo(
-        gesture_target, window,
-        ui::GestureRecognizer::ShouldCancelTouches::DontCancel);
+        gesture_target, window, ui::TransferTouchesBehavior::kDontCancel);
   }
   return wm_toplevel_window_event_handler_.AttemptToStartDrag(
       window, point_in_parent, window_component, source,
diff --git a/ash/wm/window_util.cc b/ash/wm/window_util.cc
index 7f1f85f..fdb039a 100644
--- a/ash/wm/window_util.cc
+++ b/ash/wm/window_util.cc
@@ -97,8 +97,10 @@
 
   bool ShouldUseExtendedBounds(const aura::Window* target) const override {
     // Fullscreen/maximized windows can't be drag-resized.
-    if (GetWindowState(window())->IsMaximizedOrFullscreenOrPinned())
+    if (GetWindowState(window())->IsMaximizedOrFullscreenOrPinned() ||
+        !wm::GetWindowState(target)->CanResize()) {
       return false;
+    }
 
     // The shrunken hit region only applies to children of |window()|.
     return target->parent() == window();
diff --git a/base/allocator/partition_allocator/partition_bucket.cc b/base/allocator/partition_allocator/partition_bucket.cc
index f835953..2ef690a 100644
--- a/base/allocator/partition_allocator/partition_bucket.cc
+++ b/base/allocator/partition_allocator/partition_bucket.cc
@@ -478,11 +478,7 @@
       PartitionExcessiveAllocationSize();
     }
     new_page = PartitionDirectMap(root, flags, size);
-#if !defined(OS_MACOSX)
-    // TODO(https://crbug.com/890752): Remove this when we figure out and fix
-    // whatever is breaking on macOS.
     *is_already_zeroed = true;
-#endif
   } else if (LIKELY(this->SetNewActivePage())) {
     // First, did we find an active page in the active pages list?
     new_page = this->active_pages_head;
@@ -514,11 +510,9 @@
       void* addr = PartitionPage::ToPointer(new_page);
       root->RecommitSystemPages(addr, new_page->bucket->get_bytes_per_span());
       new_page->Reset();
-#if !defined(OS_MACOSX)
-      // TODO(https://crbug.com/890752): Remove this when we figure out and fix
-      // whatever is breaking on macOS.
-      *is_already_zeroed = true;
-#endif
+      // TODO(https://crbug.com/890752): Optimizing here might cause pages to
+      // not be zeroed.
+      // *is_already_zeroed = true;
     }
     DCHECK(new_page);
   } else {
@@ -528,11 +522,9 @@
     if (LIKELY(raw_pages != nullptr)) {
       new_page = PartitionPage::FromPointerNoAlignmentCheck(raw_pages);
       InitializeSlotSpan(new_page);
-#if !defined(OS_MACOSX)
-      // TODO(https://crbug.com/890752): Remove this when we figure out and fix
-      // whatever is breaking on macOS.
-      *is_already_zeroed = true;
-#endif
+      // TODO(https://crbug.com/890752): Optimizing here causes pages to not be
+      // zeroed on at least macOS.
+      // *is_already_zeroed = true;
     }
   }
 
diff --git a/base/allocator/partition_allocator/partition_root_base.h b/base/allocator/partition_allocator/partition_root_base.h
index e1392d0..3ab3be8 100644
--- a/base/allocator/partition_allocator/partition_root_base.h
+++ b/base/allocator/partition_allocator/partition_root_base.h
@@ -142,11 +142,6 @@
   }
   PartitionCookieWriteValue(char_ret + kCookieSize + no_cookie_size);
 #else
-#if defined(OS_MACOSX)
-  // TODO(https://crbug.com/890752): Remove this when we figure out and fix
-  // whatever is breaking on macOS.
-  is_already_zeroed = false;
-#endif
   if (ret && zero_fill && !is_already_zeroed) {
     memset(ret, 0, size);
   }
diff --git a/base/sampling_heap_profiler/poisson_allocation_sampler.cc b/base/sampling_heap_profiler/poisson_allocation_sampler.cc
index e31a641..48a3a19 100644
--- a/base/sampling_heap_profiler/poisson_allocation_sampler.cc
+++ b/base/sampling_heap_profiler/poisson_allocation_sampler.cc
@@ -120,6 +120,15 @@
 // Accumulated bytes towards sample thread local key.
 TLSKey g_accumulated_bytes_tls;
 
+// A boolean used to distinguish first allocation on a thread.
+//   false - first allocation on the thread.
+//   true  - otherwise
+// Since g_accumulated_bytes_tls is initialized with zero the very first
+// allocation on a thread would always trigger the sample, thus skewing the
+// profile towards such allocations. To mitigate that we use the flag to
+// ensure the first allocation is properly accounted.
+TLSKey g_sampling_interval_initialized_tls;
+
 // Controls if sample intervals should not be randomized. Used for testing.
 bool g_deterministic;
 
@@ -294,6 +303,7 @@
     ReentryGuard::Init();
     TLSInit(&g_internal_reentry_guard);
     TLSInit(&g_accumulated_bytes_tls);
+    TLSInit(&g_sampling_interval_initialized_tls);
     return true;
   }();
   ignore_result(init_once);
@@ -411,6 +421,16 @@
 
   TLSSetValue(g_accumulated_bytes_tls, accumulated_bytes);
 
+  if (UNLIKELY(!TLSGetValue(g_sampling_interval_initialized_tls))) {
+    TLSSetValue(g_sampling_interval_initialized_tls, true);
+    // This is the very first allocation on the thread. It always produces an
+    // extra sample because g_accumulated_bytes_tls is initialized with zero
+    // due to TLS semantics.
+    // Make sure we don't count this extra sample.
+    if (!--samples)
+      return;
+  }
+
   if (UNLIKELY(TLSGetValue(g_internal_reentry_guard)))
     return;
 
diff --git a/base/task/task_traits.h b/base/task/task_traits.h
index aafd1187..3b5994f 100644
--- a/base/task/task_traits.h
+++ b/base/task/task_traits.h
@@ -123,15 +123,24 @@
 // Describes immutable metadata for a single task or a group of tasks.
 class BASE_EXPORT TaskTraits {
  private:
-  // ValidTrait ensures TaskTraits' constructor only accepts appropriate types.
-  struct ValidTrait {
-    ValidTrait(TaskPriority) {}
-    ValidTrait(TaskShutdownBehavior) {}
-    ValidTrait(MayBlock) {}
-    ValidTrait(WithBaseSyncPrimitives) {}
-  };
+  using TaskPriorityFilter =
+      trait_helpers::EnumTraitFilter<TaskPriority, TaskPriority::USER_VISIBLE>;
+  using MayBlockFilter = trait_helpers::BooleanTraitFilter<MayBlock>;
+  using TaskShutdownBehaviorFilter =
+      trait_helpers::EnumTraitFilter<TaskShutdownBehavior,
+                                     TaskShutdownBehavior::SKIP_ON_SHUTDOWN>;
+  using WithBaseSyncPrimitivesFilter =
+      trait_helpers::BooleanTraitFilter<WithBaseSyncPrimitives>;
 
  public:
+  // ValidTrait ensures TaskTraits' constructor only accepts appropriate types.
+  struct ValidTrait {
+    ValidTrait(TaskPriority);
+    ValidTrait(TaskShutdownBehavior);
+    ValidTrait(MayBlock);
+    ValidTrait(WithBaseSyncPrimitives);
+  };
+
   // Invoking this constructor without arguments produces TaskTraits that are
   // appropriate for tasks that
   //     (1) don't block (ref. MayBlock() and WithBaseSyncPrimitives()),
@@ -153,49 +162,26 @@
   // constexpr base::TaskTraits other_user_visible_may_block_traits = {
   //     base::MayBlock(), base::TaskPriority::USER_VISIBLE};
   template <class... ArgTypes,
-            class CheckArgumentsAreValidBaseTraits = std::enable_if_t<
-                trait_helpers::AreValidTraits<ValidTrait, ArgTypes...>::value>>
+            class CheckArgumentsAreValid = std::enable_if_t<
+                trait_helpers::AreValidTraits<ValidTrait, ArgTypes...>::value ||
+                trait_helpers::AreValidTraitsForExtension<ArgTypes...>::value>>
   constexpr TaskTraits(ArgTypes... args)
-      : priority_(trait_helpers::GetValueFromArgList(
-            trait_helpers::EnumArgGetter<TaskPriority,
-                                         TaskPriority::USER_VISIBLE>(),
+      : extension_(trait_helpers::GetTaskTraitsExtension(
+            trait_helpers::AreValidTraits<ValidTrait, ArgTypes...>{},
             args...)),
-        shutdown_behavior_(trait_helpers::GetValueFromArgList(
-            trait_helpers::EnumArgGetter<
-                TaskShutdownBehavior,
-                TaskShutdownBehavior::SKIP_ON_SHUTDOWN>(),
-            args...)),
+        priority_(
+            trait_helpers::GetTraitFromArgList<TaskPriorityFilter>(args...)),
+        shutdown_behavior_(
+            trait_helpers::GetTraitFromArgList<TaskShutdownBehaviorFilter>(
+                args...)),
         priority_set_explicitly_(
-            trait_helpers::HasArgOfType<TaskPriority, ArgTypes...>::value),
+            trait_helpers::TraitIsDefined<TaskPriorityFilter>(args...)),
         shutdown_behavior_set_explicitly_(
-            trait_helpers::HasArgOfType<TaskShutdownBehavior,
-                                        ArgTypes...>::value),
-        may_block_(trait_helpers::GetValueFromArgList(
-            trait_helpers::BooleanArgGetter<MayBlock>(),
-            args...)),
-        with_base_sync_primitives_(trait_helpers::GetValueFromArgList(
-            trait_helpers::BooleanArgGetter<WithBaseSyncPrimitives>(),
-            args...)) {}
-
-  // Construct TaskTraits with extension traits. See task_traits_extension.h.
-  template <class... ArgTypes,
-            class AvoidConstructorRedeclaration = void,
-            class CheckArgsContainNonBaseTrait = std::enable_if_t<
-                !trait_helpers::AreValidTraits<ValidTrait, ArgTypes...>::value>>
-  constexpr TaskTraits(ArgTypes... args)
-      // Select those arguments that are valid base TaskTraits and forward them
-      // to the above constructor via a helper constructor.
-      : TaskTraits(std::forward_as_tuple(args...),
-                   trait_helpers::SelectIndices<
-                       trait_helpers::ValidTraitTester<ValidTrait>::IsValid,
-                       ArgTypes...>{}) {
-    // Select all other arguments and try to create an extension with them.
-    extension_ = MakeTaskTraitsExtensionHelper(
-        std::forward_as_tuple(args...),
-        trait_helpers::SelectIndices<
-            trait_helpers::ValidTraitTester<ValidTrait>::IsInvalid,
-            ArgTypes...>{});
-  }
+            trait_helpers::TraitIsDefined<TaskShutdownBehaviorFilter>(args...)),
+        may_block_(trait_helpers::GetTraitFromArgList<MayBlockFilter>(args...)),
+        with_base_sync_primitives_(
+            trait_helpers::GetTraitFromArgList<WithBaseSyncPrimitivesFilter>(
+                args...)) {}
 
   constexpr TaskTraits(const TaskTraits& other) = default;
   TaskTraits& operator=(const TaskTraits& other) = default;
@@ -279,16 +265,6 @@
         with_base_sync_primitives_(left.with_base_sync_primitives_ ||
                                    right.with_base_sync_primitives_) {}
 
-  // Helper constructor which selects those arguments from |args| that are
-  // indicated by the index_sequence and forwards them to the public
-  // constructor. Due to filtering, the indices may be non-contiguous.
-  template <class... ArgTypes, std::size_t... Indices>
-  constexpr TaskTraits(std::tuple<ArgTypes...> args,
-                       std::index_sequence<Indices...>)
-      : TaskTraits(
-            std::get<Indices>(std::forward<std::tuple<ArgTypes...>>(args))...) {
-  }
-
   // Ordered for packing.
   TaskTraitsExtensionStorage extension_;
   TaskPriority priority_;
diff --git a/base/task/task_traits_details.h b/base/task/task_traits_details.h
index 82d971c5..f9a9c5d 100644
--- a/base/task/task_traits_details.h
+++ b/base/task/task_traits_details.h
@@ -5,6 +5,7 @@
 #ifndef BASE_TASK_TASK_TRAITS_DETAILS_H_
 #define BASE_TASK_TASK_TRAITS_DETAILS_H_
 
+#include <initializer_list>
 #include <tuple>
 #include <type_traits>
 #include <utility>
@@ -12,116 +13,147 @@
 namespace base {
 namespace trait_helpers {
 
-// HasArgOfType<CheckedType, ArgTypes...>::value is true iff a type in ArgTypes
-// matches CheckedType.
-template <class...>
-struct HasArgOfType : std::false_type {};
-template <class CheckedType, class FirstArgType, class... ArgTypes>
-struct HasArgOfType<CheckedType, FirstArgType, ArgTypes...>
-    : std::conditional<std::is_same<CheckedType, FirstArgType>::value,
-                       std::true_type,
-                       HasArgOfType<CheckedType, ArgTypes...>>::type {};
+// Checks if any of the elements in |ilist| is true.
+// Similar to std::any_of for the case of constexpr initializer_list.
+inline constexpr bool any_of(std::initializer_list<bool> ilist) {
+  for (auto c : ilist) {
+    if (c)
+      return true;
+  }
+  return false;
+}
 
-// When the following call is made:
-//    GetValueFromArgListImpl(CallFirstTag(), GetterType(), args...);
-// If |args| is empty, the compiler selects the first overload. This overload
-// returns getter.GetDefaultValue(). If |args| is not empty, the compiler
-// prefers using the second overload because the type of the first argument
-// matches exactly. This overload returns getter.GetValueFromArg(first_arg),
-// where |first_arg| is the first element in |args|. If
-// getter.GetValueFromArg(first_arg) isn't defined, the compiler uses the third
-// overload instead. This overload discards the first argument in |args| and
-// makes a recursive call to GetValueFromArgListImpl() with CallFirstTag() as
-// first argument.
+// Checks if all of the elements in |ilist| are true.
+// Similar to std::any_of for the case of constexpr initializer_list.
+inline constexpr bool all_of(std::initializer_list<bool> ilist) {
+  for (auto c : ilist) {
+    if (!c)
+      return false;
+  }
+  return true;
+}
 
-// Tag dispatching.
+// Counts the elements in |ilist| that are equal to |value|.
+// Similar to std::count for the case of constexpr initializer_list.
+template <class T>
+inline constexpr size_t count(std::initializer_list<T> ilist, T value) {
+  size_t c = 0;
+  for (const auto& v : ilist) {
+    c += (v == value);
+  }
+  return c;
+}
+
+// CallFirstTag is an argument tag that helps to avoid ambiguous overloaded
+// functions. When the following call is made:
+//    func(CallFirstTag(), arg...);
+// the compiler will give precedence to an overload candidate that directly
+// takes CallFirstTag. Another overload that takes CallSecondTag will be
+// considered iff the preferred overload candidates were all invalids and
+// therefore discarded.
 struct CallSecondTag {};
 struct CallFirstTag : CallSecondTag {};
 
-// Overload 1: Default value.
-template <class GetterType>
-constexpr typename GetterType::ValueType GetValueFromArgListImpl(
-    CallFirstTag,
-    GetterType getter) {
-  return getter.GetDefaultValue();
+// A trait filter class |TraitFilterType| implements the protocol to get a value
+// of type |ArgType| from an argument list and convert it to a value of type
+// |TraitType|. If the argument list contains an argument of type |ArgType|, the
+// filter class will be instantiated with that argument. If the argument list
+// contains no argument of type |ArgType|, the filter class will be instantiated
+// using the default constructor if available; a compile error is issued
+// otherwise. The filter class must have the conversion operator TraitType()
+// which returns a value of type TraitType.
+
+// |InvalidTrait| is used to return from GetTraitFromArg when the argument is
+// not compatible with the desired trait.
+struct InvalidTrait {};
+
+// Returns an object of type |TraitFilterType| constructed from |arg| if
+// compatible, or |InvalidTrait| otherwise.
+template <class TraitFilterType,
+          class ArgType,
+          class CheckArgumentIsCompatible = std::enable_if_t<
+              std::is_constructible<TraitFilterType, ArgType>::value>>
+constexpr TraitFilterType GetTraitFromArg(CallFirstTag, ArgType arg) {
+  return TraitFilterType(arg);
 }
 
-// Overload 2: Get value from first argument. Check that no argument in |args|
-// has the same type as |first_arg|.
-template <class GetterType,
-          class FirstArgType,
+template <class TraitFilterType, class ArgType>
+constexpr InvalidTrait GetTraitFromArg(CallSecondTag, ArgType arg) {
+  return InvalidTrait();
+}
+
+// Returns an object of type |TraitFilterType| constructed from a compatible
+// argument in |args...|, or default constructed if none of the arguments are
+// compatible. This is the implementation of GetTraitFromArgList() with a
+// disambiguation tag.
+template <class TraitFilterType,
           class... ArgTypes,
-          class TestGetValueFromArgDefined =
-              decltype(std::declval<GetterType>().GetValueFromArg(
-                  std::declval<FirstArgType>()))>
-constexpr typename GetterType::ValueType GetValueFromArgListImpl(
-    CallFirstTag,
-    GetterType getter,
-    const FirstArgType& first_arg,
-    const ArgTypes&... args) {
-  static_assert(!HasArgOfType<FirstArgType, ArgTypes...>::value,
-                "Multiple arguments of the same type were provided to the "
-                "constructor of TaskTraits.");
-  return getter.GetValueFromArg(first_arg);
+          class TestCompatibleArgument = std::enable_if_t<any_of(
+              {std::is_constructible<TraitFilterType, ArgTypes>::value...})>>
+constexpr TraitFilterType GetTraitFromArgListImpl(CallFirstTag,
+                                                  ArgTypes... args) {
+  return std::get<TraitFilterType>(std::make_tuple(
+      GetTraitFromArg<TraitFilterType>(CallFirstTag(), args)...));
 }
 
-// Overload 3: Discard first argument.
-template <class GetterType, class FirstArgType, class... ArgTypes>
-constexpr typename GetterType::ValueType GetValueFromArgListImpl(
-    CallSecondTag,
-    GetterType getter,
-    const FirstArgType&,
-    const ArgTypes&... args) {
-  return GetValueFromArgListImpl(CallFirstTag(), getter, args...);
+template <class TraitFilterType, class... ArgTypes>
+constexpr TraitFilterType GetTraitFromArgListImpl(CallSecondTag,
+                                                  ArgTypes... args) {
+  static_assert(std::is_constructible<TraitFilterType>::value,
+                "TaskTraits contains a Trait that must be explicity "
+                "initialized in its constructor.");
+  return TraitFilterType();
 }
 
-// If there is an argument |arg_of_type| of type Getter::ArgType in |args|,
-// returns getter.GetValueFromArg(arg_of_type). If there are more than one
-// argument of type Getter::ArgType in |args|, generates a compile-time error.
-// Otherwise, returns getter.GetDefaultValue().
-//
-// |getter| must provide:
-//     ValueType:
-//         The return type of GetValueFromArgListImpl().
-//
-//     ArgType:
-//         The type of the argument from which GetValueFromArgListImpl() derives
-//         its return value.
-//
-//     ValueType GetValueFromArg(ArgType):
-//         Converts an argument of type ArgType into a value returned by
-//         GetValueFromArgListImpl().
-//
-// |getter| may provide:
-//     ValueType GetDefaultValue():
-//         Returns the value returned by GetValueFromArgListImpl() if none of
-//         its arguments is of type ArgType. If this method is not provided,
-//         compilation will fail when no argument of type ArgType is provided.
-template <class GetterType, class... ArgTypes>
-constexpr typename GetterType::ValueType GetValueFromArgList(
-    GetterType getter,
-    const ArgTypes&... args) {
-  return GetValueFromArgListImpl(CallFirstTag(), getter, args...);
+// Constructs an object of type |TraitFilterType| from a compatible argument in
+// |args...|, or using the default constructor, and returns its associated trait
+// value using conversion to |TraitFilterType::ValueType|. If there are more
+// than one compatible argument in |args|, generates a compile-time error.
+template <class TraitFilterType, class... ArgTypes>
+constexpr typename TraitFilterType::ValueType GetTraitFromArgList(
+    ArgTypes... args) {
+  static_assert(
+      count({std::is_constructible<TraitFilterType, ArgTypes>::value...},
+            true) <= 1,
+      "Multiple arguments of the same type were provided to the "
+      "constructor of TaskTraits.");
+  return GetTraitFromArgListImpl<TraitFilterType>(CallFirstTag(), args...);
 }
 
+// Returns true if this trait is explicitly defined in an argument list, i.e.
+// there is an argument compatible with this trait in |args...|.
+template <class TraitFilterType, class... ArgTypes>
+constexpr bool TraitIsDefined(ArgTypes... args) {
+  return any_of({std::is_constructible<TraitFilterType, ArgTypes>::value...});
+}
+
+// Helper class to implemnent a |TraitFilterType|.
+template <typename T>
+struct BasicTraitFilter {
+  using ValueType = T;
+
+  constexpr operator ValueType() const { return value; }
+
+  ValueType value = {};
+};
+
 template <typename ArgType>
-struct BooleanArgGetter {
-  using ValueType = bool;
-  constexpr ValueType GetValueFromArg(ArgType) const { return true; }
-  constexpr ValueType GetDefaultValue() const { return false; }
+struct BooleanTraitFilter : public BasicTraitFilter<bool> {
+  constexpr BooleanTraitFilter() { this->value = false; }
+  constexpr BooleanTraitFilter(ArgType) { this->value = true; }
 };
 
 template <typename ArgType, ArgType DefaultValue>
-struct EnumArgGetter {
-  using ValueType = ArgType;
-  constexpr ValueType GetValueFromArg(ArgType arg) const { return arg; }
-  constexpr ValueType GetDefaultValue() const { return DefaultValue; }
+struct EnumTraitFilter : public BasicTraitFilter<ArgType> {
+  constexpr EnumTraitFilter() { this->value = DefaultValue; }
+  constexpr EnumTraitFilter(ArgType arg) { this->value = arg; }
 };
 
+// Tests whether multiple given argtument types are all valid traits according
+// to the provided ValidTraits. To use, define a ValidTraits
 template <typename ArgType>
-struct RequiredEnumArgGetter {
-  using ValueType = ArgType;
-  constexpr ValueType GetValueFromArg(ArgType arg) const { return arg; }
+struct RequiredEnumTraitFilter : public BasicTraitFilter<ArgType> {
+  constexpr RequiredEnumTraitFilter(ArgType arg) { this->value = arg; }
 };
 
 // Tests whether a given trait type is valid or invalid by testing whether it is
@@ -129,82 +161,14 @@
 // type like this:
 //
 // struct ValidTraits {
-//   ValidTraits(MyTrait) {}
+//   ValidTraits(MyTrait);
 //   ...
 // };
-template <class ValidTraits>
-struct ValidTraitTester {
-  template <class TraitType>
-  struct IsValid : std::is_convertible<TraitType, ValidTraits> {};
-
-  template <class TraitType>
-  struct IsInvalid
-      : std::conditional_t<std::is_convertible<TraitType, ValidTraits>::value,
-                           std::false_type,
-                           std::true_type> {};
-};
-
-// Tests if a given trait type is valid according to the provided ValidTraits.
-template <class ValidTraits, class TraitType>
-struct IsValidTrait
-    : ValidTraitTester<ValidTraits>::template IsValid<TraitType> {};
-
-// Tests whether multiple given traits types are all valid according to the
-// provided ValidTraits.
-template <class ValidTraits, class...>
-struct AreValidTraits : std::true_type {};
-
-template <class ValidTraits, class NextType, class... Rest>
-struct AreValidTraits<ValidTraits, NextType, Rest...>
-    : std::conditional<IsValidTrait<ValidTraits, NextType>::value,
-                       AreValidTraits<ValidTraits, Rest...>,
-                       std::false_type>::type {};
-
-// Helper struct that recursively builds up an index_sequence containing all
-// those indexes of elements in Args for which the |Predicate<Arg>::value| is
-// true.
-template <template <class> class Predicate,
-          std::size_t CurrentIndex,
-          class Output,
-          class... Args>
-struct SelectIndicesHelper;
-
-template <template <class> class Predicate,
-          std::size_t CurrentIndex,
-          std::size_t... Indices,
-          class First,
-          class... Rest>
-struct SelectIndicesHelper<Predicate,
-                           CurrentIndex,
-                           std::index_sequence<Indices...>,
-                           First,
-                           Rest...>
-    : std::conditional_t<
-          Predicate<First>::value,
-          // Push the index into the sequence and recurse.
-          SelectIndicesHelper<Predicate,
-                              CurrentIndex + 1,
-                              std::index_sequence<Indices..., CurrentIndex>,
-                              Rest...>,
-          // Skip the index and recurse.
-          SelectIndicesHelper<Predicate,
-                              CurrentIndex + 1,
-                              std::index_sequence<Indices...>,
-                              Rest...>> {};
-
-template <template <class> class Predicate,
-          std::size_t CurrentIndex,
-          class Sequence>
-struct SelectIndicesHelper<Predicate, CurrentIndex, Sequence> {
-  using type = Sequence;
-};
-
-// Selects the indices of elements in the |Args| list for which
-// |Predicate<Arg>::value| is |true|.
-template <template <class> class Predicate, class... Args>
-using SelectIndices =
-    typename SelectIndicesHelper<Predicate, 0, std::index_sequence<>, Args...>::
-        type;
+template <class ValidTraits, class... ArgTypes>
+struct AreValidTraits
+    : std::integral_constant<
+          bool,
+          all_of({std::is_convertible<ArgTypes, ValidTraits>::value...})> {};
 
 }  // namespace trait_helpers
 }  // namespace base
diff --git a/base/task/task_traits_extension.h b/base/task/task_traits_extension.h
index ad381883..b90625a 100644
--- a/base/task/task_traits_extension.h
+++ b/base/task/task_traits_extension.h
@@ -12,6 +12,7 @@
 #include <utility>
 
 #include "base/base_export.h"
+#include "base/task/task_traits_details.h"
 
 namespace base {
 
@@ -41,10 +42,14 @@
 // as its extension traits:
 //  (3) -- template <...>
 //      -- constexpr base::TaskTraitsExtensionStorage MakeTaskTraitsExtension(
-//      --     ArgTypes&&... args).
+//      --     ArgTypes... args).
 //      Constructs and serializes an extension with the given arguments into
-//      a TaskTraitsExtensionStorage and returns it. Should only accept valid
-//      arguments for the extension.
+//      a TaskTraitsExtensionStorage and returns it. When the extension is used,
+//      all traits, including the base ones, are passed to this function in
+//      order make sure TaskTraits constructor only participates in overload
+//      resolution if all traits are valid. As such, this function should only
+//      accept valid task traits recognised by the extension and the base task
+//      traits.
 //
 // EXAMPLE (see also base/task/test_task_traits_extension.h):
 // --------
@@ -57,20 +62,24 @@
 //   static constexpr uint8_t kExtensionId =
 //       TaskTraitsExtensionStorage::kFirstEmbedderExtensionId;
 //
-//   struct ValidTrait {
-//     ValidTrait(MyExtensionTrait) {}
+//   struct ValidTrait : public TaskTraits::ValidTrait {
+//     // Accept base traits in MakeTaskTraitsExtension (see above).
+//     using TaskTraits::ValidTrait::ValidTrait;
+//
+//     ValidTrait(MyExtensionTrait);
 //   };
 //
+//   using MyExtensionTraitFilter =
+//     trait_helpers::EnumTraitFilter<MyExtensionTrait, MyExtensionTrait::kA>;
+//
 //   // Constructor that accepts only valid traits as specified by ValidTraits.
 //   template <class... ArgTypes,
 //             class CheckArgumentsAreValid = std::enable_if_t<
 //                 base::trait_helpers::AreValidTraits<
 //                     ValidTrait, ArgTypes...>::value>>
 //   constexpr MyTaskTraitsExtension(ArgTypes... args)
-//       : my_trait_(base::trait_helpers::GetValueFromArgList(
-//             base::trait_helpers::EnumArgGetter<MyExtensionTrait,
-//                                                MyExtensionTrait::kValue1>(),
-//             args...)) {}
+//       : my_trait_(trait_helpers::GetTraitFromArgList<MyExtensionTraitFilter>(
+//                      args...)) {}
 //
 //   // Serializes MyTaskTraitsExtension into a storage object and returns it.
 //   constexpr base::TaskTraitsExtensionStorage Serialize() const {
@@ -100,8 +109,8 @@
 //               base::trait_helpers::AreValidTraits<
 //                   MyTaskTraitsExtension::ValidTrait, ArgTypes...>::value>>
 // constexpr base::TaskTraitsExtensionStorage MakeTaskTraitsExtension(
-//     ArgTypes&&... args) {
-//   return MyTaskTraitsExtension(std::forward<ArgTypes>(args)...).Serialize();
+//     ArgTypes... args) {
+//   return MyTaskTraitsExtension(args...).Serialize();
 // }
 // }  // namespace my_embedder
 //
@@ -171,6 +180,44 @@
 inline constexpr TaskTraitsExtensionStorage::TaskTraitsExtensionStorage(
     const TaskTraitsExtensionStorage& other) = default;
 
+namespace trait_helpers {
+
+// Helper class whose constructor tests if an extension accepts a list of
+// argument types.
+struct TaskTraitsExtension {
+  template <class... ArgTypes,
+            class CheckCanMakeExtension =
+                decltype(MakeTaskTraitsExtension(std::declval<ArgTypes>()...))>
+  constexpr TaskTraitsExtension(ArgTypes... args) {}
+};
+
+// Tests that that a trait extension accepts all |ArgsTypes...|.
+template <class... ArgTypes>
+struct AreValidTraitsForExtension
+    : std::integral_constant<
+          bool,
+          std::is_constructible<TaskTraitsExtension, ArgTypes...>::value> {};
+
+// Helper function that returns the TaskTraitsExtensionStorage of a
+// serialized extension created with |args...| if there are arguments that are
+// not valid base traits, or a default constructed TaskTraitsExtensionStorage
+// otherwise.
+template <class... ArgTypes>
+constexpr TaskTraitsExtensionStorage GetTaskTraitsExtension(
+    std::true_type base_traits,
+    ArgTypes... args) {
+  return TaskTraitsExtensionStorage();
+}
+
+template <class... ArgTypes>
+constexpr TaskTraitsExtensionStorage GetTaskTraitsExtension(
+    std::false_type base_traits,
+    ArgTypes... args) {
+  return MakeTaskTraitsExtension(args...);
+}
+
+}  // namespace trait_helpers
+
 // TODO(eseckler): Default the comparison operator once C++20 arrives.
 inline bool TaskTraitsExtensionStorage::operator==(
     const TaskTraitsExtensionStorage& other) const {
@@ -180,25 +227,6 @@
   return extension_id == other.extension_id && data == other.data;
 }
 
-// Default implementation of MakeTaskTraitsExtension template function, which
-// doesn't accept any traits and does not create an extension.
-template <class Unused = void>
-constexpr TaskTraitsExtensionStorage MakeTaskTraitsExtension(
-    TaskTraitsExtensionStorage& storage) {
-  return TaskTraitsExtensionStorage();
-}
-
-// Forwards those arguments from |args| that are indicated by the index_sequence
-// to a MakeTaskTraitsExtension() template function, which is provided by the
-// embedder in an unknown namespace; its resolution relies on argument-dependent
-// lookup. Due to filtering, the provided indices may be non-contiguous.
-template <class... ArgTypes, std::size_t... Indices>
-constexpr TaskTraitsExtensionStorage MakeTaskTraitsExtensionHelper(
-    std::tuple<ArgTypes...> args,
-    std::index_sequence<Indices...>) {
-  return MakeTaskTraitsExtension(
-      std::get<Indices>(std::forward<std::tuple<ArgTypes...>>(args))...);
-}
 
 }  // namespace base
 
diff --git a/base/task/task_traits_extension_unittest.cc b/base/task/task_traits_extension_unittest.cc
index 39033aa..12de5c6 100644
--- a/base/task/task_traits_extension_unittest.cc
+++ b/base/task/task_traits_extension_unittest.cc
@@ -9,6 +9,13 @@
 
 namespace base {
 
+TEST(TaskTraitsExtensionTest, NoExtension) {
+  constexpr TaskTraits traits = {};
+
+  EXPECT_EQ(traits.extension_id(),
+            TaskTraitsExtensionStorage::kInvalidExtensionId);
+}
+
 TEST(TaskTraitsExtensionTest, CreateWithOneExtensionTrait) {
   constexpr TaskTraits traits = {TestExtensionEnumTrait::kB};
 
diff --git a/base/task/task_traits_extension_unittest.nc b/base/task/task_traits_extension_unittest.nc
index aa74c33..739e255 100644
--- a/base/task/task_traits_extension_unittest.nc
+++ b/base/task/task_traits_extension_unittest.nc
@@ -15,7 +15,7 @@
 constexpr TaskTraits traits = {MayBlock(), MayBlock()};
 #elif defined(NCTEST_TASK_TRAITS_EXTENSION_MULTIPLE_EXTENSION_TRAITS)  // [r"Multiple arguments of the same type were provided to the constructor of TaskTraits."]
 constexpr TaskTraits traits = {TestExtensionEnumTrait::kB, TestExtensionEnumTrait::kC};
-#elif defined(NCTEST_TASK_TRAITS_EXTENSION_INVALID_TYPE)  // [r"no matching function for call to 'MakeTaskTraitsExtension'"]
+#elif defined(NCTEST_TASK_TRAITS_EXTENSION_INVALID_TYPE)  // [r"no matching constructor for initialization of 'const base::TaskTraits'"]
 constexpr TaskTraits traits = {TestExtensionEnumTrait::kB, 123};
 #elif defined(NCTEST_TASK_TRAITS_EXTENSION_TOO_MUCH_DATA_FOR_STORAGE)  // [r"no matching constructor for initialization of 'base::TaskTraitsExtensionStorage'"]
 constexpr TaskTraitsExtensionStorage TestSerializeTaskTraitsWithTooMuchData() {
diff --git a/base/task/task_traits_unittest.nc b/base/task/task_traits_unittest.nc
index 8eaafa2..aa13b54 100644
--- a/base/task/task_traits_unittest.nc
+++ b/base/task/task_traits_unittest.nc
@@ -24,7 +24,7 @@
 constexpr TaskTraits traits = {TaskShutdownBehavior::BLOCK_SHUTDOWN,
                                MayBlock(),
                                TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
-#elif defined(NCTEST_TASK_TRAITS_INVALID_TYPE)  // [r"no matching function for call to 'MakeTaskTraitsExtension'"]
+#elif defined(NCTEST_TASK_TRAITS_INVALID_TYPE)  // [r"no matching constructor for initialization of 'const base::TaskTraits'"]
 constexpr TaskTraits traits = {TaskShutdownBehavior::BLOCK_SHUTDOWN, true};
 #endif
 
diff --git a/base/task/test_task_traits_extension.h b/base/task/test_task_traits_extension.h
index ec12361..3baed34b 100644
--- a/base/task/test_task_traits_extension.h
+++ b/base/task/test_task_traits_extension.h
@@ -20,21 +20,26 @@
   static constexpr uint8_t kExtensionId =
       TaskTraitsExtensionStorage::kFirstEmbedderExtensionId;
 
-  struct ValidTrait {
-    ValidTrait(TestExtensionEnumTrait) {}
-    ValidTrait(TestExtensionBoolTrait) {}
+  struct ValidTrait : public TaskTraits::ValidTrait {
+    using TaskTraits::ValidTrait::ValidTrait;
+
+    ValidTrait(TestExtensionEnumTrait);
+    ValidTrait(TestExtensionBoolTrait);
   };
 
+  using TestExtensionEnumFilter =
+      trait_helpers::EnumTraitFilter<TestExtensionEnumTrait,
+                                     TestExtensionEnumTrait::kA>;
+  using TestExtensionBoolFilter =
+      trait_helpers::BooleanTraitFilter<TestExtensionBoolTrait>;
+
   template <class... ArgTypes,
             class CheckArgumentsAreValid = std::enable_if_t<
                 trait_helpers::AreValidTraits<ValidTrait, ArgTypes...>::value>>
   constexpr TestTaskTraitsExtension(ArgTypes... args)
-      : enum_trait_(trait_helpers::GetValueFromArgList(
-            trait_helpers::EnumArgGetter<TestExtensionEnumTrait,
-                                         TestExtensionEnumTrait::kA>(),
+      : enum_trait_(trait_helpers::GetTraitFromArgList<TestExtensionEnumFilter>(
             args...)),
-        bool_trait_(trait_helpers::GetValueFromArgList(
-            trait_helpers::BooleanArgGetter<TestExtensionBoolTrait>(),
+        bool_trait_(trait_helpers::GetTraitFromArgList<TestExtensionBoolFilter>(
             args...)) {}
 
   constexpr TaskTraitsExtensionStorage Serialize() const {
@@ -65,8 +70,7 @@
           class = std::enable_if_t<
               trait_helpers::AreValidTraits<TestTaskTraitsExtension::ValidTrait,
                                             ArgTypes...>::value>>
-constexpr TaskTraitsExtensionStorage MakeTaskTraitsExtension(
-    ArgTypes&&... args) {
+constexpr TaskTraitsExtensionStorage MakeTaskTraitsExtension(ArgTypes... args) {
   return TestTaskTraitsExtension(std::forward<ArgTypes>(args)...).Serialize();
 }
 
diff --git a/build/config/fuchsia/fidl_library.gni b/build/config/fuchsia/fidl_library.gni
index 6fbb28b6..5ec90a49 100644
--- a/build/config/fuchsia/fidl_library.gni
+++ b/build/config/fuchsia/fidl_library.gni
@@ -14,12 +14,14 @@
 #   namespace      - (optional) Namespace for the library.
 #   deps           - (optional) List of other fidl_library() targets that this
 #                    FIDL library depends on.
+#   languages      - Generate bindings for the given languages, defaults to
+#                    [ "cpp" ]. "js" also supported.
 #
 # $namespace.$library_name must match the the library name specified in the FIDL
 # files.
 
 template("fidl_library") {
-  forward_variables_from(invoker, [ "namespace" ])
+  forward_variables_from(invoker, [ "namespace", "languages" ])
 
   _library_basename = target_name
   if (defined(invoker.library_name)) {
@@ -35,6 +37,10 @@
     _library_path = _library_basename
   }
 
+  if (!defined(invoker.languages)) {
+    languages = [ "cpp" ]
+  }
+
   _response_file = "$target_gen_dir/$target_name.rsp"
   _json_representation = "$target_gen_dir/${_library_name}.fidl.json"
   _output_gen_dir = "$target_gen_dir/fidl"
@@ -131,6 +137,7 @@
 
   action("${target_name}_cpp_gen") {
     visibility = [ ":${invoker.target_name}" ]
+    forward_variables_from(invoker, [ "testonly" ])
 
     deps = [
       ":${invoker.target_name}_compile",
@@ -162,6 +169,40 @@
     ]
   }
 
+  _output_js_path = "$_output_gen_dir/${_library_path}/js/fidl.js"
+  action("${target_name}_js_gen") {
+    visibility = [ ":${invoker.target_name}" ]
+    forward_variables_from(invoker, [ "testonly" ])
+
+    deps = [
+      ":${invoker.target_name}_compile",
+    ]
+
+    inputs = [
+      # Depend on the SDK hash, to ensure rebuild if the SDK tools change.
+      rebase_path("$fuchsia_sdk/.hash"),
+      _json_representation,
+      "//tools/fuchsia/fidlgen_js/fidl.py",  # The schema helper file.
+    ]
+
+    outputs = [
+      _output_js_path,
+    ]
+
+    script = "//tools/fuchsia/fidlgen_js/gen.py"
+
+    args = [
+      rebase_path(_json_representation),
+      "--output",
+      rebase_path("${_output_js_path}"),
+    ]
+
+    data = []
+    foreach(o, outputs) {
+      data += [ rebase_path(o) ]
+    }
+  }
+
   config("${target_name}_config") {
     visibility = [ ":${invoker.target_name}" ]
     include_dirs = [ _output_gen_dir ]
@@ -185,10 +226,11 @@
     if (!defined(deps)) {
       deps = []
     }
-    deps += [
-      ":${invoker.target_name}_compile",
-      ":${invoker.target_name}_cpp_gen",
-    ]
+    deps += [ ":${invoker.target_name}_compile" ]
+
+    foreach(language, languages) {
+      deps += [ ":${invoker.target_name}_${language}_gen" ]
+    }
 
     if (!defined(public_deps)) {
       public_deps = []
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedProcessScopeFactory.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedProcessScopeFactory.java
index fb811a5..5be316ce 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedProcessScopeFactory.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedProcessScopeFactory.java
@@ -123,9 +123,10 @@
 
     private static Configuration createConfiguration() {
         return new Configuration.Builder()
-                .put(ConfigKey.FEED_SERVER_HOST, "https://www.google.com")
-                .put(ConfigKey.FEED_SERVER_PATH_AND_PARAMS,
-                        "/httpservice/noretry/NowStreamService/FeedQuery")
+                .put(ConfigKey.FEED_SERVER_ENDPOINT, FeedConfiguration.getFeedServerEndpoint())
+                .put(ConfigKey.FEED_SERVER_METHOD, FeedConfiguration.getFeedServerEndpoint())
+                .put(ConfigKey.FEED_SERVER_RESPONSE_LENGTH_PREFIXED,
+                        FeedConfiguration.getFeedServerReponseLengthPrefixed())
                 .put(ConfigKey.SESSION_LIFETIME_MS, FeedConfiguration.getSessionLifetimeMs())
                 .put(ConfigKey.VIEW_LOG_THRESHOLD, FeedConfiguration.getViewLogThreshold())
                 .put(ConfigKey.LOGGING_IMMEDIATE_CONTENT_THRESHOLD_MS,
diff --git a/chrome/android/java/res/layout/explore_sites_category_card_view.xml b/chrome/android/java/res/layout/explore_sites_category_card_view.xml
index 210452c2..d4eb720c4 100644
--- a/chrome/android/java/res/layout/explore_sites_category_card_view.xml
+++ b/chrome/android/java/res/layout/explore_sites_category_card_view.xml
@@ -8,8 +8,10 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:gravity="center_horizontal"
     android:orientation="vertical"
-    android:gravity="center_horizontal" >
+    android:paddingVertical="@dimen/explore_sites_category_padding" >
+
 
     <TextView
         android:id="@+id/category_title"
@@ -20,10 +22,11 @@
 
     <GridLayout
         android:id="@+id/category_sites"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
         android:orientation="horizontal"
         android:columnCount="4"
-        android:rowCount="2" />
+        android:columnOrderPreserved="true" />
 
 </org.chromium.chrome.browser.explore_sites.ExploreSitesCategoryCardView>
diff --git a/chrome/android/java/res/layout/explore_sites_page_layout.xml b/chrome/android/java/res/layout/explore_sites_page_layout.xml
index 0847921..e9255e41 100644
--- a/chrome/android/java/res/layout/explore_sites_page_layout.xml
+++ b/chrome/android/java/res/layout/explore_sites_page_layout.xml
@@ -8,6 +8,6 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/explore_sites_category_recycler"
     android:scrollbars="vertical"
-    android:gravity="center_horizontal"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" />
+    android:layout_height="wrap_content"
+    android:padding="@dimen/explore_sites_page_padding" />
diff --git a/chrome/android/java/res/layout/explore_sites_tile_view.xml b/chrome/android/java/res/layout/explore_sites_tile_view.xml
index 664599d..8ae3cd9 100644
--- a/chrome/android/java/res/layout/explore_sites_tile_view.xml
+++ b/chrome/android/java/res/layout/explore_sites_tile_view.xml
@@ -7,7 +7,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="@dimen/tile_view_width"
-    android:layout_height="wrap_content">
+    android:layout_height="wrap_content" >
     <include
         layout="@layout/tile_view_modern"
         android:layout_width="match_parent"
diff --git a/chrome/android/java/res/layout/explore_sites_title_card.xml b/chrome/android/java/res/layout/explore_sites_title_card.xml
new file mode 100644
index 0000000..39febb15
--- /dev/null
+++ b/chrome/android/java/res/layout/explore_sites_title_card.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+    <TextView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:textAppearance="@style/BlackHeadline1"
+        android:text="@string/explore_sites_title" />
+
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 042e90c..8d194b9 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -350,6 +350,10 @@
     <dimen name="md_incognito_ntp_line_spacing">6sp</dimen>
     <dimen name="md_incognito_ntp_padding_left">16dp</dimen>
 
+    <!-- Explore sites page -->
+    <dimen name="explore_sites_category_padding">12dp</dimen>
+    <dimen name="explore_sites_page_padding">12dp</dimen>
+
     <!-- Recent tabs page -->
     <dimen name="recent_tabs_visible_separator_padding">8dp</dimen>
     <dimen name="recent_tabs_group_view_vertical_padding">8dp</dimen>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuPropertiesDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuPropertiesDelegate.java
index 6388f51..40d1ee98 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuPropertiesDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuPropertiesDelegate.java
@@ -7,8 +7,6 @@
 import android.content.Context;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
 import android.os.SystemClock;
 import android.support.annotation.Nullable;
 import android.text.TextUtils;
@@ -17,7 +15,6 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.CommandLine;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.metrics.RecordHistogram;
@@ -143,14 +140,6 @@
                 if (offlineMenuItem != null) {
                     offlineMenuItem.setEnabled(
                             DownloadUtils.isAllowedToDownloadPage(currentTab));
-
-                    Drawable drawable = offlineMenuItem.getIcon();
-                    if (drawable != null) {
-                        int iconTint = ApiCompatibilityUtils.getColor(
-                                mActivity.getResources(), R.color.default_icon_color);
-                        drawable.mutate();
-                        drawable.setColorFilter(iconTint, PorterDuff.Mode.SRC_ATOP);
-                    }
                 }
             }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java
index d6c633f..4c53444 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java
@@ -21,6 +21,7 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.resources.ResourceManager;
 import org.chromium.ui.resources.dynamics.BitmapDynamicResource;
 import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
@@ -121,8 +122,9 @@
     private String getUpdatedTitleInternal(Tab tab, String titleString,
             boolean fetchFaviconFromHistory) {
         final int tabId = tab.getId();
-        boolean isDarkTheme = tab.isIncognito()
-                && !ChromeFeatureList.isEnabled(ChromeFeatureList.HORIZONTAL_TAB_SWITCHER_ANDROID);
+        boolean isHTSEnabled = !DeviceFormFactor.isNonMultiDisplayContextOnTablet(tab.getActivity())
+                && ChromeFeatureList.isEnabled(ChromeFeatureList.HORIZONTAL_TAB_SWITCHER_ANDROID);
+        boolean isDarkTheme = tab.isIncognito() && !isHTSEnabled;
         Bitmap originalFavicon = tab.getFavicon();
         if (originalFavicon == null) {
             originalFavicon = mDefaultFaviconHelper.getDefaultFaviconBitmap(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardAdapter.java
index 127a75f..753d15a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardAdapter.java
@@ -7,9 +7,7 @@
 import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
-import android.widget.TextView;
 
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.modelutil.ForwardingListObservable;
 import org.chromium.chrome.browser.modelutil.PropertyKey;
 import org.chromium.chrome.browser.modelutil.PropertyModel;
@@ -87,14 +85,7 @@
     @Override
     public void onBindViewHolder(CategoryCardViewHolderFactory.CategoryCardViewHolder holder,
             int position, @Nullable Void payload) {
-        if (holder.getItemViewType() == ViewType.HEADER) {
-            TextView view = (TextView) holder.itemView;
-            view.setText(R.string.explore_sites_title);
-        } else if (holder.getItemViewType() == ViewType.ERROR) {
-            // populate the error view
-        } else if (holder.getItemViewType() == ViewType.LOADING) {
-            // Populate loading view
-        } else {
+        if (holder.getItemViewType() == ViewType.CATEGORY) {
             ExploreSitesCategoryCardView view = (ExploreSitesCategoryCardView) holder.itemView;
             // Position - 1 because there is always title.
             view.setCategory(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardViewHolderFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardViewHolderFactory.java
index b62b3b7b..223babc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardViewHolderFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardViewHolderFactory.java
@@ -29,12 +29,14 @@
         View view;
         switch (viewType) {
             case CategoryCardAdapter.ViewType.HEADER:
-                TextView titleView = new TextView(parent.getContext());
-                view = new TextView(parent.getContext());
+                view = LayoutInflater.from(parent.getContext())
+                               .inflate(R.layout.explore_sites_title_card, parent,
+                                       /* attachToRoot = */ false);
                 break;
             case CategoryCardAdapter.ViewType.CATEGORY:
-                view = (ExploreSitesCategoryCardView) LayoutInflater.from(parent.getContext())
-                               .inflate(R.layout.explore_sites_category_card_view, null);
+                view = LayoutInflater.from(parent.getContext())
+                               .inflate(R.layout.explore_sites_category_card_view, parent,
+                                       /* attachToRoot = */ false);
                 break;
             case CategoryCardAdapter.ViewType.LOADING: // inflate loading spinny
             case CategoryCardAdapter.ViewType.ERROR: // inflate error
@@ -46,4 +48,4 @@
         }
         return new CategoryCardViewHolder(view);
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java
index ec7e27a..298ba35 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java
@@ -29,6 +29,8 @@
  * View for a category name and site tiles.
  */
 public class ExploreSitesCategoryCardView extends LinearLayout {
+    private static final int MAX_TILE_COUNT = 8;
+
     private TextView mTitleView;
     private GridLayout mTileView;
     private RoundedIconGenerator mIconGenerator;
@@ -107,19 +109,25 @@
     }
 
     public void updateTileViews(List<ExploreSitesSite> sites) {
-        // Remove extra views if too many.
+        // Remove extra tiles if too many.
         if (mTileView.getChildCount() > sites.size()) {
             mTileView.removeViews(sites.size(), mTileView.getChildCount() - sites.size());
         }
-        // Add views if too few.
-        if (mTileView.getChildCount() < sites.size()) {
-            for (int i = mTileView.getChildCount(); i < sites.size(); i++) {
+
+        // Maximum number of sites to show.
+        int tileMax = Math.min(MAX_TILE_COUNT, sites.size());
+
+        // Add tiles if too few
+        if (mTileView.getChildCount() < tileMax) {
+            for (int i = mTileView.getChildCount(); i < tileMax; i++) {
                 mTileView.addView(LayoutInflater.from(getContext())
-                                          .inflate(R.layout.explore_sites_tile_view, null));
+                                          .inflate(R.layout.explore_sites_tile_view, mTileView,
+                                                  /* attachToRoot = */ false));
             }
         }
-        // Initialize all the tiles again to update.
-        for (int i = 0; i < sites.size(); i++) {
+
+        // Initialize all the non-empty tiles again to update.
+        for (int i = 0; i < tileMax; i++) {
             ExploreSitesTileView tileView = (ExploreSitesTileView) mTileView.getChildAt(i);
             final ExploreSitesSite site = sites.get(i);
             tileView.initialize(site, mIconGenerator);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageNavigationDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageNavigationDelegateImpl.java
index d715ceb..9f79a01 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageNavigationDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageNavigationDelegateImpl.java
@@ -26,6 +26,7 @@
  * {@link NativePageNavigationDelegate} implementation.
  */
 public class NativePageNavigationDelegateImpl implements NativePageNavigationDelegate {
+    private static final String TAG = "PageNavDelegate";
     private final Profile mProfile;
     private final TabModelSelector mTabModelSelector;
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedAppLifecycleTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedAppLifecycleTest.java
index e4ee217..79ad5024 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedAppLifecycleTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedAppLifecycleTest.java
@@ -29,6 +29,7 @@
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.FlakyTest;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -87,6 +88,7 @@
     @Test
     @SmallTest
     @Feature({"InterestFeedContentSuggestions"})
+    @FlakyTest(message = "http://crbug.com/891419")
     public void construction_checks_active_tabbed_activities() {
         verify(mAppLifecycleListener, times(1)).onEnterForeground();
     }
@@ -94,6 +96,7 @@
     @Test
     @SmallTest
     @Feature({"InterestFeedContentSuggestions"})
+    @FlakyTest(message = "http://crbug.com/891419")
     public void activity_state_changes_increment_state_counters()
             throws InterruptedException, TimeoutException {
         assertEquals(0,
@@ -119,6 +122,7 @@
     @Test
     @SmallTest
     @Feature({"InterestFeedContentSuggestions"})
+    @FlakyTest(message = "http://crbug.com/891419")
     public void ntp_opening_triggers_initialize_only_once() throws InterruptedException {
         // We open to about:blank initially so we shouldn't have called initialize() yet.
         verify(mAppLifecycleListener, times(0)).initialize();
@@ -137,6 +141,7 @@
     @Test
     @SmallTest
     @Feature({"InterestFeedContentSuggestions"})
+    @FlakyTest(message = "http://crbug.com/891419")
     public void history_deletion_triggers_clear_all() throws InterruptedException {
         verify(mAppLifecycleListener, times(0)).onClearAll();
         mAppLifecycle.onHistoryDeleted();
@@ -149,6 +154,7 @@
     @Test
     @SmallTest
     @Feature({"InterestFeedContentSuggestions"})
+    @FlakyTest(message = "http://crbug.com/891419")
     public void cached_data_removal_triggers_clear_all() throws InterruptedException {
         verify(mAppLifecycleListener, times(0)).onClearAll();
         mAppLifecycle.onCachedDataCleared();
@@ -161,6 +167,7 @@
     @Test
     @SmallTest
     @Feature({"InterestFeedContentSuggestions"})
+    @FlakyTest(message = "http://crbug.com/891419")
     public void signout_triggers_clear_all() throws InterruptedException {
         verify(mAppLifecycleListener, times(0)).onClearAll();
         mAppLifecycle.onSignedOut();
@@ -173,6 +180,7 @@
     @Test
     @SmallTest
     @Feature({"InterestFeedContentSuggestions"})
+    @FlakyTest(message = "http://crbug.com/891419")
     public void signin_triggers_clear_all() throws InterruptedException {
         verify(mAppLifecycleListener, times(0)).onClearAll();
         mAppLifecycle.onSignedIn();
@@ -185,6 +193,7 @@
     @Test
     @SmallTest
     @Feature({"InterestFeedContentSuggestions"})
+    @FlakyTest(message = "http://crbug.com/891419")
     public void second_window_does_not_trigger_foreground_or_background()
             throws InterruptedException, TimeoutException {
         verify(mAppLifecycleListener, times(1)).onEnterForeground();
@@ -210,6 +219,7 @@
     @Test
     @SmallTest
     @Feature({"InterestFeedContentSuggestions"})
+    @FlakyTest(message = "http://crbug.com/891419")
     public void multi_window_does_not_cause_multiple_initialize() throws InterruptedException {
         mActivityTestRule.loadUrl(UrlConstants.NTP_URL);
         verify(mAppLifecycleListener, times(1)).initialize();
@@ -224,6 +234,7 @@
     @Test
     @SmallTest
     @Feature({"InterestFeedContentSuggestions"})
+    @FlakyTest(message = "http://crbug.com/891419")
     public void resume_triggers_scheduler_foregrounded()
             throws InterruptedException, TimeoutException {
         // Starting mActivity in setUp() triggers a resume.
@@ -260,4 +271,4 @@
 
         waitForStateChangeHelper.waitForCallback(0);
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/app/printing_strings.grdp b/chrome/app/printing_strings.grdp
index 5377be4..d24cda60 100644
--- a/chrome/app/printing_strings.grdp
+++ b/chrome/app/printing_strings.grdp
@@ -80,7 +80,7 @@
       Scale
     </message>
     <message name="IDS_PRINT_PREVIEW_PAGES_PER_SHEET_LABEL" desc="Pages per sheet option label.">
-      Pages Per Sheet
+      Pages per sheet
     </message>
     <message name="IDS_PRINT_PREVIEW_EXAMPLE_PAGE_RANGE_TEXT" desc="Example page range text.">
       e.g. 1-5, 8, 11-13
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index c4df5a7..60fc0a63 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3679,7 +3679,7 @@
      flag_descriptions::kNewNetErrorPageUIDescription, kOsAndroid,
      FEATURE_WITH_PARAMS_VALUE_TYPE(features::kNewNetErrorPageUI,
                                     kNewNetErrorPageUIVariations,
-                                    "NewNetErrorPageUI")},
+                                    "OfflineContentOnDinoPage")},
 #endif  // defined(OS_ANDROID)
 
 #if defined(OS_ANDROID)
diff --git a/chrome/browser/accessibility/accessibility_extension_api.cc b/chrome/browser/accessibility/accessibility_extension_api.cc
index cc84c72..7d36c76 100644
--- a/chrome/browser/accessibility/accessibility_extension_api.cc
+++ b/chrome/browser/accessibility/accessibility_extension_api.cc
@@ -35,6 +35,7 @@
 #include "ui/events/keycodes/keyboard_codes.h"
 
 #if defined(OS_CHROMEOS)
+#include "ash/public/interfaces/accessibility_controller.mojom.h"
 #include "ash/public/interfaces/accessibility_focus_ring_controller.mojom.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/public/interfaces/event_rewriter_controller.mojom.h"
@@ -53,6 +54,17 @@
 
 const char kErrorNotSupported[] = "This API is not supported on this platform.";
 
+#if defined(OS_CHROMEOS)
+ash::mojom::AccessibilityControllerPtr GetAccessibilityController() {
+  // Connect to the accessibility mojo interface in ash.
+  ash::mojom::AccessibilityControllerPtr accessibility_controller;
+  content::ServiceManagerConnection::GetForProcess()
+      ->GetConnector()
+      ->BindInterface(ash::mojom::kServiceName, &accessibility_controller);
+  return accessibility_controller;
+}
+#endif
+
 }  // namespace
 
 ExtensionFunction::ResponseAction
@@ -302,4 +314,20 @@
   return RespondNow(NoArguments());
 }
 
+ExtensionFunction::ResponseAction
+AccessibilityPrivateToggleDictationFunction::Run() {
+  ash::mojom::DictationToggleSource source =
+      ash::mojom::DictationToggleSource::kChromevox;
+  if (extension()->id() == extension_misc::kSwitchAccessExtensionId)
+    source = ash::mojom::DictationToggleSource::kSwitchAccess;
+  else if (extension()->id() == extension_misc::kChromeVoxExtensionId)
+    source = ash::mojom::DictationToggleSource::kChromevox;
+  else
+    NOTREACHED();
+
+  GetAccessibilityController()->ToggleDictationFromSource(source);
+
+  return RespondNow(NoArguments());
+}
+
 #endif  // defined (OS_CHROMEOS)
diff --git a/chrome/browser/accessibility/accessibility_extension_api.h b/chrome/browser/accessibility/accessibility_extension_api.h
index 6726455..dba2f3c 100644
--- a/chrome/browser/accessibility/accessibility_extension_api.h
+++ b/chrome/browser/accessibility/accessibility_extension_api.h
@@ -97,6 +97,17 @@
   DECLARE_EXTENSION_FUNCTION("accessibilityPrivate.onSelectToSpeakStateChanged",
                              ACCESSIBILITY_PRIVATE_ONSELECTTOSPEAKSTATECHANGED)
 };
+
+// API function that is called when a SwitchAccess user toggles Dictation from
+// the context menu.
+class AccessibilityPrivateToggleDictationFunction
+    : public UIThreadExtensionFunction {
+  ~AccessibilityPrivateToggleDictationFunction() override {}
+  ResponseAction Run() override;
+  DECLARE_EXTENSION_FUNCTION("accessibilityPrivate.toggleDictation",
+                             ACCESSIBILITY_PRIVATE_TOGGLEDICTATION)
+};
+
 #endif  // defined (OS_CHROMEOS)
 
 #endif  // CHROME_BROWSER_ACCESSIBILITY_ACCESSIBILITY_EXTENSION_API_H_
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index d077e77..f01dbb2 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -248,7 +248,7 @@
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kContentSuggestionsScrollToLoad{
-    "ContentSuggestionsScrollToLoad", base::FEATURE_ENABLED_BY_DEFAULT};
+    "ContentSuggestionsScrollToLoad", base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kContentSuggestionsThumbnailDominantColor{
     "ContentSuggestionsThumbnailDominantColor",
diff --git a/chrome/browser/android/explore_sites/explore_sites_bridge_experimental.cc b/chrome/browser/android/explore_sites/explore_sites_bridge_experimental.cc
index 0a0783d..fd62e89 100644
--- a/chrome/browser/android/explore_sites/explore_sites_bridge_experimental.cc
+++ b/chrome/browser/android/explore_sites/explore_sites_bridge_experimental.cc
@@ -14,7 +14,7 @@
 #include "chrome/browser/android/explore_sites/catalog.h"
 #include "chrome/browser/android/explore_sites/catalog.pb.h"
 #include "chrome/browser/android/explore_sites/ntp_json_fetcher.h"
-#include "chrome/browser/android/explore_sites/url_util.h"
+#include "chrome/browser/android/explore_sites/url_util_experimental.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/search/suggestions/image_decoder_impl.h"
@@ -113,7 +113,8 @@
 ScopedJavaLocalRef<jstring> JNI_ExploreSitesBridgeExperimental_GetCatalogUrl(
     JNIEnv* env,
     const JavaParamRef<jclass>& jcaller) {
-  return base::android::ConvertUTF8ToJavaString(env, GetCatalogURL().spec());
+  return base::android::ConvertUTF8ToJavaString(
+      env, GetCatalogPrototypeURL().spec());
 }
 
 // static
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
index 4f99d03..186168a 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -17,6 +17,8 @@
 #include "base/single_thread_task_runner.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
+#include "base/test/bind_test_util.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_clock.h"
 #include "base/test/test_timeouts.h"
@@ -79,6 +81,7 @@
 #include "components/password_manager/core/browser/password_manager_test_utils.h"
 #include "components/password_manager/core/browser/password_store_consumer.h"
 #include "components/prefs/testing_pref_service.h"
+#include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browsing_data_filter_builder.h"
 #include "content/public/browser/browsing_data_remover.h"
 #include "content/public/test/browsing_data_remover_test_util.h"
@@ -86,11 +89,12 @@
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/test_utils.h"
 #include "net/cookies/canonical_cookie.h"
-#include "net/cookies/cookie_store.h"
 #include "net/http/http_transaction_factory.h"
 #include "net/net_buildflags.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_test_util.h"
+#include "services/network/public/mojom/cookie_manager.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/favicon_size.h"
 
@@ -255,70 +259,59 @@
 
   // Returns true, if the given cookie exists in the cookie store.
   bool ContainsCookie() {
-    scoped_refptr<content::MessageLoopRunner> message_loop_runner =
-        new content::MessageLoopRunner;
-    quit_closure_ = message_loop_runner->QuitClosure();
-    get_cookie_success_ = false;
-    cookie_store_->GetCookieListWithOptionsAsync(
+    bool result = false;
+    base::RunLoop run_loop;
+    cookie_manager_->GetCookieList(
         kOrigin1, net::CookieOptions(),
-        base::BindOnce(&RemoveCookieTester::GetCookieListCallback,
-                       base::Unretained(this)));
-    message_loop_runner->Run();
-    return get_cookie_success_;
+        base::BindLambdaForTesting([&](const net::CookieList& cookie_list) {
+          std::string cookie_line =
+              net::CanonicalCookie::BuildCookieLine(cookie_list);
+          if (cookie_line == "A=1") {
+            result = true;
+          } else {
+            EXPECT_EQ("", cookie_line);
+            result = false;
+          }
+          run_loop.Quit();
+        }));
+    run_loop.Run();
+    return result;
   }
 
   void AddCookie() {
-    scoped_refptr<content::MessageLoopRunner> message_loop_runner =
-        new content::MessageLoopRunner;
-    quit_closure_ = message_loop_runner->QuitClosure();
-    cookie_store_->SetCookieWithOptionsAsync(
-        kOrigin1, "A=1", net::CookieOptions(),
-        base::BindOnce(&RemoveCookieTester::SetCookieCallback,
-                       base::Unretained(this)));
-    message_loop_runner->Run();
+    base::RunLoop run_loop;
+    auto cookie = net::CanonicalCookie::Create(
+        kOrigin1, "A=1", base::Time::Now(), net::CookieOptions());
+    cookie_manager_->SetCanonicalCookie(
+        *cookie, /*secure_source=*/false, /*modify_http_only=*/false,
+        base::BindLambdaForTesting([&](bool result) {
+          EXPECT_TRUE(result);
+          run_loop.Quit();
+        }));
+    run_loop.Run();
   }
 
  protected:
-  void SetCookieStore(net::CookieStore* cookie_store) {
-    cookie_store_ = cookie_store;
+  void SetCookieManager(network::mojom::CookieManagerPtr cookie_manager) {
+    cookie_manager_ = std::move(cookie_manager);
   }
 
  private:
-  void GetCookieListCallback(const net::CookieList& cookie_list) {
-    std::string cookie_line =
-        net::CanonicalCookie::BuildCookieLine(cookie_list);
-    if (cookie_line == "A=1") {
-      get_cookie_success_ = true;
-    } else {
-      EXPECT_EQ("", cookie_line);
-      get_cookie_success_ = false;
-    }
-    quit_closure_.Run();
-  }
-
-  void SetCookieCallback(bool result) {
-    ASSERT_TRUE(result);
-    quit_closure_.Run();
-  }
-
-  bool get_cookie_success_ = false;
-  base::Closure quit_closure_;
-
-  // CookieStore must out live |this|.
-  net::CookieStore* cookie_store_ = nullptr;
+  network::mojom::CookieManagerPtr cookie_manager_;
 
   DISALLOW_COPY_AND_ASSIGN(RemoveCookieTester);
 };
 
-void RunClosureAfterCookiesCleared(const base::Closure& task,
-                                   uint32_t cookies_deleted) {
-  task.Run();
-}
-
 class RemoveSafeBrowsingCookieTester : public RemoveCookieTester {
  public:
   RemoveSafeBrowsingCookieTester()
       : browser_process_(TestingBrowserProcess::GetGlobal()) {
+    system_request_context_getter_ =
+        base::MakeRefCounted<net::TestURLRequestContextGetter>(
+            base::CreateSingleThreadTaskRunnerWithTraits(
+                {content::BrowserThread::IO}));
+    browser_process_->SetSystemRequestContext(
+        system_request_context_getter_.get());
     scoped_refptr<safe_browsing::SafeBrowsingService> sb_service =
         safe_browsing::SafeBrowsingService::CreateSafeBrowsingService();
     browser_process_->SetSafeBrowsingService(sb_service.get());
@@ -328,13 +321,16 @@
     // Make sure the safe browsing cookie store has no cookies.
     // TODO(mmenke): Is this really needed?
     base::RunLoop run_loop;
-    net::URLRequestContext* request_context =
-        sb_service->url_request_context()->GetURLRequestContext();
-    request_context->cookie_store()->DeleteAllAsync(
-        base::BindOnce(&RunClosureAfterCookiesCleared, run_loop.QuitClosure()));
+    network::mojom::CookieManagerPtr cookie_manager;
+    sb_service->GetNetworkContext()->GetCookieManager(
+        mojo::MakeRequest(&cookie_manager));
+    cookie_manager->DeleteCookies(
+        network::mojom::CookieDeletionFilter::New(),
+        base::BindLambdaForTesting(
+            [&](uint32_t num_deleted) { run_loop.Quit(); }));
     run_loop.Run();
 
-    SetCookieStore(request_context->cookie_store());
+    SetCookieManager(std::move(cookie_manager));
   }
 
   virtual ~RemoveSafeBrowsingCookieTester() {
@@ -345,6 +341,7 @@
 
  private:
   TestingBrowserProcess* browser_process_;
+  scoped_refptr<net::URLRequestContextGetter> system_request_context_getter_;
 
   DISALLOW_COPY_AND_ASSIGN(RemoveSafeBrowsingCookieTester);
 };
diff --git a/chrome/browser/browsing_data/counters/site_data_counting_helper_unittest.cc b/chrome/browser/browsing_data/counters/site_data_counting_helper_unittest.cc
index 064dd960..dc184dff 100644
--- a/chrome/browser/browsing_data/counters/site_data_counting_helper_unittest.cc
+++ b/chrome/browser/browsing_data/counters/site_data_counting_helper_unittest.cc
@@ -4,39 +4,31 @@
 
 #include <algorithm>
 #include <memory>
-#include <set>
 #include <string>
+#include <vector>
 
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/run_loop.h"
 #include "base/task/post_task.h"
+#include "base/test/bind_test_util.h"
 #include "chrome/browser/browsing_data/counters/site_data_counting_helper.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/test/test_browser_thread_bundle.h"
-#include "net/cookies/cookie_store.h"
-#include "net/url_request/url_request_context.h"
-#include "net/url_request/url_request_context_getter.h"
+#include "services/network/public/mojom/cookie_manager.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using content::BrowserThread;
-
 class SiteDataCountingHelperTest : public testing::Test {
  public:
   const int64_t kTimeoutMs = 10;
 
   void SetUp() override {
     profile_.reset(new TestingProfile());
-    run_loop_.reset(new base::RunLoop());
-    tasks_ = 0;
-    cookie_callback_ = base::Bind(&SiteDataCountingHelperTest::CookieCallback,
-                                  base::Unretained(this));
   }
 
   void TearDown() override {
@@ -44,43 +36,35 @@
     base::RunLoop().RunUntilIdle();
   }
 
-  void CookieCallback(int count) {
-    // Negative values represent an unexpected error.
-    DCHECK(count >= 0);
-    last_count_ = count;
-
-    if (run_loop_)
-      run_loop_->Quit();
-  }
-
-  void DoneOnIOThread(bool success) {
-    DCHECK(success);
-    if (--tasks_ > 0)
-      return;
-    base::PostTaskWithTraits(
-        FROM_HERE, {BrowserThread::UI},
-        base::BindOnce(&SiteDataCountingHelperTest::DoneCallback,
-                       base::Unretained(this)));
-  }
-
-  void DoneCallback() { run_loop_->Quit(); }
-
-  void WaitForTasksOnIOThread() {
-    run_loop_->Run();
-    run_loop_.reset(new base::RunLoop());
-  }
-
   void CreateCookies(base::Time creation_time,
                      const std::vector<std::string>& urls) {
     content::StoragePartition* partition =
         content::BrowserContext::GetDefaultStoragePartition(profile());
-    net::URLRequestContextGetter* rq_context =
-        partition->GetURLRequestContext();
-    base::PostTaskWithTraits(
-        FROM_HERE, {BrowserThread::IO},
-        base::BindOnce(&SiteDataCountingHelperTest::CreateCookiesOnIOThread,
-                       base::Unretained(this), base::WrapRefCounted(rq_context),
-                       creation_time, urls));
+    network::mojom::CookieManager* cookie_manager =
+        partition->GetCookieManagerForBrowserProcess();
+
+    base::RunLoop run_loop;
+    int tasks = urls.size();
+
+    int i = 0;
+    for (const std::string& url_string : urls) {
+      GURL url(url_string);
+      // Cookies need a unique creation time.
+      base::Time time = creation_time + base::TimeDelta::FromMilliseconds(i++);
+      std::unique_ptr<net::CanonicalCookie> cookie =
+          net::CanonicalCookie::CreateSanitizedCookie(
+              url, "name", "A=1", url.host(), url.path(), time, base::Time(),
+              time, url.SchemeIsCryptographic(), false,
+              net::CookieSameSite::DEFAULT_MODE, net::COOKIE_PRIORITY_DEFAULT);
+      cookie_manager->SetCanonicalCookie(
+          *cookie, url.SchemeIsCryptographic(), true /*modify_http_only*/,
+          base::BindLambdaForTesting([&](bool result) {
+            if (--tasks == 0)
+              run_loop.Quit();
+          }));
+    }
+
+    run_loop.Run();
   }
 
   void CreateLocalStorage(
@@ -100,60 +84,31 @@
     }
   }
 
-  void CreateCookiesOnIOThread(
-      const scoped_refptr<net::URLRequestContextGetter>& rq_context,
-      base::Time creation_time,
-      std::vector<std::string> urls) {
-    net::CookieStore* cookie_store =
-        rq_context->GetURLRequestContext()->cookie_store();
-
-    tasks_ = urls.size();
-    int i = 0;
-    for (const std::string& url_string : urls) {
-      GURL url(url_string);
-      // Cookies need a unique creation time.
-      base::Time time = creation_time + base::TimeDelta::FromMilliseconds(i++);
-
-      cookie_store->SetCanonicalCookieAsync(
-          net::CanonicalCookie::CreateSanitizedCookie(
-              url, "name", "A=1", url.host(), url.path(), time, base::Time(),
-              time, url.SchemeIsCryptographic(), false,
-              net::CookieSameSite::DEFAULT_MODE, net::COOKIE_PRIORITY_DEFAULT),
-          url.SchemeIsCryptographic(), true /*modify_http_only*/,
-          base::BindOnce(&SiteDataCountingHelperTest::DoneOnIOThread,
-                         base::Unretained(this)));
-    }
-  }
-
-  void CountEntries(base::Time begin_time) {
-    last_count_ = -1;
-    auto* helper =
-        new SiteDataCountingHelper(profile(), begin_time, cookie_callback_);
+  int CountEntries(base::Time begin_time) {
+    base::RunLoop run_loop;
+    int result = -1;
+    auto* helper = new SiteDataCountingHelper(
+        profile(), begin_time, base::BindLambdaForTesting([&](int count) {
+          // Negative values represent an unexpected error.
+          DCHECK_GE(count, 0);
+          result = count;
+          run_loop.Quit();
+        }));
     helper->CountAndDestroySelfWhenFinished();
-  }
+    run_loop.Run();
 
-  int64_t GetResult() {
-    DCHECK_GE(last_count_, 0);
-    return last_count_;
+    return result;
   }
 
   Profile* profile() { return profile_.get(); }
 
  private:
-  base::Callback<void(int)> cookie_callback_;
-  std::unique_ptr<base::RunLoop> run_loop_;
   content::TestBrowserThreadBundle thread_bundle_;
   std::unique_ptr<TestingProfile> profile_;
-
-  int tasks_;
-  int64_t last_count_;
 };
 
 TEST_F(SiteDataCountingHelperTest, CheckEmptyResult) {
-  CountEntries(base::Time());
-  WaitForTasksOnIOThread();
-
-  DCHECK_EQ(0, GetResult());
+  EXPECT_EQ(0, CountEntries(base::Time()));
 }
 
 TEST_F(SiteDataCountingHelperTest, CountCookies) {
@@ -162,26 +117,12 @@
   base::Time yesterday = now - base::TimeDelta::FromDays(1);
 
   CreateCookies(last_hour, {"https://example.com"});
-  WaitForTasksOnIOThread();
-
   CreateCookies(yesterday, {"https://google.com", "https://bing.com"});
-  WaitForTasksOnIOThread();
 
-  CountEntries(base::Time());
-  WaitForTasksOnIOThread();
-  DCHECK_EQ(3, GetResult());
-
-  CountEntries(yesterday);
-  WaitForTasksOnIOThread();
-  DCHECK_EQ(3, GetResult());
-
-  CountEntries(last_hour);
-  WaitForTasksOnIOThread();
-  DCHECK_EQ(1, GetResult());
-
-  CountEntries(now);
-  WaitForTasksOnIOThread();
-  DCHECK_EQ(0, GetResult());
+  EXPECT_EQ(3, CountEntries(base::Time()));
+  EXPECT_EQ(3, CountEntries(yesterday));
+  EXPECT_EQ(1, CountEntries(last_hour));
+  EXPECT_EQ(0, CountEntries(now));
 }
 
 TEST_F(SiteDataCountingHelperTest, LocalStorage) {
@@ -190,9 +131,7 @@
                      {FILE_PATH_LITERAL("https_example.com_443.localstorage"),
                       FILE_PATH_LITERAL("https_bing.com_443.localstorage")});
 
-  CountEntries(base::Time());
-  WaitForTasksOnIOThread();
-  DCHECK_EQ(2, GetResult());
+  EXPECT_EQ(2, CountEntries(base::Time()));
 }
 
 TEST_F(SiteDataCountingHelperTest, CookiesAndLocalStorage) {
@@ -201,11 +140,8 @@
   CreateLocalStorage(now,
                      {FILE_PATH_LITERAL("https_example.com_443.localstorage"),
                       FILE_PATH_LITERAL("https_bing.com_443.localstorage")});
-  WaitForTasksOnIOThread();
 
-  CountEntries(base::Time());
-  WaitForTasksOnIOThread();
-  DCHECK_EQ(3, GetResult());
+  EXPECT_EQ(3, CountEntries(base::Time()));
 }
 
 TEST_F(SiteDataCountingHelperTest, SameHostDifferentScheme) {
@@ -214,8 +150,6 @@
   CreateLocalStorage(now,
                      {FILE_PATH_LITERAL("https_google.com_443.localstorage"),
                       FILE_PATH_LITERAL("http_google.com_80.localstorage")});
-  WaitForTasksOnIOThread();
-  CountEntries(base::Time());
-  WaitForTasksOnIOThread();
-  DCHECK_EQ(1, GetResult());
+
+  EXPECT_EQ(1, CountEntries(base::Time()));
 }
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index c5f05ba8..c79f73f8 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -1494,6 +1494,20 @@
 #endif
 }
 
+network::mojom::URLLoaderFactoryPtrInfo
+ChromeContentBrowserClient::CreateURLLoaderFactoryForNetworkRequests(
+    content::RenderProcessHost* process,
+    network::mojom::NetworkContext* network_context,
+    const url::Origin& request_initiator) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+  return ChromeContentBrowserClientExtensionsPart::
+      CreateURLLoaderFactoryForNetworkRequests(process, network_context,
+                                               request_initiator);
+#else
+  return network::mojom::URLLoaderFactoryPtrInfo();
+#endif
+}
+
 // These are treated as WebUI schemes but do not get WebUI bindings. Also,
 // view-source is allowed for these schemes.
 void ChromeContentBrowserClient::GetAdditionalWebUISchemes(
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index c229f00..30a59ea 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -131,6 +131,11 @@
       const url::Origin& initiator_origin,
       int render_process_id,
       content::ResourceType resource_type) override;
+  network::mojom::URLLoaderFactoryPtrInfo
+  CreateURLLoaderFactoryForNetworkRequests(
+      content::RenderProcessHost* process,
+      network::mojom::NetworkContext* network_context,
+      const url::Origin& request_initiator) override;
   void GetAdditionalWebUISchemes(
       std::vector<std::string>* additional_schemes) override;
   void GetAdditionalViewSourceSchemes(
diff --git a/chrome/browser/chrome_content_browser_manifest_overlay.json b/chrome/browser/chrome_content_browser_manifest_overlay.json
index 2e5854f..3734fee 100644
--- a/chrome/browser/chrome_content_browser_manifest_overlay.json
+++ b/chrome/browser/chrome_content_browser_manifest_overlay.json
@@ -90,13 +90,13 @@
           "chrome.mojom.OpenSearchDocumentDescriptionHandler",
           "chrome.mojom.PrerenderCanceler",
           "chromeos.ime.mojom.InputEngineManager",
+          "chromeos.media_perception.mojom.MediaPerception",
           "contextual_search.mojom.ContextualSearchJsApiService",
           "dom_distiller.mojom.DistillabilityService",
           "dom_distiller.mojom.DistillerJavaScriptService",
           "extensions.KeepAlive",
           "extensions.mime_handler.BeforeUnloadControl",
           "extensions.mime_handler.MimeHandlerService",
-          "extensions.mojom.InlineInstall",
           "media_router.mojom.MediaRouter",
           "page_load_metrics.mojom.PageLoadMetrics",
           "safe_browsing.mojom.PhishingDetectorClient",
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index e74c82e..f1ca707 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -83,6 +83,7 @@
     "//chromeos:cryptohome_proto",
     "//chromeos:cryptohome_signkey_proto",
     "//chromeos:login_manager_proto",
+    "//chromeos:media_perception_proto",
     "//chromeos:metrics_event_proto",
     "//chromeos:oobe_config_proto",
     "//chromeos/assistant:buildflags",
diff --git a/chrome/browser/chromeos/arc/pip/OWNERS b/chrome/browser/chromeos/arc/pip/OWNERS
new file mode 100644
index 0000000..ae6d94c
--- /dev/null
+++ b/chrome/browser/chromeos/arc/pip/OWNERS
@@ -0,0 +1 @@
+edcourtney@chromium.org
diff --git a/chrome/browser/chromeos/crostini/crostini_share_path.cc b/chrome/browser/chromeos/crostini/crostini_share_path.cc
index 27cd4565..8a8d3f8 100644
--- a/chrome/browser/chromeos/crostini/crostini_share_path.cc
+++ b/chrome/browser/chromeos/crostini/crostini_share_path.cc
@@ -76,7 +76,7 @@
   request.mutable_shared_path()->set_writable(true);
   request.set_storage_location(
       vm_tools::seneschal::SharePathRequest::DOWNLOADS);
-  request.set_owner_id(CryptohomeIdForProfile(profile));
+  request.set_owner_id(crostini::CryptohomeIdForProfile(profile));
   chromeos::DBusThreadManager::Get()->GetSeneschalClient()->SharePath(
       request, base::BindOnce(&OnSeneschalSharePathResponse, std::move(path),
                               std::move(callback)));
diff --git a/chrome/browser/chromeos/crostini/crostini_util.cc b/chrome/browser/chromeos/crostini/crostini_util.cc
index 31a2887..010e4af 100644
--- a/chrome/browser/chromeos/crostini/crostini_util.cc
+++ b/chrome/browser/chromeos/crostini/crostini_util.cc
@@ -82,7 +82,7 @@
       browser->window()->Close();
     if (result ==
         crostini::ConciergeClientResult::OFFLINE_WHEN_UPGRADE_REQUIRED) {
-      ShowCrostiniUpgradeView(profile, CrostiniUISurface::kAppList);
+      ShowCrostiniUpgradeView(profile, crostini::CrostiniUISurface::kAppList);
     }
     return;
   }
@@ -210,6 +210,8 @@
 
 }  // namespace
 
+namespace crostini {
+
 void SetCrostiniUIAllowedForTesting(bool enabled) {
   g_crostini_ui_allowed_for_testing = enabled;
 }
@@ -407,3 +409,5 @@
   // If device policy is not set, allow Crostini.
   return true;
 }
+
+}  // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_util.h b/chrome/browser/chromeos/crostini/crostini_util.h
index 4c0bc89..947b36d 100644
--- a/chrome/browser/chromeos/crostini/crostini_util.h
+++ b/chrome/browser/chromeos/crostini/crostini_util.h
@@ -22,6 +22,8 @@
 
 class Profile;
 
+namespace crostini {
+
 // Enables/disables overriding IsCrostiniUIAllowedForProfile's normal
 // behaviour and returning true instead.
 void SetCrostiniUIAllowedForTesting(bool enabled);
@@ -124,4 +126,6 @@
 // policy.
 bool IsUnaffiliatedCrostiniAllowedByPolicy();
 
+}  // namespace crostini
+
 #endif  // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_UTIL_H_
diff --git a/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc b/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc
index 1918195..60f6eadf 100644
--- a/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc
+++ b/chrome/browser/chromeos/dbus/chrome_features_service_provider.cc
@@ -82,7 +82,7 @@
   std::unique_ptr<dbus::Response> response =
       dbus::Response::FromMethodCall(method_call);
   dbus::MessageWriter writer(response.get());
-  writer.AppendBool(IsCrostiniAllowedForProfile(profile));
+  writer.AppendBool(crostini::IsCrostiniAllowedForProfile(profile));
   response_sender.Run(std::move(response));
 }
 
diff --git a/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc b/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc
index 57fbf16..c00547b3 100644
--- a/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc
+++ b/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc
@@ -78,7 +78,7 @@
   }
 
   Profile* profile = ProfileManager::GetPrimaryUserProfile();
-  if (IsCrostiniEnabled(profile)) {
+  if (crostini::IsCrostiniEnabled(profile)) {
     crostini::CrostiniRegistryService* registry_service =
         crostini::CrostiniRegistryServiceFactory::GetForProfile(profile);
     registry_service->UpdateApplicationList(request);
@@ -104,8 +104,8 @@
   }
 
   Profile* profile = ProfileManager::GetPrimaryUserProfile();
-  if (IsCrostiniEnabled(profile) &&
-      request.owner_id() == CryptohomeIdForProfile(profile)) {
+  if (crostini::IsCrostiniEnabled(profile) &&
+      request.owner_id() == crostini::CryptohomeIdForProfile(profile)) {
     crostini::CrostiniManager::GetForProfile(profile)->LaunchContainerTerminal(
         request.vm_name(), request.container_name(),
         std::vector<std::string>(request.params().begin(),
@@ -131,7 +131,7 @@
   }
 
   Profile* profile = ProfileManager::GetPrimaryUserProfile();
-  if (IsCrostiniEnabled(profile)) {
+  if (crostini::IsCrostiniEnabled(profile)) {
     crostini::CrostiniMimeTypesService* mime_types_service =
         crostini::CrostiniMimeTypesServiceFactory::GetForProfile(profile);
     mime_types_service->UpdateMimeTypes(request);
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index 48ee56e..61bb906 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -695,7 +695,7 @@
   DVLOG(1) << "AutotestPrivateSetCrostiniEnabledFunction " << params->enabled;
 
   Profile* profile = Profile::FromBrowserContext(browser_context());
-  if (!IsCrostiniUIAllowedForProfile(profile))
+  if (!crostini::IsCrostiniUIAllowedForProfile(profile))
     return RespondNow(Error(kCrostiniNotAvailableForCurrentUserError));
 
   // Set the preference to indicate Crostini is enabled/disabled.
@@ -721,7 +721,7 @@
   DVLOG(1) << "AutotestPrivateInstallCrostiniFunction";
 
   Profile* profile = Profile::FromBrowserContext(browser_context());
-  if (!IsCrostiniUIAllowedForProfile(profile))
+  if (!crostini::IsCrostiniUIAllowedForProfile(profile))
     return RespondNow(Error(kCrostiniNotAvailableForCurrentUserError));
 
   // Run GUI installer which will install crostini vm / container and
@@ -731,7 +731,7 @@
   CrostiniInstallerView::Show(profile);
   CrostiniInstallerView::GetActiveViewForTesting()->Accept();
   crostini::CrostiniManager::GetForProfile(profile)->RestartCrostini(
-      kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
+      crostini::kCrostiniDefaultVmName, crostini::kCrostiniDefaultContainerName,
       base::BindOnce(
           &AutotestPrivateRunCrostiniInstallerFunction::CrostiniRestarted,
           this));
@@ -760,7 +760,7 @@
   DVLOG(1) << "AutotestPrivateRunCrostiniUninstallerFunction";
 
   Profile* profile = Profile::FromBrowserContext(browser_context());
-  if (!IsCrostiniUIAllowedForProfile(profile))
+  if (!crostini::IsCrostiniUIAllowedForProfile(profile))
     return RespondNow(Error(kCrostiniNotAvailableForCurrentUserError));
 
   // Run GUI uninstaller which will remove crostini vm / container. We then
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 7613bfc..1179ce8 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
@@ -548,7 +548,7 @@
       crostini::CrostiniManager::GetForProfile(browser()->profile());
   crostini_manager->set_skip_restart_for_testing();
   vm_tools::concierge::VmInfo vm_info;
-  crostini_manager->AddRunningVmForTesting(kCrostiniDefaultVmName,
+  crostini_manager->AddRunningVmForTesting(crostini::kCrostiniDefaultVmName,
                                            std::move(vm_info));
 
   ExpectCrostiniMount();
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 cdf02000..cafa74c 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
@@ -645,8 +645,9 @@
 
 ExtensionFunction::ResponseAction
 FileManagerPrivateIsCrostiniEnabledFunction::Run() {
-  return RespondNow(OneArgument(std::make_unique<base::Value>(
-      IsCrostiniEnabled(Profile::FromBrowserContext(browser_context())))));
+  return RespondNow(
+      OneArgument(std::make_unique<base::Value>(crostini::IsCrostiniEnabled(
+          Profile::FromBrowserContext(browser_context())))));
 }
 
 FileManagerPrivateMountCrostiniFunction::
@@ -660,9 +661,9 @@
   // files into Linux files should still work.
   Profile* profile =
       Profile::FromBrowserContext(browser_context())->GetOriginalProfile();
-  DCHECK(IsCrostiniEnabled(profile));
+  DCHECK(crostini::IsCrostiniEnabled(profile));
   crostini::CrostiniManager::GetForProfile(profile)->RestartCrostini(
-      kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
+      crostini::kCrostiniDefaultVmName, crostini::kCrostiniDefaultContainerName,
       base::BindOnce(&FileManagerPrivateMountCrostiniFunction::RestartCallback,
                      this));
   return true;
@@ -693,7 +694,7 @@
       file_system_context->CrackURL(GURL(params->url));
 
   crostini::SharePath(
-      profile, kCrostiniDefaultVmName, cracked.path(),
+      profile, crostini::kCrostiniDefaultVmName, cracked.path(),
       base::BindOnce(&FileManagerPrivateInternalSharePathWithCrostiniFunction::
                          SharePathCallback,
                      this));
@@ -762,7 +763,8 @@
           profile, file_system_context->CrackURL(GURL(params->url)));
   crostini::CrostiniPackageInstallerService::GetForProfile(profile)
       ->InstallLinuxPackage(
-          kCrostiniDefaultVmName, kCrostiniDefaultContainerName, url,
+          crostini::kCrostiniDefaultVmName,
+          crostini::kCrostiniDefaultContainerName, url,
           base::BindOnce(
               &FileManagerPrivateInternalInstallLinuxPackageFunction::
                   OnInstallLinuxPackage,
diff --git a/chrome/browser/chromeos/file_manager/crostini_file_tasks.cc b/chrome/browser/chromeos/file_manager/crostini_file_tasks.cc
index afd4c8f..1bf1ffc 100644
--- a/chrome/browser/chromeos/file_manager/crostini_file_tasks.cc
+++ b/chrome/browser/chromeos/file_manager/crostini_file_tasks.cc
@@ -89,7 +89,7 @@
                        const std::vector<extensions::EntryInfo>& entries,
                        std::vector<FullTaskDescriptor>* result_list,
                        base::OnceClosure completion_closure) {
-  if (!IsCrostiniUIAllowedForProfile(profile)) {
+  if (!crostini::IsCrostiniUIAllowedForProfile(profile)) {
     std::move(completion_closure).Run();
     return;
   }
@@ -138,7 +138,7 @@
 
   ui::ScaleFactor scale_factor = ui::GetSupportedScaleFactors().back();
 
-  LoadIcons(
+  crostini::LoadIcons(
       profile, result_app_ids, kIconSizeInDip, scale_factor, kIconLoadTimeout,
       base::BindOnce(OnAppIconsLoaded, profile, result_app_ids, scale_factor,
                      result_list, std::move(completion_closure)));
@@ -149,7 +149,7 @@
     const TaskDescriptor& task,
     const std::vector<storage::FileSystemURL>& file_system_urls,
     FileTaskFinishedCallback done) {
-  DCHECK(IsCrostiniUIAllowedForProfile(profile));
+  DCHECK(crostini::IsCrostiniUIAllowedForProfile(profile));
 
   std::vector<std::string> files;
   for (const storage::FileSystemURL& file_system_url : file_system_urls) {
@@ -157,7 +157,8 @@
         profile, file_system_url));
   }
 
-  LaunchCrostiniApp(profile, task.app_id, display::kInvalidDisplayId, files);
+  crostini::LaunchCrostiniApp(profile, task.app_id, display::kInvalidDisplayId,
+                              files);
 }
 
 }  // namespace file_tasks
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 995f237..a9a58d81 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
@@ -1432,7 +1432,7 @@
   crostini::CrostiniManager* crostini_manager =
       crostini::CrostiniManager::GetForProfile(profile()->GetOriginalProfile());
   vm_tools::concierge::VmInfo vm_info;
-  crostini_manager->AddRunningVmForTesting(kCrostiniDefaultVmName,
+  crostini_manager->AddRunningVmForTesting(crostini::kCrostiniDefaultVmName,
                                            std::move(vm_info));
   return crostini_volume_->mount_path();
 }
diff --git a/chrome/browser/chromeos/file_manager/file_tasks_unittest.cc b/chrome/browser/chromeos/file_manager/file_tasks_unittest.cc
index c7fc5f4..0fd02e3 100644
--- a/chrome/browser/chromeos/file_manager/file_tasks_unittest.cc
+++ b/chrome/browser/chromeos/file_manager/file_tasks_unittest.cc
@@ -1128,8 +1128,8 @@
 
     // Setup the custom MIME type mapping.
     vm_tools::apps::MimeTypes mime_types_list;
-    mime_types_list.set_vm_name(kCrostiniDefaultVmName);
-    mime_types_list.set_container_name(kCrostiniDefaultContainerName);
+    mime_types_list.set_vm_name(crostini::kCrostiniDefaultVmName);
+    mime_types_list.set_container_name(crostini::kCrostiniDefaultContainerName);
     (*mime_types_list.mutable_mime_type_mappings())["foo"] = "foo/x-bar";
 
     crostini::CrostiniMimeTypesServiceFactory::GetForProfile(&test_profile_)
diff --git a/chrome/browser/chromeos/file_manager/path_util.cc b/chrome/browser/chromeos/file_manager/path_util.cc
index 1243149..ba253bdf 100644
--- a/chrome/browser/chromeos/file_manager/path_util.cc
+++ b/chrome/browser/chromeos/file_manager/path_util.cc
@@ -151,8 +151,9 @@
 std::string GetCrostiniMountPointName(Profile* profile) {
   // crostini_<hash>_termina_penguin
   return base::JoinString(
-      {"crostini", CryptohomeIdForProfile(profile), kCrostiniDefaultVmName,
-       kCrostiniDefaultContainerName},
+      {"crostini", crostini::CryptohomeIdForProfile(profile),
+       crostini::kCrostiniDefaultVmName,
+       crostini::kCrostiniDefaultContainerName},
       "_");
 }
 
@@ -198,10 +199,11 @@
   base::FilePath folder;
   if (id == mount_point_name_crostini) {
     folder = base::FilePath(mount_point_name_crostini);
-    result = ContainerHomeDirectoryForProfile(profile);
+    result = crostini::ContainerHomeDirectoryForProfile(profile);
   } else if (id == mount_point_name_downloads) {
     folder = base::FilePath(mount_point_name_downloads);
-    result = ContainerChromeOSBaseDirectory().Append(kDownloadsFolderName);
+    result =
+        crostini::ContainerChromeOSBaseDirectory().Append(kDownloadsFolderName);
   } else {
     NOTREACHED();
   }
diff --git a/chrome/browser/chromeos/file_manager/volume_manager.cc b/chrome/browser/chromeos/file_manager/volume_manager.cc
index ba5413a..11d4b1a 100644
--- a/chrome/browser/chromeos/file_manager/volume_manager.cc
+++ b/chrome/browser/chromeos/file_manager/volume_manager.cc
@@ -557,7 +557,8 @@
   // Listen for crostini container shutdown and remove volume.
   crostini::CrostiniManager::GetForProfile(profile_)
       ->AddShutdownContainerCallback(
-          kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
+          crostini::kCrostiniDefaultVmName,
+          crostini::kCrostiniDefaultContainerName,
           base::BindOnce(&VolumeManager::RemoveSshfsCrostiniVolume,
                          weak_ptr_factory_.GetWeakPtr(), sshfs_mount_path));
 }
diff --git a/chrome/browser/chromeos/login/lock/views_screen_locker.cc b/chrome/browser/chromeos/login/lock/views_screen_locker.cc
index 6d7f143..846ab21 100644
--- a/chrome/browser/chromeos/login/lock/views_screen_locker.cc
+++ b/chrome/browser/chromeos/login/lock/views_screen_locker.cc
@@ -8,6 +8,7 @@
 #include <string>
 #include <utility>
 
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/interfaces/login_user_info.mojom.h"
 #include "base/bind.h"
 #include "base/i18n/time_formatting.h"
@@ -28,6 +29,7 @@
 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/components/proximity_auth/screenlock_bridge.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/login/auth/authpolicy_login_helper.h"
 #include "components/user_manager/known_user.h"
 #include "components/user_manager/user_manager.h"
@@ -38,6 +40,7 @@
 
 namespace {
 constexpr char kLockDisplay[] = "lock";
+constexpr char kExternalBinaryAuth[] = "external_binary_auth";
 
 ash::mojom::FingerprintUnlockState ConvertFromFingerprintState(
     ScreenLocker::FingerprintState state) {
@@ -57,11 +60,40 @@
   }
 }
 
+void OnSetState(base::Optional<mri::State> maybe_state) {
+  // TODO/FIXME: add handling for starting the graph process.
+  NOTIMPLEMENTED();
+}
+
+// Starts the graph specified by |configuration| if the current graph
+// is SUSPENDED or if the current configuration is different.
+void StartGraphIfNeeded(chromeos::MediaAnalyticsClient* client,
+                        const std::string& configuration,
+                        base::Optional<mri::State> maybe_state) {
+  if (!maybe_state)
+    return;
+
+  if (maybe_state->status() == mri::State::SUSPENDED) {
+    mri::State new_state;
+    new_state.set_status(mri::State::RUNNING);
+    new_state.set_configuration(configuration);
+    client->SetState(new_state, base::BindOnce(&OnSetState));
+  } else if (maybe_state->configuration() != configuration) {
+    mri::State suspend_state;
+    suspend_state.set_status(mri::State::SUSPENDED);
+    suspend_state.set_configuration(configuration);
+    client->SetState(suspend_state, base::BindOnce(&StartGraphIfNeeded, client,
+                                                   configuration));
+  }
+}
+
 }  // namespace
 
 ViewsScreenLocker::ViewsScreenLocker(ScreenLocker* screen_locker)
     : screen_locker_(screen_locker),
       version_info_updater_(std::make_unique<MojoVersionInfoDispatcher>()),
+      media_analytics_client_(
+          chromeos::DBusThreadManager::Get()->GetMediaAnalyticsClient()),
       weak_factory_(this) {
   LoginScreenClient::Get()->SetDelegate(this);
   user_board_view_mojo_ = std::make_unique<UserBoardViewMojo>();
@@ -74,6 +106,9 @@
           kDeviceLoginScreenInputMethods,
           base::Bind(&ViewsScreenLocker::OnAllowedInputMethodsChanged,
                      base::Unretained(this)));
+
+  if (base::FeatureList::IsEnabled(ash::features::kUnlockWithExternalBinary))
+    scoped_observer_.Add(media_analytics_client_);
 }
 
 ViewsScreenLocker::~ViewsScreenLocker() {
@@ -198,6 +233,8 @@
 void ViewsScreenLocker::HandleAuthenticateUserWithExternalBinary(
     const AccountId& account_id,
     AuthenticateUserWithExternalBinaryCallback callback) {
+  media_analytics_client_->GetState(base::BindOnce(
+      &StartGraphIfNeeded, media_analytics_client_, kExternalBinaryAuth));
   // TODO/FIXME: implement the actual external binary auth check.
   bool auth_success = false;
   NOTIMPLEMENTED();
@@ -299,6 +336,13 @@
       reverse);
 }
 
+void ViewsScreenLocker::OnDetectionSignal(
+    const mri::MediaPerception& media_perception) {
+  // TODO/FIXME: respond to positive/negative signal from external binary
+  // auth service.
+  NOTIMPLEMENTED();
+}
+
 void ViewsScreenLocker::UpdatePinKeyboardState(const AccountId& account_id) {
   quick_unlock::PinBackend::GetInstance()->CanAuthenticate(
       account_id, base::BindOnce(&ViewsScreenLocker::OnPinCanAuthenticate,
diff --git a/chrome/browser/chromeos/login/lock/views_screen_locker.h b/chrome/browser/chromeos/login/lock/views_screen_locker.h
index 7688ac3..30aa64b 100644
--- a/chrome/browser/chromeos/login/lock/views_screen_locker.h
+++ b/chrome/browser/chromeos/login/lock/views_screen_locker.h
@@ -6,10 +6,13 @@
 #define CHROME_BROWSER_CHROMEOS_LOGIN_LOCK_VIEWS_SCREEN_LOCKER_H_
 
 #include "base/memory/weak_ptr.h"
+#include "base/scoped_observer.h"
 #include "chrome/browser/chromeos/lock_screen_apps/focus_cycler_delegate.h"
 #include "chrome/browser/chromeos/login/lock/screen_locker.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/browser/ui/ash/login_screen_client.h"
+#include "chromeos/dbus/media_analytics_client.h"
+#include "chromeos/dbus/media_perception/media_perception.pb.h"
 #include "chromeos/dbus/power_manager_client.h"
 
 namespace chromeos {
@@ -25,7 +28,8 @@
 class ViewsScreenLocker : public LoginScreenClient::Delegate,
                           public ScreenLocker::Delegate,
                           public PowerManagerClient::Observer,
-                          public lock_screen_apps::FocusCyclerDelegate {
+                          public lock_screen_apps::FocusCyclerDelegate,
+                          public chromeos::MediaAnalyticsClient::Observer {
  public:
   explicit ViewsScreenLocker(ScreenLocker* screen_locker);
   ~ViewsScreenLocker() override;
@@ -78,6 +82,9 @@
   void UnregisterLockScreenAppFocusHandler() override;
   void HandleLockScreenAppFocusOut(bool reverse) override;
 
+  // chromeos::MediaAnalyticsClient::Observer
+  void OnDetectionSignal(const mri::MediaPerception& media_perception) override;
+
  private:
   void UpdatePinKeyboardState(const AccountId& account_id);
   void OnAllowedInputMethodsChanged();
@@ -109,6 +116,11 @@
   // Updates UI when version info is changed.
   std::unique_ptr<MojoVersionInfoDispatcher> version_info_updater_;
 
+  chromeos::MediaAnalyticsClient* media_analytics_client_;
+
+  ScopedObserver<chromeos::MediaAnalyticsClient, ViewsScreenLocker>
+      scoped_observer_{this};
+
   base::WeakPtrFactory<ViewsScreenLocker> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ViewsScreenLocker);
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.cc b/chrome/browser/chromeos/login/session/user_session_manager.cc
index cdd80d4..60ae146 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager.cc
+++ b/chrome/browser/chromeos/login/session/user_session_manager.cc
@@ -1859,6 +1859,13 @@
     return;
   }
 
+  // Skip key update when using PIN. The keys should wrap password instead of
+  // PIN.
+  if (user_context.IsUsingPin()) {
+    NotifyEasyUnlockKeyOpsFinished();
+    return;
+  }
+
   const base::ListValue* device_list = nullptr;
   EasyUnlockService* easy_unlock_service = EasyUnlockService::GetForUser(*user);
   if (easy_unlock_service) {
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
index cf9089c..9bb1f9f 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
@@ -590,13 +590,15 @@
   else
     finalize_animation_type_ = ANIMATION_NONE;
 
-  // Observe the user switch animation and defer the deletion of itself only
-  // after the animation is finished.
-  MultiUserWindowManager* window_manager =
-      MultiUserWindowManager::GetInstance();
-  // MultiUserWindowManager instance might be nullptr in a unit test.
-  if (window_manager)
-    window_manager->AddObserver(this);
+  if (finalize_animation_type_ == ANIMATION_ADD_USER) {
+    // Observe the user switch animation and defer the deletion of itself only
+    // after the animation is finished.
+    MultiUserWindowManager* window_manager =
+        MultiUserWindowManager::GetInstance();
+    // MultiUserWindowManager instance might be nullptr in a unit test.
+    if (window_manager)
+      window_manager->AddObserver(this);
+  }
 
   VLOG(1) << "Login WebUI >> user adding";
   if (!login_window_)
diff --git a/chrome/browser/chromeos/smb_client/smb_url.cc b/chrome/browser/chromeos/smb_client/smb_url.cc
index f95a92b..46818ab94 100644
--- a/chrome/browser/chromeos/smb_client/smb_url.cc
+++ b/chrome/browser/chromeos/smb_client/smb_url.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/chromeos/smb_client/smb_url.h"
 
+#include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
 #include "chrome/browser/chromeos/smb_client/smb_constants.h"
 #include "url/url_canon_stdstring.h"
@@ -13,6 +14,7 @@
 
 namespace {
 
+const char kSingleBackslash[] = "\\";
 const char kDoubleBackslash[] = "\\\\";
 
 // Returns true if |url| starts with "smb://" or "\\".
@@ -59,6 +61,8 @@
   if (ShouldProcessUrl(raw_url)) {
     // Add "smb://" if |url| starts with "\\" and canonicalize the URL.
     CanonicalizeSmbUrl(AddSmbSchemeIfMissing(raw_url));
+    // Create the Windows UNC for the url.
+    CreateWindowsUnc(raw_url);
   }
 }
 
@@ -90,6 +94,12 @@
   return !url_.empty() && host_.is_valid();
 }
 
+std::string SmbUrl::GetWindowsUNCString() const {
+  DCHECK(IsValid());
+
+  return windows_unc_;
+}
+
 void SmbUrl::CanonicalizeSmbUrl(const std::string& url) {
   DCHECK(!IsValid());
   DCHECK(ShouldProcessUrl(url));
@@ -135,6 +145,21 @@
   DCHECK_EQ(url_.substr(scheme.begin, scheme.len), kSmbScheme);
 }
 
+void SmbUrl::CreateWindowsUnc(const std::string& url) {
+  url::Parsed parsed;
+  if (!ParseAndValidateUrl(url, &parsed)) {
+    return;
+  }
+
+  const std::string host = url.substr(parsed.host.begin, parsed.host.len);
+  std::string path = url.substr(parsed.path.begin, parsed.path.len);
+
+  // Turn any forward slashes into escaped backslashes.
+  base::ReplaceChars(path, "/", kSingleBackslash, &path);
+
+  windows_unc_ = base::StrCat({kDoubleBackslash, host, path});
+}
+
 void SmbUrl::Reset() {
   host_.reset();
   url_.clear();
diff --git a/chrome/browser/chromeos/smb_client/smb_url.h b/chrome/browser/chromeos/smb_client/smb_url.h
index a07e424..3e23a45 100644
--- a/chrome/browser/chromeos/smb_client/smb_url.h
+++ b/chrome/browser/chromeos/smb_client/smb_url.h
@@ -38,16 +38,26 @@
   // should be called after the constructor.
   bool IsValid() const;
 
+  // Returns |url_| in the format \\server\share.
+  std::string GetWindowsUNCString() const;
+
  private:
   // Canonicalize |url| and saves the output as url_ and host_ if successful.
   void CanonicalizeSmbUrl(const std::string& url);
 
+  // Parse |url| into a Windows UNC |windows_unc_|.
+  void CreateWindowsUnc(const std::string& url);
+
   // Resets url_ and parsed_.
   void Reset();
 
   // String form of the canonical url.
   std::string url_;
 
+  // String form of the Windows Universal Naming Convention of the url.
+  // MS-DTYP section 2.2.57
+  std::string windows_unc_;
+
   // Holds the identified host of the URL. This does not store the host itself.
   url::Component host_;
 
diff --git a/chrome/browser/chromeos/smb_client/smb_url_unittest.cc b/chrome/browser/chromeos/smb_client/smb_url_unittest.cc
index 7bbca31..92d9399 100644
--- a/chrome/browser/chromeos/smb_client/smb_url_unittest.cc
+++ b/chrome/browser/chromeos/smb_client/smb_url_unittest.cc
@@ -31,6 +31,13 @@
     EXPECT_EQ(expected_host, smb_url.GetHost());
   }
 
+  void ExpectValidWindowsUNC(const std::string& url,
+                             const std::string& expected_unc) {
+    SmbUrl smb_url(url);
+    EXPECT_TRUE(smb_url.IsValid());
+    EXPECT_EQ(expected_unc, smb_url.GetWindowsUNCString());
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(SmbUrlTest);
 };
@@ -103,5 +110,16 @@
   EXPECT_EQ(expected_host, smb_url.GetHost());
 }
 
+TEST_F(SmbUrlTest, GetWindowsURL) {
+  ExpectValidWindowsUNC("smb://server/share", "\\\\server\\share");
+  ExpectValidWindowsUNC("smb://server/share/long/folder",
+                        "\\\\server\\share\\long\\folder");
+  ExpectValidWindowsUNC("smb://server/share/folder.with.dots",
+                        "\\\\server\\share\\folder.with.dots");
+  ExpectValidWindowsUNC("smb://server\\share/mixed\\slashes",
+                        "\\\\server\\share\\mixed\\slashes");
+  ExpectValidWindowsUNC("\\\\server/share", "\\\\server\\share");
+}
+
 }  // namespace smb_client
 }  // namespace chromeos
diff --git a/chrome/browser/devtools/chrome_devtools_manager_delegate.cc b/chrome/browser/devtools/chrome_devtools_manager_delegate.cc
index 643c19c..ca0e39f8 100644
--- a/chrome/browser/devtools/chrome_devtools_manager_delegate.cc
+++ b/chrome/browser/devtools/chrome_devtools_manager_delegate.cc
@@ -273,8 +273,8 @@
 
   bool equals = remote_locations.size() == remote_locations_.size();
   if (equals) {
-    RemoteLocations::iterator it1 = remote_locations.begin();
-    RemoteLocations::iterator it2 = remote_locations_.begin();
+    auto it1 = remote_locations.begin();
+    auto it2 = remote_locations_.begin();
     while (it1 != remote_locations.end()) {
       DCHECK(it2 != remote_locations_.end());
       if (!(*it1).Equals(*it2))
diff --git a/chrome/browser/devtools/device/adb/adb_client_socket_browsertest.cc b/chrome/browser/devtools/device/adb/adb_client_socket_browsertest.cc
index 1288d9c..8c46ddf 100644
--- a/chrome/browser/devtools/device/adb/adb_client_socket_browsertest.cc
+++ b/chrome/browser/devtools/device/adb/adb_client_socket_browsertest.cc
@@ -16,8 +16,7 @@
 static scoped_refptr<DevToolsAndroidBridge::RemoteBrowser>
 FindBrowserByDisplayName(DevToolsAndroidBridge::RemoteBrowsers browsers,
                          const std::string& name) {
-  for (DevToolsAndroidBridge::RemoteBrowsers::iterator it = browsers.begin();
-      it != browsers.end(); ++it)
+  for (auto it = browsers.begin(); it != browsers.end(); ++it)
     if ((*it)->display_name() == name)
       return *it;
   return NULL;
diff --git a/chrome/browser/devtools/device/android_device_info_query.cc b/chrome/browser/devtools/device/android_device_info_query.cc
index 8dbbb4d..cae3802 100644
--- a/chrome/browser/devtools/device/android_device_info_query.cc
+++ b/chrome/browser/devtools/device/android_device_info_query.cc
@@ -246,8 +246,7 @@
   if (!unix_user.empty() && unix_user[0] == 'u') {
     size_t pos = unix_user.find('_');
     if (pos != std::string::npos) {
-      StringMap::const_iterator it =
-          id_to_username.find(unix_user.substr(1, pos - 1));
+      auto it = id_to_username.find(unix_user.substr(1, pos - 1));
       if (it != id_to_username.end())
         return it->second;
     }
@@ -310,7 +309,7 @@
     std::string socket = pair.first;
     std::string pid = pair.second;
     std::string package;
-    StringMap::iterator pit = pid_to_package.find(pid);
+    auto pit = pid_to_package.find(pid);
     if (pit != pid_to_package.end())
       package = pit->second;
 
@@ -320,7 +319,7 @@
     browser_info.display_name =
         AndroidDeviceManager::GetBrowserName(socket, package);
 
-    StringMap::iterator uit = pid_to_user.find(pid);
+    auto uit = pid_to_user.find(pid);
     if (uit != pid_to_user.end())
       browser_info.user = GetUserName(uit->second, id_to_username);
 
diff --git a/chrome/browser/devtools/device/android_device_manager.cc b/chrome/browser/devtools/device/android_device_manager.cc
index 8399552..74558dce 100644
--- a/chrome/browser/devtools/device/android_device_manager.cc
+++ b/chrome/browser/devtools/device/android_device_manager.cc
@@ -339,8 +339,7 @@
     DevicesRequest* request = new DevicesRequest(callback);
     // Avoid destruction while sending requests
     request->AddRef();
-    for (DeviceProviders::const_iterator it = providers.begin();
-         it != providers.end(); ++it) {
+    for (auto it = providers.begin(); it != providers.end(); ++it) {
       device_task_runner->PostTask(
           FROM_HERE, base::BindOnce(&DeviceProvider::QueryDevices, *it,
                                     base::Bind(&DevicesRequest::ProcessSerials,
@@ -365,8 +364,7 @@
 
   void ProcessSerials(scoped_refptr<DeviceProvider> provider,
                       const Serials& serials) {
-    for (Serials::const_iterator it = serials.begin(); it != serials.end();
-         ++it) {
+    for (auto it = serials.begin(); it != serials.end(); ++it) {
       descriptors_->resize(descriptors_->size() + 1);
       descriptors_->back().provider = provider;
       descriptors_->back().serial = *it;
@@ -546,8 +544,7 @@
 
 void AndroidDeviceManager::SetDeviceProviders(
     const DeviceProviders& providers) {
-  for (DeviceProviders::iterator it = providers_.begin();
-      it != providers_.end(); ++it) {
+  for (auto it = providers_.begin(); it != providers_.end(); ++it) {
     (*it)->AddRef();
     DeviceProvider* raw_ptr = it->get();
     *it = nullptr;
@@ -580,7 +577,7 @@
   for (DeviceDescriptors::const_iterator it = descriptors->begin();
        it != descriptors->end();
        ++it) {
-    DeviceWeakMap::iterator found = devices_.find(it->serial);
+    auto found = devices_.find(it->serial);
     scoped_refptr<Device> device;
     if (found == devices_.end() || !found->second ||
         found->second->provider_.get() != it->provider.get()) {
diff --git a/chrome/browser/devtools/device/devtools_android_bridge.cc b/chrome/browser/devtools/device/devtools_android_bridge.cc
index db3f446..9a2f7a3 100644
--- a/chrome/browser/devtools/device/devtools_android_bridge.cc
+++ b/chrome/browser/devtools/device/devtools_android_bridge.cc
@@ -106,7 +106,7 @@
 scoped_refptr<content::DevToolsAgentHost>
 DevToolsAndroidBridge::GetBrowserAgentHost(
     scoped_refptr<RemoteBrowser> browser) {
-  DeviceMap::iterator it = device_map_.find(browser->serial());
+  auto it = device_map_.find(browser->serial());
   if (it == device_map_.end())
     return nullptr;
 
@@ -123,7 +123,7 @@
     callback.Run(net::ERR_FAILED, std::string());
     return;
   }
-  DeviceMap::iterator it = device_map_.find(serial);
+  auto it = device_map_.find(serial);
   if (it == device_map_.end()) {
     callback.Run(net::ERR_FAILED, std::string());
     return;
@@ -186,8 +186,8 @@
 void DevToolsAndroidBridge::RemoveDeviceListListener(
     DeviceListListener* listener) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DeviceListListeners::iterator it = std::find(
-      device_list_listeners_.begin(), device_list_listeners_.end(), listener);
+  auto it = std::find(device_list_listeners_.begin(),
+                      device_list_listeners_.end(), listener);
   DCHECK(it != device_list_listeners_.end());
   device_list_listeners_.erase(it);
   if (!NeedsDeviceListPolling())
@@ -204,8 +204,8 @@
 void DevToolsAndroidBridge::RemoveDeviceCountListener(
     DeviceCountListener* listener) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DeviceCountListeners::iterator it = std::find(
-      device_count_listeners_.begin(), device_count_listeners_.end(), listener);
+  auto it = std::find(device_count_listeners_.begin(),
+                      device_count_listeners_.end(), listener);
   DCHECK(it != device_count_listeners_.end());
   device_count_listeners_.erase(it);
   if (device_count_listeners_.empty())
@@ -222,10 +222,8 @@
 
 void DevToolsAndroidBridge::RemovePortForwardingListener(
     PortForwardingListener* listener) {
-  PortForwardingListeners::iterator it = std::find(
-      port_forwarding_listeners_.begin(),
-      port_forwarding_listeners_.end(),
-      listener);
+  auto it = std::find(port_forwarding_listeners_.begin(),
+                      port_forwarding_listeners_.end(), listener);
   DCHECK(it != port_forwarding_listeners_.end());
   port_forwarding_listeners_.erase(it);
   if (!NeedsDeviceListPolling())
@@ -269,14 +267,14 @@
   }
 
   DeviceListListeners copy(device_list_listeners_);
-  for (DeviceListListeners::iterator it = copy.begin(); it != copy.end(); ++it)
+  for (auto it = copy.begin(); it != copy.end(); ++it)
     (*it)->DeviceListChanged(remote_devices);
 
   ForwardingStatus status =
       port_forwarding_controller_->DeviceListChanged(complete_devices);
   PortForwardingListeners forwarding_listeners(port_forwarding_listeners_);
-  for (PortForwardingListeners::iterator it = forwarding_listeners.begin();
-       it != forwarding_listeners.end(); ++it) {
+  for (auto it = forwarding_listeners.begin(); it != forwarding_listeners.end();
+       ++it) {
     (*it)->PortStatusChanged(status);
   }
 }
@@ -305,7 +303,7 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   DeviceCountListeners copy(device_count_listeners_);
-  for (DeviceCountListeners::iterator it = copy.begin(); it != copy.end(); ++it)
+  for (auto it = copy.begin(); it != copy.end(); ++it)
     (*it)->DeviceCountChanged(count);
 
   if (device_count_listeners_.empty())
diff --git a/chrome/browser/devtools/device/devtools_device_discovery.cc b/chrome/browser/devtools/device/devtools_device_discovery.cc
index da58bbf..fbc6efe 100644
--- a/chrome/browser/devtools/device/devtools_device_discovery.cc
+++ b/chrome/browser/devtools/device/devtools_device_discovery.cc
@@ -428,7 +428,7 @@
   scoped_refptr<RemoteDevice> remote_device =
       new RemoteDevice(device->serial(), device_info);
   complete_devices_.push_back(std::make_pair(device, remote_device));
-  for (RemoteBrowsers::iterator it = remote_device->browsers().begin();
+  for (auto it = remote_device->browsers().begin();
        it != remote_device->browsers().end(); ++it) {
     device->SendJsonRequest(
         (*it)->socket(),
@@ -563,10 +563,8 @@
       model_(device_info.model),
       connected_(device_info.connected),
       screen_size_(device_info.screen_size) {
-  for (std::vector<AndroidDeviceManager::BrowserInfo>::const_iterator it =
-      device_info.browser_info.begin();
-      it != device_info.browser_info.end();
-      ++it) {
+  for (auto it = device_info.browser_info.begin();
+       it != device_info.browser_info.end(); ++it) {
     browsers_.push_back(new RemoteBrowser(serial, *it));
   }
 }
diff --git a/chrome/browser/devtools/device/port_forwarding_controller.cc b/chrome/browser/devtools/device/port_forwarding_controller.cc
index 866bb33..29770838 100644
--- a/chrome/browser/devtools/device/port_forwarding_controller.cc
+++ b/chrome/browser/devtools/device/port_forwarding_controller.cc
@@ -389,11 +389,10 @@
     const ForwardingMap& old_map,
     const ForwardingMap& new_map) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  for (ForwardingMap::const_iterator new_it(new_map.begin());
-      new_it != new_map.end(); ++new_it) {
+  for (auto new_it(new_map.begin()); new_it != new_map.end(); ++new_it) {
     int port = new_it->first;
     const std::string& location = new_it->second;
-    ForwardingMap::const_iterator old_it = old_map.find(port);
+    auto old_it = old_map.find(port);
     if (old_it != old_map.end() && old_it->second == location)
       continue;  // The port points to the same location in both configs, skip.
 
@@ -421,7 +420,7 @@
     port_status_[port] = kStatusConnecting;
 #endif  // BUILDFLAG(DEBUG_DEVTOOLS)
   } else {
-    PortStatusMap::iterator it = port_status_.find(port);
+    auto it = port_status_.find(port);
     if (it != port_status_.end() && it->second == kStatusError) {
       // The bind command failed on this port, do not attempt unbind.
       port_status_.erase(it);
@@ -446,7 +445,7 @@
   if (!ParseResponse(message, &id, &error_code))
     return false;
 
-  CommandCallbackMap::iterator it = pending_responses_.find(id);
+  auto it = pending_responses_.find(id);
   if (it == pending_responses_.end())
     return false;
 
@@ -462,7 +461,7 @@
 
 void PortForwardingController::Connection::ProcessUnbindResponse(
     int port, PortStatus status) {
-  PortStatusMap::iterator it = port_status_.find(port);
+  auto it = port_status_.find(port);
   if (it == port_status_.end())
     return;
   if (status == kStatusError)
@@ -507,7 +506,7 @@
       !params->GetString(kConnectionIdParam, &connection_id))
     return;
 
-  std::map<int, std::string>::iterator it = forwarding_map_.find(port);
+  auto it = forwarding_map_.find(port);
   if (it == forwarding_map_.end())
     return;
 
@@ -549,7 +548,7 @@
         pair.second);
     if (!remote_device->is_connected())
       continue;
-    Registry::iterator rit = registry_.find(remote_device->serial());
+    auto rit = registry_.find(remote_device->serial());
     if (rit == registry_.end()) {
       if (remote_device->browsers().size() > 0) {
         new Connection(profile_, &registry_, device,
@@ -592,6 +591,6 @@
 }
 
 void PortForwardingController::UpdateConnections() {
-  for (Registry::iterator it = registry_.begin(); it != registry_.end(); ++it)
+  for (auto it = registry_.begin(); it != registry_.end(); ++it)
     it->second->UpdateForwardingMap(forwarding_map_);
 }
diff --git a/chrome/browser/devtools/device/usb/android_usb_device.cc b/chrome/browser/devtools/device/usb/android_usb_device.cc
index dda4ad5..5df1dffe 100644
--- a/chrome/browser/devtools/device/usb/android_usb_device.cc
+++ b/chrome/browser/devtools/device/usb/android_usb_device.cc
@@ -621,9 +621,9 @@
     case AdbMessage::kCommandWRTE:
     case AdbMessage::kCommandCLSE:
       {
-        AndroidUsbSockets::iterator it = sockets_.find(message->arg1);
-        if (it != sockets_.end())
-          it->second->HandleIncoming(std::move(message));
+      auto it = sockets_.find(message->arg1);
+      if (it != sockets_.end())
+        it->second->HandleIncoming(std::move(message));
       }
       break;
     default:
@@ -652,8 +652,7 @@
 void AndroidUsbDevice::Terminate() {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
-  std::vector<AndroidUsbDevice*>::iterator it =
-      std::find(g_devices.Get().begin(), g_devices.Get().end(), this);
+  auto it = std::find(g_devices.Get().begin(), g_devices.Get().end(), this);
   if (it != g_devices.Get().end())
     g_devices.Get().erase(it);
 
@@ -667,8 +666,7 @@
 
   // Iterate over copy.
   AndroidUsbSockets sockets(sockets_);
-  for (AndroidUsbSockets::iterator it = sockets.begin();
-       it != sockets.end(); ++it) {
+  for (auto it = sockets.begin(); it != sockets.end(); ++it) {
     it->second->Terminated(true);
   }
   DCHECK(sockets_.empty());
diff --git a/chrome/browser/devtools/device/usb/usb_device_provider.cc b/chrome/browser/devtools/device/usb/usb_device_provider.cc
index 52faa6f7..cc9db68 100644
--- a/chrome/browser/devtools/device/usb/usb_device_provider.cc
+++ b/chrome/browser/devtools/device/usb/usb_device_provider.cc
@@ -101,7 +101,7 @@
 
 void UsbDeviceProvider::QueryDeviceInfo(const std::string& serial,
                                         const DeviceInfoCallback& callback) {
-  UsbDeviceMap::iterator it = device_map_.find(serial);
+  auto it = device_map_.find(serial);
   if (it == device_map_.end() || !it->second->is_connected()) {
     AndroidDeviceManager::DeviceInfo offline_info;
     callback.Run(offline_info);
@@ -114,7 +114,7 @@
 void UsbDeviceProvider::OpenSocket(const std::string& serial,
                                    const std::string& name,
                                    const SocketCallback& callback) {
-  UsbDeviceMap::iterator it = device_map_.find(serial);
+  auto it = device_map_.find(serial);
   if (it == device_map_.end()) {
     callback.Run(net::ERR_CONNECTION_FAILED,
                  base::WrapUnique<net::StreamSocket>(NULL));
@@ -144,8 +144,7 @@
                                           const AndroidUsbDevices& devices) {
   std::vector<std::string> result;
   device_map_.clear();
-  for (AndroidUsbDevices::const_iterator it = devices.begin();
-       it != devices.end(); ++it) {
+  for (auto it = devices.begin(); it != devices.end(); ++it) {
     result.push_back((*it)->serial());
     device_map_[(*it)->serial()] = *it;
     (*it)->InitOnCallerThread();
diff --git a/chrome/browser/devtools/devtools_embedder_message_dispatcher.cc b/chrome/browser/devtools/devtools_embedder_message_dispatcher.cc
index 61a0bf3..46fe046 100644
--- a/chrome/browser/devtools/devtools_embedder_message_dispatcher.cc
+++ b/chrome/browser/devtools/devtools_embedder_message_dispatcher.cc
@@ -119,7 +119,7 @@
   bool Dispatch(const DispatchCallback& callback,
                 const std::string& method,
                 const base::ListValue* params) override {
-    HandlerMap::iterator it = handlers_.find(method);
+    auto it = handlers_.find(method);
     return it != handlers_.end() && it->second.Run(callback, *params);
   }
 
diff --git a/chrome/browser/devtools/devtools_file_helper.cc b/chrome/browser/devtools/devtools_file_helper.cc
index 9c791d9eb..f411c2c 100644
--- a/chrome/browser/devtools/devtools_file_helper.cc
+++ b/chrome/browser/devtools/devtools_file_helper.cc
@@ -229,7 +229,7 @@
                               bool save_as,
                               const SaveCallback& saveCallback,
                               const CancelCallback& cancelCallback) {
-  PathsMap::iterator it = saved_files_.find(url);
+  auto it = saved_files_.find(url);
   if (it != saved_files_.end() && !save_as) {
     SaveAsFileSelected(url, content, saveCallback, it->second);
     return;
@@ -276,7 +276,7 @@
 void DevToolsFileHelper::Append(const std::string& url,
                                 const std::string& content,
                                 const AppendCallback& callback) {
-  PathsMap::iterator it = saved_files_.find(url);
+  auto it = saved_files_.find(url);
   if (it == saved_files_.end())
     return;
   callback.Run();
diff --git a/chrome/browser/devtools/devtools_file_system_indexer.cc b/chrome/browser/devtools/devtools_file_system_indexer.cc
index 8d458a12..ca1a46a 100644
--- a/chrome/browser/devtools/devtools_file_system_indexer.cc
+++ b/chrome/browser/devtools/devtools_file_system_indexer.cc
@@ -184,7 +184,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   EnsureInitialized();
   FileId file_id = GetFileId(file_path);
-  vector<Trigram>::const_iterator it = index.begin();
+  auto it = index.begin();
   for (; it != index.end(); ++it) {
     Trigram trigram = *it;
     index_[trigram].push_back(file_id);
diff --git a/chrome/browser/devtools/devtools_sanity_browsertest.cc b/chrome/browser/devtools/devtools_sanity_browsertest.cc
index 0e28dbe1..830d664 100644
--- a/chrome/browser/devtools/devtools_sanity_browsertest.cc
+++ b/chrome/browser/devtools/devtools_sanity_browsertest.cc
@@ -566,9 +566,7 @@
     extensions::ProcessManager* manager =
         extensions::ProcessManager::Get(browser()->profile());
     extensions::ProcessManager::FrameSet all_frames = manager->GetAllFrames();
-    for (extensions::ProcessManager::FrameSet::const_iterator iter =
-             all_frames.begin();
-         iter != all_frames.end();) {
+    for (auto iter = all_frames.begin(); iter != all_frames.end();) {
       if (!content::WebContents::FromRenderFrameHost(*iter)->IsLoading())
         ++iter;
       else
diff --git a/chrome/browser/devtools/devtools_targets_ui.cc b/chrome/browser/devtools/devtools_targets_ui.cc
index 4342c6b..da914c2 100644
--- a/chrome/browser/devtools/devtools_targets_ui.cc
+++ b/chrome/browser/devtools/devtools_targets_ui.cc
@@ -210,7 +210,7 @@
 
 void AdbTargetsUIHandler::Open(const std::string& browser_id,
                                const std::string& url) {
-  RemoteBrowsers::iterator it = remote_browsers_.find(browser_id);
+  auto it = remote_browsers_.find(browser_id);
   if (it != remote_browsers_.end() && android_bridge_)
     android_bridge_->OpenRemotePage(it->second, url);
 }
@@ -218,7 +218,7 @@
 scoped_refptr<DevToolsAgentHost>
 AdbTargetsUIHandler::GetBrowserAgentHost(
     const std::string& browser_id) {
-  RemoteBrowsers::iterator it = remote_browsers_.find(browser_id);
+  auto it = remote_browsers_.find(browser_id);
   if (it == remote_browsers_.end() || !android_bridge_)
     return nullptr;
 
@@ -233,8 +233,7 @@
     return;
 
   base::ListValue device_list;
-  for (DevToolsAndroidBridge::RemoteDevices::const_iterator dit =
-      devices.begin(); dit != devices.end(); ++dit) {
+  for (auto dit = devices.begin(); dit != devices.end(); ++dit) {
     DevToolsAndroidBridge::RemoteDevice* device = dit->get();
     std::unique_ptr<base::DictionaryValue> device_data(
         new base::DictionaryValue());
@@ -248,8 +247,7 @@
     auto browser_list = std::make_unique<base::ListValue>();
 
     DevToolsAndroidBridge::RemoteBrowsers& browsers = device->browsers();
-    for (DevToolsAndroidBridge::RemoteBrowsers::iterator bit =
-        browsers.begin(); bit != browsers.end(); ++bit) {
+    for (auto bit = browsers.begin(); bit != browsers.end(); ++bit) {
       DevToolsAndroidBridge::RemoteBrowser* browser = bit->get();
       std::unique_ptr<base::DictionaryValue> browser_data(
           new base::DictionaryValue());
@@ -324,7 +322,7 @@
 
 scoped_refptr<DevToolsAgentHost> DevToolsTargetsUIHandler::GetTarget(
     const std::string& target_id) {
-  TargetMap::iterator it = targets_.find(target_id);
+  auto it = targets_.find(target_id);
   if (it != targets_.end())
     return it->second;
   return NULL;
@@ -383,12 +381,10 @@
 void PortForwardingStatusSerializer::PortStatusChanged(
     const ForwardingStatus& status) {
   base::DictionaryValue result;
-  for (ForwardingStatus::const_iterator sit = status.begin();
-      sit != status.end(); ++sit) {
+  for (auto sit = status.begin(); sit != status.end(); ++sit) {
     auto port_status_dict = std::make_unique<base::DictionaryValue>();
     const PortStatusMap& port_status_map = sit->second;
-    for (PortStatusMap::const_iterator it = port_status_map.begin();
-         it != port_status_map.end(); ++it) {
+    for (auto it = port_status_map.begin(); it != port_status_map.end(); ++it) {
       port_status_dict->SetInteger(base::IntToString(it->first), it->second);
     }
 
diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc
index 12474d2..425953b42 100644
--- a/chrome/browser/devtools/devtools_ui_bindings.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings.cc
@@ -505,8 +505,7 @@
     return NULL;
   DevToolsUIBindingsList* instances =
       g_devtools_ui_bindings_instances.Pointer();
-  for (DevToolsUIBindingsList::iterator it(instances->begin());
-       it != instances->end(); ++it) {
+  for (auto it(instances->begin()); it != instances->end(); ++it) {
     if ((*it)->web_contents() == web_contents)
       return *it;
   }
@@ -550,8 +549,7 @@
   // Remove self from global list.
   DevToolsUIBindingsList* instances =
       g_devtools_ui_bindings_instances.Pointer();
-  DevToolsUIBindingsList::iterator it(
-      std::find(instances->begin(), instances->end(), this));
+  auto it(std::find(instances->begin(), instances->end(), this));
   DCHECK(it != instances->end());
   instances->erase(it);
 }
@@ -825,7 +823,7 @@
 
 void DevToolsUIBindings::StopIndexing(int index_request_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  IndexingJobsMap::iterator it = indexing_jobs_.find(index_request_id);
+  auto it = indexing_jobs_.find(index_request_id);
   if (it == indexing_jobs_.end())
     return;
   it->second->Stop();
@@ -1229,8 +1227,7 @@
     const std::vector<std::string>& file_paths) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   base::ListValue file_paths_value;
-  for (std::vector<std::string>::const_iterator it(file_paths.begin());
-       it != file_paths.end(); ++it) {
+  for (auto it(file_paths.begin()); it != file_paths.end(); ++it) {
     file_paths_value.AppendString(*it);
   }
   base::Value request_id_value(request_id);
diff --git a/chrome/browser/devtools/devtools_window.cc b/chrome/browser/devtools/devtools_window.cc
index fd99d17..1e6f9486 100644
--- a/chrome/browser/devtools/devtools_window.cc
+++ b/chrome/browser/devtools/devtools_window.cc
@@ -269,7 +269,7 @@
   base::ListValue* shortcut_list;
   if (!parsed_message || !parsed_message->GetAsList(&shortcut_list))
       return;
-  base::ListValue::iterator it = shortcut_list->begin();
+  auto it = shortcut_list->begin();
   for (; it != shortcut_list->end(); ++it) {
     base::DictionaryValue* dictionary;
     if (!it->GetAsDictionary(&dictionary))
@@ -421,8 +421,7 @@
   owned_toolbox_web_contents_.reset();
 
   DevToolsWindows* instances = g_devtools_window_instances.Pointer();
-  DevToolsWindows::iterator it(
-      std::find(instances->begin(), instances->end(), this));
+  auto it(std::find(instances->begin(), instances->end(), this));
   DCHECK(it != instances->end());
   instances->erase(it);
 
@@ -484,8 +483,7 @@
   if (!inspected_web_contents || !g_devtools_window_instances.IsCreated())
     return NULL;
   DevToolsWindows* instances = g_devtools_window_instances.Pointer();
-  for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
-       ++it) {
+  for (auto it(instances->begin()); it != instances->end(); ++it) {
     if ((*it)->GetInspectedWebContents() == inspected_web_contents)
       return *it;
   }
@@ -497,8 +495,7 @@
   if (!web_contents || !g_devtools_window_instances.IsCreated())
     return false;
   DevToolsWindows* instances = g_devtools_window_instances.Pointer();
-  for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
-       ++it) {
+  for (auto it(instances->begin()); it != instances->end(); ++it) {
     if ((*it)->main_web_contents_ == web_contents ||
         (*it)->toolbox_web_contents_ == web_contents)
       return true;
@@ -1094,8 +1091,7 @@
   if (!agent_host || !g_devtools_window_instances.IsCreated())
     return NULL;
   DevToolsWindows* instances = g_devtools_window_instances.Pointer();
-  for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
-       ++it) {
+  for (auto it(instances->begin()); it != instances->end(); ++it) {
     if ((*it)->bindings_->IsAttachedTo(agent_host))
       return *it;
   }
@@ -1108,8 +1104,7 @@
   if (!web_contents || !g_devtools_window_instances.IsCreated())
     return NULL;
   DevToolsWindows* instances = g_devtools_window_instances.Pointer();
-  for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
-       ++it) {
+  for (auto it(instances->begin()); it != instances->end(); ++it) {
     if ((*it)->main_web_contents_ == web_contents)
       return *it;
   }
diff --git a/chrome/browser/devtools/devtools_window_testing.cc b/chrome/browser/devtools/devtools_window_testing.cc
index c0c99ff..c4cbcab 100644
--- a/chrome/browser/devtools/devtools_window_testing.cc
+++ b/chrome/browser/devtools/devtools_window_testing.cc
@@ -36,8 +36,7 @@
 DevToolsWindowTesting::~DevToolsWindowTesting() {
   DevToolsWindowTestings* instances =
       g_devtools_window_testing_instances.Pointer();
-  DevToolsWindowTestings::iterator it(
-      std::find(instances->begin(), instances->end(), this));
+  auto it(std::find(instances->begin(), instances->end(), this));
   DCHECK(it != instances->end());
   instances->erase(it);
   if (!close_callback_.is_null()) {
@@ -64,9 +63,7 @@
     return NULL;
   DevToolsWindowTestings* instances =
       g_devtools_window_testing_instances.Pointer();
-  for (DevToolsWindowTestings::iterator it(instances->begin());
-       it != instances->end();
-       ++it) {
+  for (auto it(instances->begin()); it != instances->end(); ++it) {
     if ((*it)->devtools_window_ == window)
       return *it;
   }
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index abdd74a..12c729e3 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -562,8 +562,6 @@
     "extension_message_bubble_controller.h",
     "extension_migrator.cc",
     "extension_migrator.h",
-    "extension_reenabler.cc",
-    "extension_reenabler.h",
     "extension_service.cc",
     "extension_service.h",
     "extension_special_storage_policy.cc",
@@ -728,10 +726,6 @@
     "webstore_data_fetcher.h",
     "webstore_data_fetcher_delegate.cc",
     "webstore_data_fetcher_delegate.h",
-    "webstore_inline_installer.cc",
-    "webstore_inline_installer.h",
-    "webstore_inline_installer_factory.cc",
-    "webstore_inline_installer_factory.h",
     "webstore_install_helper.cc",
     "webstore_install_helper.h",
     "webstore_install_with_prompt.cc",
@@ -742,8 +736,6 @@
     "webstore_reinstaller.h",
     "webstore_standalone_installer.cc",
     "webstore_standalone_installer.h",
-    "webstore_startup_installer.cc",
-    "webstore_startup_installer.h",
     "window_controller.cc",
     "window_controller.h",
     "window_controller_list.cc",
@@ -792,7 +784,6 @@
     "//chrome/browser/web_applications/components",
     "//chrome/browser/web_applications/extensions",
     "//chrome/common",
-    "//chrome/common/extensions:mojo_bindings",
     "//chrome/common/extensions/api:extensions_features",
     "//chrome/common/safe_browsing:proto",
     "//chrome/services/removable_storage_writer/public/mojom",
@@ -977,6 +968,9 @@
       "//ash/public/cpp",
       "//chromeos/components/proximity_auth",
       "//chromeos/services/ime/public/mojom",
+      "//chromeos/services/machine_learning/public/cpp",
+      "//chromeos/services/machine_learning/public/mojom",
+      "//chromeos/services/media_perception/public/mojom",
       "//components/arc",
       "//components/chrome_apps",
       "//components/constrained_window",
diff --git a/chrome/browser/extensions/api/inline_install_private/inline_install_private_api.cc b/chrome/browser/extensions/api/inline_install_private/inline_install_private_api.cc
index 66322d8..f49f337 100644
--- a/chrome/browser/extensions/api/inline_install_private/inline_install_private_api.cc
+++ b/chrome/browser/extensions/api/inline_install_private/inline_install_private_api.cc
@@ -30,10 +30,6 @@
   friend class base::RefCountedThreadSafe<Installer>;
   ~Installer() override;
 
-  // Needed so that we send the right referrer value in requests to the
-  // webstore.
-  const GURL& GetRequestorURL() const override { return requestor_url_; }
-
   std::unique_ptr<ExtensionInstallPrompt::Prompt> CreateInstallPrompt()
       const override;
 
@@ -58,7 +54,7 @@
     const {
   std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt(
       new ExtensionInstallPrompt::Prompt(
-          ExtensionInstallPrompt::INLINE_INSTALL_PROMPT));
+          ExtensionInstallPrompt::WEBSTORE_WIDGET_PROMPT));
   prompt->SetWebstoreData(localized_user_count(),
                           show_user_count(),
                           average_rating(),
diff --git a/chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.cc b/chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.cc
index b5542d7..920bd4d8 100644
--- a/chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.cc
+++ b/chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/component_updater/cros_component_installer_chromeos.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/delegate_to_browser_gpu_service_accelerator_factory.h"
+#include "content/public/browser/render_frame_host.h"
 #include "content/public/common/service_manager_connection.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "services/service_manager/public/cpp/connector.h"
@@ -89,4 +90,19 @@
   (*provider)->InjectGpuDependencies(std::move(accelerator_factory));
 }
 
+void MediaPerceptionAPIDelegateChromeOS::SetMediaPerceptionRequestHandler(
+    MediaPerceptionRequestHandler handler) {
+  handler_ = std::move(handler);
+}
+
+void MediaPerceptionAPIDelegateChromeOS::ForwardMediaPerceptionRequest(
+    chromeos::media_perception::mojom::MediaPerceptionRequest request,
+    content::RenderFrameHost* render_frame_host) {
+  if (!handler_) {
+    DLOG(ERROR) << "Got request but the handler is not set.";
+    return;
+  }
+  handler_.Run(std::move(request));
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.h b/chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.h
index 5f75d1e..1d765110 100644
--- a/chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.h
+++ b/chrome/browser/extensions/api/media_perception_private/media_perception_api_delegate_chromeos.h
@@ -22,8 +22,15 @@
       LoadCrOSComponentCallback load_callback) override;
   void BindDeviceFactoryProviderToVideoCaptureService(
       video_capture::mojom::DeviceFactoryProviderPtr* provider) override;
+  void SetMediaPerceptionRequestHandler(
+      MediaPerceptionRequestHandler handler) override;
+  void ForwardMediaPerceptionRequest(
+      chromeos::media_perception::mojom::MediaPerceptionRequest request,
+      content::RenderFrameHost* render_frame_host) override;
 
  private:
+  MediaPerceptionRequestHandler handler_;
+
   DISALLOW_COPY_AND_ASSIGN(MediaPerceptionAPIDelegateChromeOS);
 };
 
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
index 94aece1..b225140 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -1594,23 +1594,27 @@
   // Create a profile that will be destroyed later.
   base::ScopedAllowBlockingForTesting allow_blocking;
   ProfileManager* profile_manager = g_browser_process->profile_manager();
-  Profile* tmp_profile = Profile::CreateProfile(
+  Profile* temp_profile = Profile::CreateProfile(
       profile_manager->user_data_dir().AppendASCII("profile"), nullptr,
       Profile::CreateMode::CREATE_MODE_SYNCHRONOUS);
 
   // Create a WebRequestAPI instance that we can control the lifetime of.
-  auto api = std::make_unique<WebRequestAPI>(tmp_profile);
-  // Make sure we are proxying for |tmp_profile|.
+  auto api = std::make_unique<WebRequestAPI>(temp_profile);
+  // Make sure we are proxying for |temp_profile|.
   api->ForceProxyForTesting();
-  content::BrowserContext::GetDefaultStoragePartition(tmp_profile)
+  content::BrowserContext::GetDefaultStoragePartition(temp_profile)
       ->FlushNetworkInterfaceForTesting();
 
   network::mojom::URLLoaderFactoryPtr factory;
   auto request = mojo::MakeRequest(&factory);
-  EXPECT_TRUE(api->MaybeProxyURLLoaderFactory(nullptr, false, &request));
+  auto temp_web_contents =
+      WebContents::Create(WebContents::CreateParams(temp_profile));
+  EXPECT_TRUE(api->MaybeProxyURLLoaderFactory(temp_web_contents->GetMainFrame(),
+                                              false, &request));
+  temp_web_contents.reset();
   auto params = network::mojom::URLLoaderFactoryParams::New();
   params->process_id = 0;
-  content::BrowserContext::GetDefaultStoragePartition(tmp_profile)
+  content::BrowserContext::GetDefaultStoragePartition(temp_profile)
       ->GetNetworkContext()
       ->CreateURLLoaderFactory(std::move(request), std::move(params));
 
@@ -1627,7 +1631,7 @@
   // WebRequestAPI. This will cause the connection error that will reach the
   // proxy before the ProxySet shutdown code runs on the IO thread.
   api->Shutdown();
-  ProfileDestroyer::DestroyProfileWhenAppropriate(tmp_profile);
+  ProfileDestroyer::DestroyProfileWhenAppropriate(temp_profile);
   client.Unbind();
   api.reset();
 }
diff --git a/chrome/browser/extensions/api/webstore_widget_private/app_installer.cc b/chrome/browser/extensions/api/webstore_widget_private/app_installer.cc
index bc08c60..025bfdb 100644
--- a/chrome/browser/extensions/api/webstore_widget_private/app_installer.cc
+++ b/chrome/browser/extensions/api/webstore_widget_private/app_installer.cc
@@ -53,10 +53,6 @@
   return web_contents_ != NULL;
 }
 
-const GURL& AppInstaller::GetRequestorURL() const {
-  return GURL::EmptyGURL();
-}
-
 std::unique_ptr<ExtensionInstallPrompt::Prompt>
 AppInstaller::CreateInstallPrompt() const {
   if (silent_installation_)
@@ -64,7 +60,7 @@
 
   std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt(
       new ExtensionInstallPrompt::Prompt(
-          ExtensionInstallPrompt::INLINE_INSTALL_PROMPT));
+          ExtensionInstallPrompt::WEBSTORE_WIDGET_PROMPT));
 
   prompt->SetWebstoreData(localized_user_count(), show_user_count(),
                           average_rating(), rating_count());
@@ -83,40 +79,6 @@
   return web_contents_;
 }
 
-bool AppInstaller::CheckInlineInstallPermitted(
-    const base::DictionaryValue& webstore_data,
-    std::string* error) const {
-  DCHECK(error != NULL);
-  DCHECK(error->empty());
-
-  // We expect to be able to inline install the app.
-  bool inline_install_not_supported = false;
-  if (webstore_data.HasKey(kInlineInstallNotSupportedKey) &&
-      !webstore_data.GetBoolean(kInlineInstallNotSupportedKey,
-                                &inline_install_not_supported)) {
-    *error = extensions::webstore_install::kInvalidWebstoreResponseError;
-    return false;
-  }
-
-  DCHECK(!inline_install_not_supported)
-      << "App does not support inline installation";
-
-  if (inline_install_not_supported) {
-    *error = extensions::webstore_install::kInvalidWebstoreResponseError;
-    return false;
-  }
-
-  return true;
-}
-
-bool AppInstaller::CheckRequestorPermitted(
-    const base::DictionaryValue& webstore_data,
-    std::string* error) const {
-  DCHECK(error != NULL);
-  DCHECK(error->empty());
-  return true;
-}
-
 void AppInstaller::OnWebContentsDestroyed(content::WebContents* web_contents) {
   callback_.Run(false, kWebContentsDestroyedError,
                 extensions::webstore_install::OTHER_ERROR);
diff --git a/chrome/browser/extensions/api/webstore_widget_private/app_installer.h b/chrome/browser/extensions/api/webstore_widget_private/app_installer.h
index b142bf7..7f379fe 100644
--- a/chrome/browser/extensions/api/webstore_widget_private/app_installer.h
+++ b/chrome/browser/extensions/api/webstore_widget_private/app_installer.h
@@ -37,16 +37,11 @@
 
   // WebstoreStandaloneInstaller implementation.
   bool CheckRequestorAlive() const override;
-  const GURL& GetRequestorURL() const override;
   bool ShouldShowPostInstallUI() const override;
   bool ShouldShowAppInstalledBubble() const override;
   content::WebContents* GetWebContents() const override;
   std::unique_ptr<ExtensionInstallPrompt::Prompt> CreateInstallPrompt()
       const override;
-  bool CheckInlineInstallPermitted(const base::DictionaryValue& webstore_data,
-                                   std::string* error) const override;
-  bool CheckRequestorPermitted(const base::DictionaryValue& webstore_data,
-                               std::string* error) const override;
 
  private:
   class WebContentsObserver;
diff --git a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
index a9380ce..147d3e6 100644
--- a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
+++ b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
@@ -59,6 +59,7 @@
 #include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
 #include "extensions/browser/info_map.h"
 #include "extensions/browser/io_thread_extension_message_filter.h"
+#include "extensions/browser/url_loader_factory_manager.h"
 #include "extensions/browser/url_request_util.h"
 #include "extensions/browser/view_type_utils.h"
 #include "extensions/common/constants.h"
@@ -851,6 +852,17 @@
 }
 
 // static
+network::mojom::URLLoaderFactoryPtrInfo
+ChromeContentBrowserClientExtensionsPart::
+    CreateURLLoaderFactoryForNetworkRequests(
+        content::RenderProcessHost* process,
+        network::mojom::NetworkContext* network_context,
+        const url::Origin& request_initiator) {
+  return URLLoaderFactoryManager::CreateFactory(process, network_context,
+                                                request_initiator);
+}
+
+// static
 void ChromeContentBrowserClientExtensionsPart::RecordShouldAllowOpenURLFailure(
     ShouldAllowOpenURLFailureReason reason,
     const GURL& site_url) {
diff --git a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h
index 2f0a8c84..8668fc39 100644
--- a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h
+++ b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h
@@ -11,10 +11,13 @@
 #include "base/macros.h"
 #include "chrome/browser/chrome_content_browser_client_parts.h"
 #include "content/public/common/resource_type.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "ui/base/page_transition_types.h"
 
 namespace content {
 struct Referrer;
+class RenderProcessHost;
 class ResourceContext;
 class VpnServiceProxy;
 }
@@ -89,6 +92,12 @@
       int render_process_id,
       content::ResourceType resource_type);
 
+  static network::mojom::URLLoaderFactoryPtrInfo
+  CreateURLLoaderFactoryForNetworkRequests(
+      content::RenderProcessHost* process,
+      network::mojom::NetworkContext* network_context,
+      const url::Origin& request_initiator);
+
  private:
   FRIEND_TEST_ALL_PREFIXES(ChromeContentBrowserClientExtensionsPartTest,
                            ShouldAllowOpenURLMetricsForEmptySiteURL);
diff --git a/chrome/browser/extensions/chrome_extensions_interface_registration.cc b/chrome/browser/extensions/chrome_extensions_interface_registration.cc
index 81dc39fb..be85bab 100644
--- a/chrome/browser/extensions/chrome_extensions_interface_registration.cc
+++ b/chrome/browser/extensions/chrome_extensions_interface_registration.cc
@@ -20,7 +20,10 @@
 #include "chromeos/chromeos_features.h"
 #include "chromeos/services/ime/public/mojom/constants.mojom.h"
 #include "chromeos/services/ime/public/mojom/input_engine.mojom.h"
+#include "chromeos/services/media_perception/public/mojom/media_perception.mojom.h"
 #include "content/public/common/service_manager_connection.h"
+#include "extensions/browser/api/extensions_api_client.h"
+#include "extensions/browser/api/media_perception_private/media_perception_api_delegate.h"
 #include "services/service_manager/public/cpp/connector.h"
 #endif
 
@@ -65,6 +68,25 @@
         &ForwardRequest<chromeos::ime::mojom::InputEngineManager>,
         chromeos::ime::mojom::kServiceName));
   }
+
+  if (extension->permissions_data()->HasAPIPermission(
+          APIPermission::kMediaPerceptionPrivate)) {
+    extensions::ExtensionsAPIClient* client =
+        extensions::ExtensionsAPIClient::Get();
+    extensions::MediaPerceptionAPIDelegate* delegate = nullptr;
+    if (client)
+      delegate = client->GetMediaPerceptionAPIDelegate();
+    if (delegate) {
+      // Note that it is safe to use base::Unretained here because |delegate| is
+      // owned by the |client|, which is instantiated by the
+      // ChromeExtensionsBrowserClient, which in turn is owned and lives as long
+      // as the BrowserProcessImpl.
+      registry->AddInterface(
+          base::BindRepeating(&extensions::MediaPerceptionAPIDelegate::
+                                  ForwardMediaPerceptionRequest,
+                              base::Unretained(delegate)));
+    }
+  }
 #endif
 }
 
diff --git a/chrome/browser/extensions/extension_install_prompt.cc b/chrome/browser/extensions/extension_install_prompt.cc
index 2f7ef04..8694f7c 100644
--- a/chrome/browser/extensions/extension_install_prompt.cc
+++ b/chrome/browser/extensions/extension_install_prompt.cc
@@ -53,9 +53,9 @@
 namespace {
 
 bool AllowWebstoreData(ExtensionInstallPrompt::PromptType type) {
-  return type == ExtensionInstallPrompt::INLINE_INSTALL_PROMPT ||
-         type == ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT ||
-         type == ExtensionInstallPrompt::REPAIR_PROMPT;
+  return type == ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT ||
+         type == ExtensionInstallPrompt::REPAIR_PROMPT ||
+         type == ExtensionInstallPrompt::WEBSTORE_WIDGET_PROMPT;
 }
 
 // Returns bitmap for the default icon with size equal to the default icon's
@@ -179,7 +179,7 @@
   int id = -1;
   switch (type_) {
     case INSTALL_PROMPT:
-    case INLINE_INSTALL_PROMPT:
+    case WEBSTORE_WIDGET_PROMPT:
       id = IDS_EXTENSION_INSTALL_PROMPT_TITLE;
       break;
     case RE_ENABLE_PROMPT:
@@ -234,7 +234,7 @@
   int id = -1;
   switch (type_) {
     case INSTALL_PROMPT:
-    case INLINE_INSTALL_PROMPT:
+    case WEBSTORE_WIDGET_PROMPT:
       if (extension_->is_app())
         id = IDS_EXTENSION_INSTALL_PROMPT_ACCEPT_BUTTON_APP;
       else if (extension_->is_theme())
@@ -295,7 +295,7 @@
   int id = -1;
   switch (type_) {
     case INSTALL_PROMPT:
-    case INLINE_INSTALL_PROMPT:
+    case WEBSTORE_WIDGET_PROMPT:
     case RE_ENABLE_PROMPT:
     case REMOTE_INSTALL_PROMPT:
     case REPAIR_PROMPT:
@@ -323,7 +323,7 @@
   int id = -1;
   switch (type_) {
     case INSTALL_PROMPT:
-    case INLINE_INSTALL_PROMPT:
+    case WEBSTORE_WIDGET_PROMPT:
     case EXTERNAL_INSTALL_PROMPT:
     case REMOTE_INSTALL_PROMPT:
     case DELEGATED_PERMISSIONS_PROMPT:
@@ -361,13 +361,6 @@
   return GetPermissionCount() > 0 || type_ == POST_INSTALL_PERMISSIONS_PROMPT;
 }
 
-bool ExtensionInstallPrompt::Prompt::ShouldUseTabModalDialog() const {
-  // For inline install, we want the install prompt to be tab modal so that the
-  // dialog is always clearly associated with the page that made the inline
-  // install request.
-  return type_ == INLINE_INSTALL_PROMPT;
-}
-
 void ExtensionInstallPrompt::Prompt::AppendRatingStars(
     StarAppender appender, void* data) const {
   CHECK(appender);
diff --git a/chrome/browser/extensions/extension_install_prompt.h b/chrome/browser/extensions/extension_install_prompt.h
index db454db0..ec13a56b 100644
--- a/chrome/browser/extensions/extension_install_prompt.h
+++ b/chrome/browser/extensions/extension_install_prompt.h
@@ -55,7 +55,7 @@
   enum PromptType {
     UNSET_PROMPT_TYPE = -1,
     INSTALL_PROMPT = 0,
-    INLINE_INSTALL_PROMPT = 1,
+    // INLINE_INSTALL_PROMPT_DEPRECATED = 1,
     // BUNDLE_INSTALL_PROMPT_DEPRECATED = 2,
     RE_ENABLE_PROMPT = 3,
     PERMISSIONS_PROMPT = 4,
@@ -67,6 +67,7 @@
     DELEGATED_PERMISSIONS_PROMPT = 10,
     // DELEGATED_BUNDLE_PERMISSIONS_PROMPT_DEPRECATED = 11,
     NUM_PROMPT_TYPES = 12,
+    WEBSTORE_WIDGET_PROMPT = 13,
   };
 
   // The last prompt type to display; only used for testing.
@@ -111,8 +112,6 @@
 
     bool ShouldShowPermissions() const;
 
-    bool ShouldUseTabModalDialog() const;
-
     // Getters for webstore metadata. Only populated when the type is
     // INLINE_INSTALL_PROMPT, EXTERNAL_INSTALL_PROMPT, or REPAIR_PROMPT.
 
diff --git a/chrome/browser/extensions/extension_reenabler.cc b/chrome/browser/extensions/extension_reenabler.cc
deleted file mode 100644
index 407166a..0000000
--- a/chrome/browser/extensions/extension_reenabler.cc
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/extensions/extension_reenabler.h"
-
-#include "base/logging.h"
-#include "base/memory/ptr_util.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/webstore_data_fetcher.h"
-#include "chrome/browser/extensions/webstore_inline_installer.h"
-#include "chrome/browser/net/system_network_context_manager.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/storage_partition.h"
-#include "content/public/browser/web_contents.h"
-#include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_registry.h"
-#include "extensions/browser/extension_system.h"
-#include "extensions/common/extension.h"
-
-namespace extensions {
-
-ExtensionReenabler::~ExtensionReenabler() {
-  if (!finished_)
-    Finish(ABORTED);
-}
-
-// static
-std::unique_ptr<ExtensionReenabler> ExtensionReenabler::PromptForReenable(
-    const scoped_refptr<const Extension>& extension,
-    content::BrowserContext* browser_context,
-    content::WebContents* web_contents,
-    const GURL& referrer_url,
-    const Callback& callback) {
-#if DCHECK_IS_ON()
-  // We should only try to reenable an extension that is, in fact, disabled.
-  DCHECK(ExtensionRegistry::Get(browser_context)->disabled_extensions().
-             Contains(extension->id()));
-  // Currently, this should only be used for extensions that are disabled due
-  // to a permissions increase.
-  int disable_reasons =
-      ExtensionPrefs::Get(browser_context)->GetDisableReasons(extension->id());
-  DCHECK_NE(0, disable_reasons & disable_reason::DISABLE_PERMISSIONS_INCREASE);
-#endif  // DCHECK_IS_ON()
-
-  return base::WrapUnique(new ExtensionReenabler(
-      extension, browser_context, referrer_url, callback, web_contents,
-      ExtensionInstallPrompt::GetDefaultShowDialogCallback()));
-}
-
-// static
-std::unique_ptr<ExtensionReenabler>
-ExtensionReenabler::PromptForReenableWithCallbackForTest(
-    const scoped_refptr<const Extension>& extension,
-    content::BrowserContext* browser_context,
-    const Callback& callback,
-    const ExtensionInstallPrompt::ShowDialogCallback& show_dialog_callback) {
-  return base::WrapUnique(new ExtensionReenabler(extension, browser_context,
-                                                 GURL(), callback, nullptr,
-                                                 show_dialog_callback));
-}
-
-ExtensionReenabler::ExtensionReenabler(
-    const scoped_refptr<const Extension>& extension,
-    content::BrowserContext* browser_context,
-    const GURL& referrer_url,
-    const Callback& callback,
-    content::WebContents* web_contents,
-    const ExtensionInstallPrompt::ShowDialogCallback& show_dialog_callback)
-    : extension_(extension),
-      browser_context_(browser_context),
-      referrer_url_(referrer_url),
-      callback_(callback),
-      show_dialog_callback_(show_dialog_callback),
-      finished_(false),
-      registry_observer_(this),
-      weak_factory_(this) {
-  DCHECK(extension_.get());
-  registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
-
-  install_prompt_.reset(new ExtensionInstallPrompt(web_contents));
-
-  // If we have a non-empty referrer, then we have to validate that it's a valid
-  // url for the extension.
-  if (!referrer_url_.is_empty()) {
-    webstore_data_fetcher_.reset(
-        new WebstoreDataFetcher(this, referrer_url_, extension->id()));
-    webstore_data_fetcher_->Start(
-        content::BrowserContext::GetDefaultStoragePartition(browser_context_)
-            ->GetURLLoaderFactoryForBrowserProcess()
-            .get());
-  } else {
-    ExtensionInstallPrompt::PromptType type =
-        ExtensionInstallPrompt::GetReEnablePromptTypeForExtension(
-            browser_context, extension.get());
-    install_prompt_->ShowDialog(
-        base::Bind(&ExtensionReenabler::OnInstallPromptDone,
-                   weak_factory_.GetWeakPtr()),
-        extension.get(), nullptr,
-        std::make_unique<ExtensionInstallPrompt::Prompt>(type),
-        show_dialog_callback_);
-  }
-}
-
-void ExtensionReenabler::OnInstallPromptDone(
-    ExtensionInstallPrompt::Result install_result) {
-  ReenableResult result = ABORTED;
-  switch (install_result) {
-    case ExtensionInstallPrompt::Result::ACCEPTED: {
-      // Stop observing - we don't want to see our own enablement.
-      registry_observer_.RemoveAll();
-
-      ExtensionService* extension_service =
-          ExtensionSystem::Get(browser_context_)->extension_service();
-      if (extension_service->browser_terminating()) {
-        result = ABORTED;
-      } else {
-        extension_service->GrantPermissionsAndEnableExtension(extension_.get());
-        // The re-enable could have failed if the extension is disallowed by
-        // policy.
-        bool enabled = ExtensionRegistry::Get(browser_context_)
-                           ->enabled_extensions()
-                           .GetByID(extension_->id()) != nullptr;
-        result = enabled ? REENABLE_SUCCESS : NOT_ALLOWED;
-      }
-      break;
-    }
-    case ExtensionInstallPrompt::Result::USER_CANCELED:
-      result = USER_CANCELED;
-      break;
-    case ExtensionInstallPrompt::Result::ABORTED:
-      result = ABORTED;
-      break;
-  }
-
-  Finish(result);
-}
-
-void ExtensionReenabler::OnExtensionLoaded(
-    content::BrowserContext* browser_context,
-    const Extension* extension) {
-  // If the user chose to manually re-enable the extension then, for all
-  // intents and purposes, this was a success.
-  if (extension == extension_.get())
-    Finish(REENABLE_SUCCESS);
-}
-
-void ExtensionReenabler::OnExtensionUninstalled(
-    content::BrowserContext* browser_context,
-    const Extension* extension,
-    UninstallReason reason) {
-  if (extension == extension_.get())
-    Finish(USER_CANCELED);
-}
-
-void ExtensionReenabler::OnWebstoreRequestFailure() {
-  Finish(ABORTED);
-}
-
-void ExtensionReenabler::OnWebstoreResponseParseSuccess(
-    std::unique_ptr<base::DictionaryValue> webstore_data) {
-  DCHECK(!referrer_url_.is_empty());
-  std::string error;
-  if (!WebstoreInlineInstaller::IsRequestorPermitted(*webstore_data,
-                                                     referrer_url_,
-                                                     &error)) {
-    Finish(NOT_ALLOWED);
-  } else {
-    ExtensionInstallPrompt::PromptType type =
-        ExtensionInstallPrompt::GetReEnablePromptTypeForExtension(
-            browser_context_, extension_.get());
-    install_prompt_->ShowDialog(
-        base::Bind(&ExtensionReenabler::OnInstallPromptDone,
-                   weak_factory_.GetWeakPtr()),
-        extension_.get(), nullptr,
-        std::make_unique<ExtensionInstallPrompt::Prompt>(type),
-        show_dialog_callback_);
-  }
-}
-
-void ExtensionReenabler::OnWebstoreResponseParseFailure(
-    const std::string& error) {
-  Finish(ABORTED);
-}
-
-void ExtensionReenabler::Finish(ReenableResult result) {
-  DCHECK(!finished_);
-  finished_ = true;
-  registry_observer_.RemoveAll();
-  callback_.Run(result);
-}
-
-}  // namespace extensions
diff --git a/chrome/browser/extensions/extension_reenabler.h b/chrome/browser/extensions/extension_reenabler.h
deleted file mode 100644
index 4c19f32..0000000
--- a/chrome/browser/extensions/extension_reenabler.h
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_REENABLER_H_
-#define CHROME_BROWSER_EXTENSIONS_EXTENSION_REENABLER_H_
-
-#include <memory>
-
-#include "base/callback.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "base/scoped_observer.h"
-#include "chrome/browser/extensions/extension_install_prompt.h"
-#include "chrome/browser/extensions/webstore_data_fetcher_delegate.h"
-#include "extensions/browser/extension_registry_observer.h"
-
-namespace content {
-class BrowserContext;
-}
-
-namespace extensions {
-
-class Extension;
-class ExtensionRegistry;
-class WebstoreDataFetcher;
-
-// A class to handle reenabling an extension disabled due to a permissions
-// increase.
-// TODO(devlin): Once we get the UI figured out, we should also have this handle
-// other disable reasons.
-class ExtensionReenabler : public ExtensionRegistryObserver,
-                           public WebstoreDataFetcherDelegate {
- public:
-  enum ReenableResult {
-    REENABLE_SUCCESS,  // The extension has been successfully re-enabled.
-    USER_CANCELED,     // The user chose to not re-enable the extension.
-    NOT_ALLOWED,       // The re-enable is not allowed.
-    ABORTED,           // The re-enable process was aborted due to, e.g.,
-                       // shutdown or a bad webstore response.
-  };
-
-  using Callback = base::Callback<void(ReenableResult)>;
-
-  ~ExtensionReenabler() override;
-
-  // Prompts the user to reenable the given |extension|, and calls |callback|
-  // upon completion.
-  // If |referrer_url| is non-empty, then this will also check to make sure
-  // that the referrer_url is listed as a trusted url by the extension.
-  static std::unique_ptr<ExtensionReenabler> PromptForReenable(
-      const scoped_refptr<const Extension>& extension,
-      content::BrowserContext* browser_context,
-      content::WebContents* web_contents,
-      const GURL& referrer_url,
-      const Callback& callback);
-
-  // Like PromptForReenable, but allows tests to inject the
-  // ExtensionInstallPrompt.
-  static std::unique_ptr<ExtensionReenabler>
-  PromptForReenableWithCallbackForTest(
-      const scoped_refptr<const Extension>& extension,
-      content::BrowserContext* browser_context,
-      const Callback& callback,
-      const ExtensionInstallPrompt::ShowDialogCallback& show_callback);
-
- private:
-  ExtensionReenabler(
-      const scoped_refptr<const Extension>& extension,
-      content::BrowserContext* browser_context,
-      const GURL& referrer_url,
-      const Callback& callback,
-      content::WebContents* web_contents,
-      const ExtensionInstallPrompt::ShowDialogCallback& show_callback);
-
-  void OnInstallPromptDone(ExtensionInstallPrompt::Result result);
-
-  // ExtensionRegistryObserver:
-  void OnExtensionLoaded(content::BrowserContext* browser_context,
-                         const Extension* extension) override;
-  void OnExtensionUninstalled(content::BrowserContext* browser_context,
-                              const Extension* extension,
-                              UninstallReason reason) override;
-
-  // WebstoreDataFetcherDelegate:
-  void OnWebstoreRequestFailure() override;
-  void OnWebstoreResponseParseSuccess(
-      std::unique_ptr<base::DictionaryValue> webstore_data) override;
-  void OnWebstoreResponseParseFailure(const std::string& error) override;
-
-  // Sets the |finished_| bit and runs |callback_| with the given |result|.
-  void Finish(ReenableResult result);
-
-  // The extension to be re-enabled.
-  scoped_refptr<const Extension> extension_;
-
-  // The associated browser context.
-  content::BrowserContext* browser_context_;
-
-  // The url of the referrer, if any. If this is non-empty, it means we have
-  // to check that the url is trusted by the extension.
-  GURL referrer_url_;
-
-  // The callback to run upon completion.
-  Callback callback_;
-
-  // The callback to use to show the dialog.
-  ExtensionInstallPrompt::ShowDialogCallback show_dialog_callback_;
-
-  // The re-enable prompt.
-  std::unique_ptr<ExtensionInstallPrompt> install_prompt_;
-
-  // Indicates whether the re-enable process finished.
-  bool finished_;
-
-  // The data fetcher for retrieving webstore data.
-  std::unique_ptr<WebstoreDataFetcher> webstore_data_fetcher_;
-
-  ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      registry_observer_;
-
-  base::WeakPtrFactory<ExtensionReenabler> weak_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(ExtensionReenabler);
-};
-
-}  // namespace extensions
-
-#endif  // CHROME_BROWSER_EXTENSIONS_EXTENSION_REENABLER_H_
diff --git a/chrome/browser/extensions/extension_reenabler_unittest.cc b/chrome/browser/extensions/extension_reenabler_unittest.cc
deleted file mode 100644
index 2bc89df5..0000000
--- a/chrome/browser/extensions/extension_reenabler_unittest.cc
+++ /dev/null
@@ -1,269 +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 <utility>
-
-#include "base/macros.h"
-#include "base/run_loop.h"
-#include "chrome/browser/extensions/extension_install_prompt.h"
-#include "chrome/browser/extensions/extension_reenabler.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_service_test_base.h"
-#include "chrome/browser/extensions/extension_system_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/test/base/testing_profile.h"
-#include "components/crx_file/id_util.h"
-#include "extensions/browser/extension_dialog_auto_confirm.h"
-#include "extensions/browser/extension_registry.h"
-#include "extensions/browser/management_policy.h"
-#include "extensions/browser/test_extensions_browser_client.h"
-#include "extensions/common/extension.h"
-#include "extensions/common/extension_builder.h"
-#include "extensions/common/value_builder.h"
-
-namespace extensions {
-
-namespace {
-
-// A simple provider that says all extensions must remain disabled.
-class TestManagementProvider : public ManagementPolicy::Provider {
- public:
-  TestManagementProvider() {}
-  ~TestManagementProvider() override {}
-
- private:
-  // MananagementPolicy::Provider:
-  std::string GetDebugPolicyProviderName() const override { return "test"; }
-  bool MustRemainDisabled(const Extension* extension,
-                          disable_reason::DisableReason* reason,
-                          base::string16* error) const override {
-    return true;
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(TestManagementProvider);
-};
-
-// A helper class for all the various callbacks associated with reenabling an
-// extension. This class also helps store the results of the run.
-class CallbackHelper {
- public:
-  CallbackHelper() {}
-  ~CallbackHelper() {}
-
-  // Get a callback to run on the completion of the reenable process and reset
-  // |result_|.
-  ExtensionReenabler::Callback GetCallback() {
-    result_.reset();
-    return base::Bind(&CallbackHelper::OnComplete,
-                      base::Unretained(this));
-  }
-
-  // Check if we have receved any result, and if it matches the expected one.
-  bool has_result() const { return result_ != nullptr; }
-  bool result_matches(ExtensionReenabler::ReenableResult expected) const {
-    return result_.get() && *result_ == expected;
-  }
-
-  // Create a test ExtensionInstallPrompt that will not display any UI (which
-  // causes unit tests to crash), but rather runs the given |quit_closure| (with
-  // the prompt still active|.
-  ExtensionInstallPrompt::ShowDialogCallback CreateShowCallback(
-      base::OnceClosure quit_closure) {
-    quit_closure_ = std::move(quit_closure);
-    return base::Bind(&CallbackHelper::OnShow, base::Unretained(this));
-  }
-
- private:
-  // The callback to run once the reenable process finishes.
-  void OnComplete(ExtensionReenabler::ReenableResult result) {
-    result_.reset(new ExtensionReenabler::ReenableResult(result));
-  }
-
-  // The callback to run when a test ExtensionInstallPrompt is ready to show.
-  void OnShow(ExtensionInstallPromptShowParams* show_params,
-              const ExtensionInstallPrompt::DoneCallback& done_callback,
-              std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt) {
-    DCHECK(quit_closure_);
-    std::move(quit_closure_).Run();
-  }
-
-  // The closure to quit the currently-running loop; used with test
-  // ExtensionInstallPrompts.
-  base::OnceClosure quit_closure_;
-
-  // The result of the reenable process, or null if the process hasn't finished.
-  std::unique_ptr<ExtensionReenabler::ReenableResult> result_;
-
-  DISALLOW_COPY_AND_ASSIGN(CallbackHelper);
-};
-
-}  // namespace
-
-class ExtensionReenablerUnitTest : public ExtensionServiceTestBase {
- public:
-  ExtensionReenablerUnitTest() {}
-  ~ExtensionReenablerUnitTest() override {}
-
- private:
-  void SetUp() override;
-  void TearDown() override;
-
-  std::unique_ptr<TestExtensionsBrowserClient> test_browser_client_;
-
-  DISALLOW_COPY_AND_ASSIGN(ExtensionReenablerUnitTest);
-};
-
-void ExtensionReenablerUnitTest::SetUp() {
-  ExtensionServiceTestBase::SetUp();
-  InitializeEmptyExtensionService();
-  // We need a TestExtensionsBrowserClient because the real one tries to
-  // implicitly convert any browser context to a (non-Testing)Profile.
-  test_browser_client_.reset(new TestExtensionsBrowserClient(profile()));
-  test_browser_client_->set_extension_system_factory(
-      ExtensionSystemFactory::GetInstance());
-  ExtensionsBrowserClient::Set(test_browser_client_.get());
-}
-
-void ExtensionReenablerUnitTest::TearDown() {
-  profile_.reset();
-  ExtensionsBrowserClient::Set(nullptr);
-  test_browser_client_.reset();
-  ExtensionServiceTestBase::TearDown();
-}
-
-// Test that the ExtensionReenabler reenables disabled extensions.
-TEST_F(ExtensionReenablerUnitTest, TestReenablingDisabledExtension) {
-  // Create a simple extension and add it to the service.
-  scoped_refptr<const Extension> extension =
-      ExtensionBuilder()
-          .SetManifest(DictionaryBuilder()
-                           .Set("name", "test ext")
-                           .Set("version", "1.0")
-                           .Set("manifest_version", 2)
-                           .Set("description", "a test ext")
-                           .Build())
-          .SetID(crx_file::id_util::GenerateId("test ext"))
-          .Build();
-  service()->AddExtension(extension.get());
-  EXPECT_TRUE(registry()->enabled_extensions().Contains(extension->id()));
-
-  CallbackHelper callback_helper;
-
-  // Check that the ExtensionReenabler can re-enable disabled extensions.
-  {
-    // Disable the extension due to a permissions increase (the only type of
-    // disablement we handle with the ExtensionReenabler so far).
-    service()->DisableExtension(extension->id(),
-                                disable_reason::DISABLE_PERMISSIONS_INCREASE);
-    // Sanity check that it's disabled.
-    EXPECT_TRUE(registry()->disabled_extensions().Contains(extension->id()));
-
-    // Automatically confirm install prompts.
-    ScopedTestDialogAutoConfirm auto_confirm(
-        ScopedTestDialogAutoConfirm::ACCEPT);
-
-    // Run the ExtensionReenabler.
-    std::unique_ptr<ExtensionReenabler> extension_reenabler =
-        ExtensionReenabler::PromptForReenable(extension, profile(),
-                                              nullptr,  // No web contents.
-                                              GURL(),   // No referrer.
-                                              callback_helper.GetCallback());
-    base::RunLoop().RunUntilIdle();
-
-    // The extension should be enabled.
-    EXPECT_TRUE(registry()->enabled_extensions().Contains(extension->id()));
-    EXPECT_TRUE(
-        callback_helper.result_matches(ExtensionReenabler::REENABLE_SUCCESS));
-  }
-
-  // Check that we don't re-enable extensions that must remain disabled, and
-  // that the re-enabler reports failure correctly.
-  {
-    ScopedTestDialogAutoConfirm auto_confirm(
-        ScopedTestDialogAutoConfirm::ACCEPT);
-
-    ManagementPolicy* management_policy =
-        ExtensionSystem::Get(browser_context())->management_policy();
-    ASSERT_TRUE(management_policy);
-    TestManagementProvider test_provider;
-    management_policy->RegisterProvider(&test_provider);
-    service()->DisableExtension(extension->id(),
-                                disable_reason::DISABLE_PERMISSIONS_INCREASE);
-
-    std::unique_ptr<ExtensionReenabler> extension_reenabler =
-        ExtensionReenabler::PromptForReenable(extension, profile(),
-                                              nullptr,  // No web contents.
-                                              GURL(),   // No referrer.
-                                              callback_helper.GetCallback());
-    base::RunLoop().RunUntilIdle();
-
-    // The extension should be enabled.
-    EXPECT_TRUE(registry()->disabled_extensions().Contains(extension->id()));
-    EXPECT_TRUE(
-        callback_helper.result_matches(ExtensionReenabler::NOT_ALLOWED));
-
-    management_policy->UnregisterProvider(&test_provider);
-  }
-
-  // Check that canceling the re-enable prompt doesn't re-enable the extension.
-  {
-    // Disable it again, and try canceling the prompt.
-    service()->DisableExtension(extension->id(),
-                                disable_reason::DISABLE_PERMISSIONS_INCREASE);
-    ScopedTestDialogAutoConfirm auto_confirm(
-        ScopedTestDialogAutoConfirm::CANCEL);
-    std::unique_ptr<ExtensionReenabler> extension_reenabler =
-        ExtensionReenabler::PromptForReenable(extension, profile(),
-                                              nullptr,  // No web contents.
-                                              GURL(),   // No referrer.
-                                              callback_helper.GetCallback());
-    base::RunLoop().RunUntilIdle();
-
-    // The extension should remain disabled.
-    EXPECT_TRUE(registry()->disabled_extensions().Contains(extension->id()));
-    EXPECT_TRUE(
-        callback_helper.result_matches(ExtensionReenabler::USER_CANCELED));
-  }
-
-  // Test that if the extension is re-enabled while the prompt is active, the
-  // prompt exits and reports success.
-  {
-    base::RunLoop run_loop;
-    std::unique_ptr<ExtensionReenabler> extension_reenabler =
-        ExtensionReenabler::PromptForReenableWithCallbackForTest(
-            extension, profile(), callback_helper.GetCallback(),
-            callback_helper.CreateShowCallback(run_loop.QuitClosure()));
-    run_loop.Run();
-
-    // We shouldn't have any result yet (the user hasn't confirmed or canceled).
-    EXPECT_FALSE(callback_helper.has_result());
-
-    // Reenable the extension. This should count as a success for reenabling.
-    service()->GrantPermissionsAndEnableExtension(extension.get());
-    EXPECT_TRUE(
-        callback_helper.result_matches(ExtensionReenabler::REENABLE_SUCCESS));
-  }
-
-  // Test that prematurely destroying the re-enable prompt doesn't crash and
-  // reports an "aborted" result.
-  {
-    // Disable again, and create another prompt.
-    service()->DisableExtension(extension->id(),
-                                disable_reason::DISABLE_PERMISSIONS_INCREASE);
-    base::RunLoop run_loop;
-    std::unique_ptr<ExtensionReenabler> extension_reenabler =
-        ExtensionReenabler::PromptForReenableWithCallbackForTest(
-            extension, profile(), callback_helper.GetCallback(),
-            callback_helper.CreateShowCallback(run_loop.QuitClosure()));
-    run_loop.Run();
-    EXPECT_FALSE(callback_helper.has_result());
-    // Destroy the reenabler to simulate the owning context being shut down
-    // (e.g., the tab closing).
-    extension_reenabler.reset();
-    EXPECT_TRUE(
-        callback_helper.result_matches(ExtensionReenabler::ABORTED));
-  }
-}
-
-}  // namespace extensions
diff --git a/chrome/browser/extensions/tab_helper.cc b/chrome/browser/extensions/tab_helper.cc
index acb3d78..68eb168f 100644
--- a/chrome/browser/extensions/tab_helper.cc
+++ b/chrome/browser/extensions/tab_helper.cc
@@ -20,8 +20,6 @@
 #include "chrome/browser/extensions/install_observer.h"
 #include "chrome/browser/extensions/install_tracker.h"
 #include "chrome/browser/extensions/install_tracker_factory.h"
-#include "chrome/browser/extensions/webstore_inline_installer.h"
-#include "chrome/browser/extensions/webstore_inline_installer_factory.h"
 #include "chrome/browser/installable/installable_metrics.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sessions/session_tab_helper.h"
@@ -70,75 +68,6 @@
 
 namespace extensions {
 
-// A helper class to watch the progress of inline installation and update the
-// renderer. Owned by the TabHelper.
-class TabHelper::InlineInstallObserver : public InstallObserver {
- public:
-  InlineInstallObserver(TabHelper* tab_helper,
-                        content::BrowserContext* browser_context,
-                        const ExtensionId& extension_id,
-                        bool observe_download_progress,
-                        bool observe_install_stage)
-      : tab_helper_(tab_helper),
-        extension_id_(extension_id),
-        observe_download_progress_(observe_download_progress),
-        observe_install_stage_(observe_install_stage),
-        install_observer_(this) {
-    DCHECK(tab_helper);
-    DCHECK(observe_download_progress || observe_install_stage);
-    InstallTracker* install_tracker =
-        InstallTrackerFactory::GetForBrowserContext(browser_context);
-    if (install_tracker)
-      install_observer_.Add(install_tracker);
-  }
-  ~InlineInstallObserver() override {}
-
- private:
-  // InstallObserver:
-  void OnBeginExtensionDownload(const ExtensionId& extension_id) override {
-    SendInstallStageChangedMessage(extension_id,
-                                   api::webstore::INSTALL_STAGE_DOWNLOADING);
-  }
-  void OnDownloadProgress(const ExtensionId& extension_id,
-                          int percent_downloaded) override {
-    if (observe_download_progress_ && extension_id == extension_id_) {
-      auto iter =
-          tab_helper_->inline_install_progress_listeners_.find(extension_id);
-      DCHECK(iter != tab_helper_->inline_install_progress_listeners_.end());
-      iter->second->InlineInstallDownloadProgress(percent_downloaded);
-    }
-  }
-  void OnBeginCrxInstall(const ExtensionId& extension_id) override {
-    SendInstallStageChangedMessage(extension_id,
-                                   api::webstore::INSTALL_STAGE_INSTALLING);
-  }
-  void OnShutdown() override { install_observer_.RemoveAll(); }
-
-  void SendInstallStageChangedMessage(const ExtensionId& extension_id,
-                                      api::webstore::InstallStage stage) {
-    if (observe_install_stage_ && extension_id == extension_id_) {
-      auto iter =
-          tab_helper_->inline_install_progress_listeners_.find(extension_id);
-      DCHECK(iter != tab_helper_->inline_install_progress_listeners_.end());
-      iter->second->InlineInstallStageChanged(stage);
-    }
-  }
-
-  // The owning TabHelper (guaranteed to be valid).
-  TabHelper* const tab_helper_;
-
-  // The id of the extension to observe.
-  ExtensionId extension_id_;
-
-  // Whether or not to observe download/install progress.
-  const bool observe_download_progress_;
-  const bool observe_install_stage_;
-
-  ScopedObserver<InstallTracker, InstallObserver> install_observer_;
-
-  DISALLOW_COPY_AND_ASSIGN(InlineInstallObserver);
-};
-
 TabHelper::~TabHelper() {
   RemoveScriptExecutionObserver(ActivityLog::GetInstance(profile_));
 }
@@ -152,9 +81,7 @@
       script_executor_(
           new ScriptExecutor(web_contents, &script_execution_observers_)),
       extension_action_runner_(new ExtensionActionRunner(web_contents)),
-      webstore_inline_installer_factory_(new WebstoreInlineInstallerFactory()),
       registry_observer_(this),
-      bindings_(web_contents, this),
       image_loader_ptr_factory_(this),
       weak_ptr_factory_(this) {
   // The ActiveTabPermissionManager requires a session ID; ensure this
@@ -379,80 +306,6 @@
     pending_web_app_action_ = NONE;
 }
 
-void TabHelper::DoInlineInstall(
-    const std::string& webstore_item_id,
-    int listeners_mask,
-    mojom::InlineInstallProgressListenerPtr install_progress_listener,
-    DoInlineInstallCallback callback) {
-  content::RenderFrameHost* host = web_contents()->GetMainFrame();
-  if (bindings_.GetCurrentTargetFrame() != host) {
-    NOTREACHED();
-    return;
-  }
-
-  GURL requestor_url(host->GetLastCommittedURL());
-  // Check that the listener is reasonable. We should never get anything other
-  // than an install stage listener, a download listener, or both.
-  // The requestor_url should also be valid, and the renderer should disallow
-  // child frames from sending the IPC.
-  if ((listeners_mask & ~(api::webstore::INSTALL_STAGE_LISTENER |
-                          api::webstore::DOWNLOAD_PROGRESS_LISTENER)) != 0 ||
-      !requestor_url.is_valid() || requestor_url == url::kAboutBlankURL) {
-    NOTREACHED();
-    return;
-  }
-
-  if (base::ContainsKey(install_callbacks_, webstore_item_id)) {
-    std::move(callback).Run(false, webstore_install::kInstallInProgressError,
-                            webstore_install::INSTALL_IN_PROGRESS);
-    return;
-  }
-
-  install_callbacks_[webstore_item_id] = std::move(callback);
-  inline_install_progress_listeners_[webstore_item_id] =
-      std::move(install_progress_listener);
-  // Inform the Webstore API that an inline install is happening, in case the
-  // page requested status updates.
-  ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
-  if (registry->disabled_extensions().Contains(webstore_item_id) &&
-      (ExtensionPrefs::Get(profile_)->GetDisableReasons(webstore_item_id) &
-       disable_reason::DISABLE_PERMISSIONS_INCREASE) != 0) {
-    // The extension was disabled due to permissions increase. Prompt for
-    // re-enable.
-    // TODO(devlin): We should also prompt for re-enable for other reasons,
-    // like user-disabled.
-    // For clarity, explicitly end any prior reenable process.
-    extension_reenabler_.reset();
-    extension_reenabler_ = ExtensionReenabler::PromptForReenable(
-        registry->disabled_extensions().GetByID(webstore_item_id), profile_,
-        web_contents(), requestor_url,
-        base::Bind(&TabHelper::OnReenableComplete,
-                   weak_ptr_factory_.GetWeakPtr(), webstore_item_id));
-  } else {
-    // TODO(devlin): We should adddress the case of the extension already
-    // being installed and enabled.
-    bool observe_download_progress =
-        (listeners_mask & api::webstore::DOWNLOAD_PROGRESS_LISTENER) != 0;
-    bool observe_install_stage =
-        (listeners_mask & api::webstore::INSTALL_STAGE_LISTENER) != 0;
-    if (observe_install_stage || observe_download_progress) {
-      DCHECK_EQ(0u, install_observers_.count(webstore_item_id));
-      install_observers_[webstore_item_id] =
-          std::make_unique<InlineInstallObserver>(
-              this, web_contents()->GetBrowserContext(), webstore_item_id,
-              observe_download_progress, observe_install_stage);
-    }
-
-    WebstoreStandaloneInstaller::Callback callback =
-        base::Bind(&TabHelper::OnInlineInstallComplete,
-                   weak_ptr_factory_.GetWeakPtr(), webstore_item_id);
-    scoped_refptr<WebstoreInlineInstaller> installer(
-        webstore_inline_installer_factory_->CreateInstaller(
-            web_contents(), host, webstore_item_id, requestor_url, callback));
-    installer->BeginInstall();
-  }
-}
-
 void TabHelper::OnGetAppInstallState(content::RenderFrameHost* host,
                                      const GURL& requestor_url,
                                      int return_route_id,
@@ -514,11 +367,6 @@
   }
 }
 
-void TabHelper::SetWebstoreInlineInstallerFactoryForTests(
-    WebstoreInlineInstallerFactory* factory) {
-  webstore_inline_installer_factory_.reset(factory);
-}
-
 void TabHelper::OnImageLoaded(const gfx::Image& image) {
   if (!image.IsEmpty()) {
     extension_app_icon_ = *image.ToSkBitmap();
@@ -530,47 +378,6 @@
   return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
 }
 
-void TabHelper::OnReenableComplete(const ExtensionId& extension_id,
-                                   ExtensionReenabler::ReenableResult result) {
-  // Map the re-enable results to webstore-install results.
-  webstore_install::Result webstore_result = webstore_install::SUCCESS;
-  std::string error;
-  switch (result) {
-    case ExtensionReenabler::REENABLE_SUCCESS:
-      break;  // already set
-    case ExtensionReenabler::USER_CANCELED:
-      webstore_result = webstore_install::USER_CANCELLED;
-      error = "User canceled install.";
-      break;
-    case ExtensionReenabler::NOT_ALLOWED:
-      webstore_result = webstore_install::NOT_PERMITTED;
-      error = "Install not permitted.";
-      break;
-    case ExtensionReenabler::ABORTED:
-      webstore_result = webstore_install::ABORTED;
-      error = "Aborted due to tab closing.";
-      break;
-  }
-
-  OnInlineInstallComplete(extension_id,
-                          result == ExtensionReenabler::REENABLE_SUCCESS, error,
-                          webstore_result);
-  // Note: ExtensionReenabler contained the callback with the curried-in
-  // |extension_id|; delete it last.
-  extension_reenabler_.reset();
-}
-
-void TabHelper::OnInlineInstallComplete(const ExtensionId& extension_id,
-                                        bool success,
-                                        const std::string& error,
-                                        webstore_install::Result result) {
-  install_observers_.erase(extension_id);
-  auto iter = install_callbacks_.find(extension_id);
-  DCHECK(iter != install_callbacks_.end());
-  std::move(iter->second).Run(success, success ? std::string() : error, result);
-  install_callbacks_.erase(iter);
-}
-
 WebContents* TabHelper::GetAssociatedWebContents() const {
   return web_contents();
 }
diff --git a/chrome/browser/extensions/tab_helper.h b/chrome/browser/extensions/tab_helper.h
index 8fa70a9..0db580c 100644
--- a/chrome/browser/extensions/tab_helper.h
+++ b/chrome/browser/extensions/tab_helper.h
@@ -15,9 +15,7 @@
 #include "base/observer_list.h"
 #include "base/scoped_observer.h"
 #include "chrome/browser/extensions/active_tab_permission_granter.h"
-#include "chrome/browser/extensions/extension_reenabler.h"
 #include "chrome/common/chrome_render_frame.mojom.h"
-#include "chrome/common/extensions/mojom/inline_install.mojom.h"
 #include "chrome/common/extensions/webstore_install_result.h"
 #include "chrome/common/web_application_info.h"
 #include "content/public/browser/web_contents_binding_set.h"
@@ -43,14 +41,12 @@
 class ExtensionActionRunner;
 class BookmarkAppHelper;
 class Extension;
-class WebstoreInlineInstallerFactory;
 
 // Per-tab extension helper. Also handles non-extension apps.
 class TabHelper : public content::WebContentsObserver,
                   public ExtensionFunctionDispatcher::Delegate,
                   public ExtensionRegistryObserver,
-                  public content::WebContentsUserData<TabHelper>,
-                  public mojom::InlineInstaller {
+                  public content::WebContentsUserData<TabHelper> {
  public:
   ~TabHelper() override;
 
@@ -101,14 +97,7 @@
     return active_tab_permission_granter_.get();
   }
 
-  // Sets the factory used to create inline webstore item installers.
-  // Used for testing. Takes ownership of the factory instance.
-  void SetWebstoreInlineInstallerFactoryForTests(
-      WebstoreInlineInstallerFactory* factory);
-
  private:
-  class InlineInstallObserver;
-
   // Utility function to invoke member functions on all relevant
   // ContentRulesRegistries.
   template <class Func>
@@ -148,13 +137,6 @@
                            const Extension* extension,
                            UnloadedExtensionReason reason) override;
 
-  // mojom::InlineInstall:
-  void DoInlineInstall(
-      const std::string& webstore_item_id,
-      int listeners_mask,
-      mojom::InlineInstallProgressListenerPtr install_progress_listener,
-      DoInlineInstallCallback callback) override;
-
   // Message handlers.
   void OnDidGetWebApplicationInfo(
       chrome::mojom::ChromeRenderFrameAssociatedPtr chrome_render_frame,
@@ -179,16 +161,6 @@
 
   void OnImageLoaded(const gfx::Image& image);
 
-  // WebstoreStandaloneInstaller::Callback.
-  void OnInlineInstallComplete(const ExtensionId& extension_id,
-                               bool success,
-                               const std::string& error,
-                               webstore_install::Result result);
-
-  // ExtensionReenabler::Callback.
-  void OnReenableComplete(const ExtensionId& extension_id,
-                          ExtensionReenabler::ReenableResult result);
-
   // Requests application info for the specified page. This is an asynchronous
   // request. The delegate is notified by way of OnDidGetWebApplicationInfo when
   // the data is available.
@@ -231,30 +203,9 @@
 
   std::unique_ptr<BookmarkAppHelper> bookmark_app_helper_;
 
-  // Creates WebstoreInlineInstaller instances for inline install triggers.
-  std::unique_ptr<WebstoreInlineInstallerFactory>
-      webstore_inline_installer_factory_;
-
-  // The reenable prompt for disabled extensions, if any.
-  std::unique_ptr<ExtensionReenabler> extension_reenabler_;
-
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
       registry_observer_;
 
-  // Map of InlineInstallObservers for inline installations that have progress
-  // listeners.
-  std::map<ExtensionId, std::unique_ptr<InlineInstallObserver>>
-      install_observers_;
-
-  // Map of function callbacks that are invoked when the inline installation for
-  // a particular extension (hence ExtensionId) completes.
-  std::map<ExtensionId, DoInlineInstallCallback> install_callbacks_;
-
-  content::WebContentsFrameBindingSet<mojom::InlineInstaller> bindings_;
-
-  std::map<ExtensionId, mojom::InlineInstallProgressListenerPtr>
-      inline_install_progress_listeners_;
-
   // Vend weak pointers that can be invalidated to stop in-progress loads.
   base::WeakPtrFactory<TabHelper> image_loader_ptr_factory_;
 
diff --git a/chrome/browser/extensions/webstore_data_fetcher.cc b/chrome/browser/extensions/webstore_data_fetcher.cc
index 1b84b751..558b721 100644
--- a/chrome/browser/extensions/webstore_data_fetcher.cc
+++ b/chrome/browser/extensions/webstore_data_fetcher.cc
@@ -43,10 +43,6 @@
 
 WebstoreDataFetcher::~WebstoreDataFetcher() {}
 
-void WebstoreDataFetcher::SetPostData(const std::string& data) {
-  post_data_ = data;
-}
-
 void WebstoreDataFetcher::Start(
     network::mojom::URLLoaderFactory* url_loader_factory) {
   GURL webstore_data_url(extension_urls::GetWebstoreItemJsonDataURL(id_));
@@ -86,14 +82,10 @@
   resource_request->load_flags =
       net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DISABLE_CACHE;
   resource_request->referrer = referrer_url_;
-  resource_request->method = post_data_.empty() ? "GET" : "POST";
+  resource_request->method = "GET";
   simple_url_loader_ = network::SimpleURLLoader::Create(
       std::move(resource_request), traffic_annotation);
 
-  if (!post_data_.empty())
-    simple_url_loader_->AttachStringForUpload(post_data_,
-                                              "application/octet-stream");
-
   if (max_auto_retries_ > 0) {
     simple_url_loader_->SetRetryOptions(
         max_auto_retries_,
diff --git a/chrome/browser/extensions/webstore_data_fetcher.h b/chrome/browser/extensions/webstore_data_fetcher.h
index af46509..1d1edae 100644
--- a/chrome/browser/extensions/webstore_data_fetcher.h
+++ b/chrome/browser/extensions/webstore_data_fetcher.h
@@ -36,10 +36,6 @@
                       const std::string webstore_item_id);
   ~WebstoreDataFetcher();
 
-  // Makes this request use a POST instead of GET, and sends |data| in the
-  // body of the request. If |data| is empty, this is a no-op.
-  void SetPostData(const std::string& data);
-
   void Start(network::mojom::URLLoaderFactory* url_loader_factory);
 
   void set_max_auto_retries(int max_retries) {
diff --git a/chrome/browser/extensions/webstore_data_fetcher_delegate.cc b/chrome/browser/extensions/webstore_data_fetcher_delegate.cc
index 21cab5d..72233ec 100644
--- a/chrome/browser/extensions/webstore_data_fetcher_delegate.cc
+++ b/chrome/browser/extensions/webstore_data_fetcher_delegate.cc
@@ -11,8 +11,6 @@
 const char WebstoreDataFetcherDelegate::kIdKey[] = "id";
 const char WebstoreDataFetcherDelegate::kExternalInstallDefaultButtonKey[] =
     "external_install_default_button";
-const char WebstoreDataFetcherDelegate::kInlineInstallNotSupportedKey[] =
-    "inline_install_not_supported";
 const char WebstoreDataFetcherDelegate::kLocalizedDescriptionKey[] =
     "localized_description";
 const char WebstoreDataFetcherDelegate::kLocalizedNameKey[] = "localized_name";
diff --git a/chrome/browser/extensions/webstore_data_fetcher_delegate.h b/chrome/browser/extensions/webstore_data_fetcher_delegate.h
index a541e59..bef9178 100644
--- a/chrome/browser/extensions/webstore_data_fetcher_delegate.h
+++ b/chrome/browser/extensions/webstore_data_fetcher_delegate.h
@@ -33,7 +33,6 @@
   static const char kIconUrlKey[];
   static const char kIdKey[];
   static const char kExternalInstallDefaultButtonKey[];
-  static const char kInlineInstallNotSupportedKey[];
   static const char kLocalizedDescriptionKey[];
   static const char kLocalizedNameKey[];
   static const char kManifestKey[];
diff --git a/chrome/browser/extensions/webstore_inline_installer.cc b/chrome/browser/extensions/webstore_inline_installer.cc
deleted file mode 100644
index b10f636..0000000
--- a/chrome/browser/extensions/webstore_inline_installer.cc
+++ /dev/null
@@ -1,318 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/extensions/webstore_inline_installer.h"
-
-#include <utility>
-
-#include "base/json/json_writer.h"
-#include "base/strings/stringprintf.h"
-#include "base/values.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/extensions/webstore_data_fetcher.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h"
-#include "chrome/browser/safe_browsing/safe_browsing_service.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
-#include "chrome/common/pref_names.h"
-#include "components/prefs/pref_service.h"
-#include "components/safe_browsing/proto/csd.pb.h"
-#include "content/public/browser/navigation_entry.h"
-#include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/web_contents.h"
-
-using content::WebContents;
-using safe_browsing::SafeBrowsingNavigationObserverManager;
-using safe_browsing::ReferrerChain;
-
-namespace {
-
-// The number of user gestures to trace back for CWS pings.
-const int kExtensionReferrerUserGestureLimit = 2;
-}
-
-namespace extensions {
-
-const char kInvalidWebstoreResponseError[] =
-    "Invalid Chrome Web Store response.";
-const char kNoVerifiedSitesError[] =
-    "Inline installs can only be initiated for Chrome Web Store items that "
-    "have one or more verified sites.";
-const char kNotFromVerifiedSitesError[] =
-    "Installs can only be initiated by one of the Chrome Web Store item's "
-    "verified sites.";
-const char kInlineInstallSupportedError[] =
-    "Inline installation is not supported for this item. The user will be "
-    "redirected to the Chrome Web Store.";
-const char kInitiatedFromPopupError[] =
-    "Inline installs can not be initiated from pop-up windows.";
-const char kInitiatedFromFullscreenError[] =
-    "Inline installs can not be initiated from fullscreen.";
-
-WebstoreInlineInstaller::WebstoreInlineInstaller(
-    content::WebContents* web_contents,
-    content::RenderFrameHost* host,
-    const std::string& webstore_item_id,
-    const GURL& requestor_url,
-    const Callback& callback)
-    : WebstoreStandaloneInstaller(
-          webstore_item_id,
-          Profile::FromBrowserContext(web_contents->GetBrowserContext()),
-          callback),
-      content::WebContentsObserver(web_contents),
-      host_(host),
-      requestor_url_(requestor_url) {}
-
-WebstoreInlineInstaller::~WebstoreInlineInstaller() {}
-
-// static
-bool WebstoreInlineInstaller::IsRequestorPermitted(
-    const base::DictionaryValue& webstore_data,
-    const GURL& requestor_url,
-    std::string* error) {
-  // Ensure that there is at least one verified site present.
-  const bool data_has_single_site = webstore_data.HasKey(kVerifiedSiteKey);
-  const bool data_has_site_list = webstore_data.HasKey(kVerifiedSitesKey);
-  if (!data_has_single_site && !data_has_site_list) {
-    *error = kNoVerifiedSitesError;
-    return false;
-  }
-  bool requestor_is_ok = false;
-  // Handle the deprecated single-site case.
-  if (!data_has_site_list) {
-    std::string verified_site;
-    if (!webstore_data.GetString(kVerifiedSiteKey, &verified_site)) {
-      *error = kInvalidWebstoreResponseError;
-      return false;
-    }
-    requestor_is_ok = IsRequestorURLInVerifiedSite(requestor_url,
-                                                   verified_site);
-  } else {
-    const base::ListValue* verified_sites = NULL;
-    if (!webstore_data.GetList(kVerifiedSitesKey, &verified_sites)) {
-      *error = kInvalidWebstoreResponseError;
-      return false;
-    }
-    for (auto it = verified_sites->begin();
-         it != verified_sites->end() && !requestor_is_ok; ++it) {
-      std::string verified_site;
-      if (!it->GetAsString(&verified_site)) {
-        *error = kInvalidWebstoreResponseError;
-        return false;
-      }
-      if (IsRequestorURLInVerifiedSite(requestor_url, verified_site)) {
-        requestor_is_ok = true;
-      }
-    }
-  }
-  if (!requestor_is_ok) {
-    *error = kNotFromVerifiedSitesError;
-    return false;
-  }
-  *error = "";
-  return true;
-}
-
-bool WebstoreInlineInstaller::SafeBrowsingNavigationEventsEnabled() const {
-  return SafeBrowsingNavigationObserverManager::IsEnabledAndReady(profile());
-}
-
-std::string WebstoreInlineInstaller::GetPostData() {
-  // web_contents() might return null during tab destruction. This object would
-  // also be destroyed shortly thereafter but check to be on the safe side.
-  if (!web_contents())
-    return std::string();
-
-  // Report extra data only when SafeBrowsing is enabled and SB navigation
-  // observer is enabled for the current profile.
-  if (!profile()->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled) ||
-      !SafeBrowsingNavigationEventsEnabled()) {
-    return std::string();
-  }
-
-  scoped_refptr<SafeBrowsingNavigationObserverManager>
-      navigation_observer_manager = g_browser_process->safe_browsing_service()
-                                        ->navigation_observer_manager();
-
-  ReferrerChain referrer_chain;
-  SafeBrowsingNavigationObserverManager::AttributionResult result =
-      navigation_observer_manager->IdentifyReferrerChainByWebContents(
-          web_contents(), kExtensionReferrerUserGestureLimit, &referrer_chain);
-
-  // If the referrer chain is incomplete we'll append most recent navigations
-  // to referrer chain for diagnose purpose. This only happens if user is not
-  // in incognito mode and has opted into extended reporting to Scout reporting.
-  int recent_navigations_to_collect =
-      SafeBrowsingNavigationObserverManager::CountOfRecentNavigationsToAppend(
-          *profile(), result);
-  navigation_observer_manager->AppendRecentNavigations(
-      recent_navigations_to_collect, &referrer_chain);
-  safe_browsing::ExtensionWebStoreInstallRequest request;
-  request.mutable_referrer_chain()->Swap(&referrer_chain);
-  request.mutable_referrer_chain_options()->set_recent_navigations_to_collect(
-      recent_navigations_to_collect);
-
-  return request.SerializeAsString();
-}
-
-bool WebstoreInlineInstaller::CheckRequestorAlive() const {
-  // The frame or tab may have gone away - cancel installation in that case.
-  return host_ != nullptr && web_contents() != nullptr &&
-         chrome::FindBrowserWithWebContents(web_contents()) != nullptr;
-}
-
-const GURL& WebstoreInlineInstaller::GetRequestorURL() const {
-  return requestor_url_;
-}
-
-std::unique_ptr<ExtensionInstallPrompt::Prompt>
-WebstoreInlineInstaller::CreateInstallPrompt() const {
-  std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt(
-      new ExtensionInstallPrompt::Prompt(
-          ExtensionInstallPrompt::INLINE_INSTALL_PROMPT));
-
-  // crbug.com/260742: Don't display the user count if it's zero. The reason
-  // it's zero is very often that the number isn't actually being counted
-  // (intentionally), which means that it's unlikely to be correct.
-  prompt->SetWebstoreData(localized_user_count(),
-                          show_user_count(),
-                          average_rating(),
-                          rating_count());
-  return prompt;
-}
-
-bool WebstoreInlineInstaller::ShouldShowPostInstallUI() const {
-  return true;
-}
-
-bool WebstoreInlineInstaller::ShouldShowAppInstalledBubble() const {
-  return true;
-}
-
-WebContents* WebstoreInlineInstaller::GetWebContents() const {
-  return web_contents();
-}
-
-bool WebstoreInlineInstaller::CheckInlineInstallPermitted(
-    const base::DictionaryValue& webstore_data,
-    std::string* error) const {
-  Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
-  DCHECK(browser);
-  if (browser->is_type_popup()) {
-    *error = kInitiatedFromPopupError;
-    return false;
-  }
-  FullscreenController* controller =
-      browser->exclusive_access_manager()->fullscreen_controller();
-  if (controller->IsTabFullscreen()) {
-    *error = kInitiatedFromFullscreenError;
-    return false;
-  }
-  // The store may not support inline installs for this item, in which case
-  // we open the store-provided redirect URL in a new tab and abort the
-  // installation process.
-  bool inline_install_not_supported = false;
-  if (webstore_data.HasKey(kInlineInstallNotSupportedKey)
-      && !webstore_data.GetBoolean(kInlineInstallNotSupportedKey,
-                                    &inline_install_not_supported)) {
-    *error = kInvalidWebstoreResponseError;
-    return false;
-  }
-  if (inline_install_not_supported) {
-    std::string redirect_url;
-    if (!webstore_data.GetString(kRedirectUrlKey, &redirect_url)) {
-      *error = kInvalidWebstoreResponseError;
-      return false;
-    }
-    web_contents()->OpenURL(content::OpenURLParams(
-        GURL(redirect_url),
-        content::Referrer::SanitizeForRequest(
-            GURL(redirect_url),
-            content::Referrer(web_contents()->GetURL(),
-                              blink::kWebReferrerPolicyDefault)),
-        WindowOpenDisposition::NEW_FOREGROUND_TAB,
-        ui::PAGE_TRANSITION_AUTO_BOOKMARK, false));
-    *error = kInlineInstallSupportedError;
-    return false;
-  }
-  *error = "";
-  return true;
-}
-
-bool WebstoreInlineInstaller::CheckRequestorPermitted(
-    const base::DictionaryValue& webstore_data,
-    std::string* error) const {
-  return IsRequestorPermitted(webstore_data, requestor_url_, error);
-}
-
-//
-// Private implementation.
-//
-
-void WebstoreInlineInstaller::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
-  if (navigation_handle->HasCommitted() &&
-      !navigation_handle->IsSameDocument() &&
-      (navigation_handle->GetRenderFrameHost() == host_ ||
-       navigation_handle->IsInMainFrame())) {
-    host_ = nullptr;
-  }
-}
-
-void WebstoreInlineInstaller::WebContentsDestroyed() {
-  AbortInstall();
-}
-
-// static
-bool WebstoreInlineInstaller::IsRequestorURLInVerifiedSite(
-    const GURL& requestor_url,
-    const std::string& verified_site) {
-  // Turn the verified site into a URL that can be parsed by URLPattern.
-  // |verified_site| must follow the format:
-  //
-  // [scheme://]host[:port][/path/specifier]
-  //
-  // If scheme is omitted, URLPattern will match against either an
-  // HTTP or HTTPS requestor. If scheme is specified, it must be either HTTP
-  // or HTTPS, and URLPattern will only match the scheme specified.
-  GURL verified_site_url(verified_site);
-  int valid_schemes = URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS;
-  if (!verified_site_url.is_valid() || !verified_site_url.IsStandard())
-    // If no scheme is specified, GURL will fail to parse the string correctly.
-    // It will either determine that the URL is invalid, or parse a
-    // host:port/path as scheme:host/path.
-    verified_site_url = GURL("http://" + verified_site);
-  else if (verified_site_url.SchemeIs("http"))
-    valid_schemes = URLPattern::SCHEME_HTTP;
-  else if (verified_site_url.SchemeIs("https"))
-    valid_schemes = URLPattern::SCHEME_HTTPS;
-  else
-    return false;
-
-  std::string port_spec =
-      verified_site_url.has_port() ? ":" + verified_site_url.port() : "";
-  std::string path_spec = verified_site_url.path() + "*";
-  std::string verified_site_pattern_spec =
-      base::StringPrintf(
-          "%s://*.%s%s%s",
-          verified_site_url.scheme().c_str(),
-          verified_site_url.host().c_str(),
-          port_spec.c_str(),
-          path_spec.c_str());
-
-  URLPattern verified_site_pattern(valid_schemes);
-  URLPattern::ParseResult parse_result =
-      verified_site_pattern.Parse(verified_site_pattern_spec);
-  if (parse_result != URLPattern::ParseResult::kSuccess) {
-    DLOG(WARNING) << "Failed to parse '" << verified_site_pattern_spec
-                  << "': " << URLPattern::GetParseResultString(parse_result);
-    return false;
-  }
-  verified_site_pattern.SetScheme("*");
-
-  return verified_site_pattern.MatchesURL(requestor_url);
-}
-
-}  // namespace extensions
diff --git a/chrome/browser/extensions/webstore_inline_installer.h b/chrome/browser/extensions/webstore_inline_installer.h
deleted file mode 100644
index 5b99811..0000000
--- a/chrome/browser/extensions/webstore_inline_installer.h
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_EXTENSIONS_WEBSTORE_INLINE_INSTALLER_H_
-#define CHROME_BROWSER_EXTENSIONS_WEBSTORE_INLINE_INSTALLER_H_
-
-#include <memory>
-#include <string>
-
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "chrome/browser/extensions/webstore_standalone_installer.h"
-#include "content/public/browser/web_contents_observer.h"
-
-namespace content {
-class WebContents;
-}
-
-namespace extensions {
-
-// Manages inline installs requested by a page: downloads and parses metadata
-// from the webstore, shows the install UI, starts the download once the user
-// confirms, optionally transfers the user to the store if the "View details"
-// link is clicked in the UI, shows the "App installed" bubble and the
-// post-install UI after successful installation.
-//
-// Clients will be notified of success or failure via the |callback| argument
-// passed into the constructor.
-class WebstoreInlineInstaller : public WebstoreStandaloneInstaller,
-                                public content::WebContentsObserver {
- public:
-  typedef WebstoreStandaloneInstaller::Callback Callback;
-
-  WebstoreInlineInstaller(content::WebContents* web_contents,
-                          content::RenderFrameHost* host,
-                          const std::string& webstore_item_id,
-                          const GURL& requestor_url,
-                          const Callback& callback);
-
-  // Returns true if given |requestor_url| is a verified site according to the
-  // given |webstore_data|.
-  static bool IsRequestorPermitted(const base::DictionaryValue& webstore_data,
-                                   const GURL& requestor_url,
-                                   std::string* error);
-
- protected:
-  friend class base::RefCountedThreadSafe<WebstoreInlineInstaller>;
-
-  ~WebstoreInlineInstaller() override;
-
-  // Returns whether to use the new navigation event tracker.
-  virtual bool SafeBrowsingNavigationEventsEnabled() const;
-
-  // Implementations WebstoreStandaloneInstaller Template Method's hooks.
-  std::string GetPostData() override;
-  bool CheckRequestorAlive() const override;
-  const GURL& GetRequestorURL() const override;
-  bool ShouldShowPostInstallUI() const override;
-  bool ShouldShowAppInstalledBubble() const override;
-  content::WebContents* GetWebContents() const override;
-  std::unique_ptr<ExtensionInstallPrompt::Prompt> CreateInstallPrompt()
-      const override;
-  bool CheckInlineInstallPermitted(const base::DictionaryValue& webstore_data,
-                                   std::string* error) const override;
-  bool CheckRequestorPermitted(const base::DictionaryValue& webstore_data,
-                               std::string* error) const override;
-
- private:
-  // content::WebContentsObserver interface implementation.
-  void DidFinishNavigation(
-      content::NavigationHandle* navigation_handle) override;
-  void WebContentsDestroyed() override;
-
-  // Checks whether the install is initiated by a page in a verified site
-  // (which is at least a domain, but can also have a port or a path).
-  static bool IsRequestorURLInVerifiedSite(const GURL& requestor_url,
-                                           const std::string& verified_site);
-
-  // This corresponds to the frame that initiated the install request.
-  content::RenderFrameHost* host_;
-  GURL requestor_url_;
-
-  DISALLOW_IMPLICIT_CONSTRUCTORS(WebstoreInlineInstaller);
-};
-
-}  // namespace extensions
-
-#endif  // CHROME_BROWSER_EXTENSIONS_WEBSTORE_INLINE_INSTALLER_H_
diff --git a/chrome/browser/extensions/webstore_inline_installer_browsertest.cc b/chrome/browser/extensions/webstore_inline_installer_browsertest.cc
deleted file mode 100644
index e2a7da6c..0000000
--- a/chrome/browser/extensions/webstore_inline_installer_browsertest.cc
+++ /dev/null
@@ -1,584 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/extensions/webstore_inline_installer.h"
-
-#include "base/json/json_reader.h"
-#include "base/macros.h"
-#include "base/memory/ptr_util.h"
-#include "base/run_loop.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/values.h"
-#include "build/build_config.h"
-#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/extensions/extension_install_prompt.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/tab_helper.h"
-#include "chrome/browser/extensions/webstore_inline_installer_factory.h"
-#include "chrome/browser/extensions/webstore_installer_test.h"
-#include "chrome/browser/extensions/webstore_standalone_installer.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/common/pref_names.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "components/content_settings/core/browser/host_content_settings_map.h"
-#include "components/prefs/pref_service.h"
-#include "components/safe_browsing/features.h"
-#include "components/safe_browsing/proto/csd.pb.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/browser_test_utils.h"
-#include "extensions/browser/extension_registry.h"
-#include "extensions/browser/extension_system.h"
-#include "extensions/common/permissions/permission_set.h"
-#include "net/dns/mock_host_resolver.h"
-#include "net/test/embedded_test_server/http_request.h"
-#include "url/gurl.h"
-
-using content::WebContents;
-
-namespace extensions {
-
-namespace {
-
-const char kWebstoreDomain[] = "cws.com";
-const char kAppDomain[] = "app.com";
-const char kNonAppDomain[] = "nonapp.com";
-const char kTestExtensionId[] = "ecglahbcnmdpdciemllbhojghbkagdje";
-const char kTestDataPath[] = "extensions/api_test/webstore_inline_install";
-const char kCrxFilename[] = "extension.crx";
-
-const char kRedirect1Domain[] = "redirect1.com";
-const char kRedirect2Domain[] = "redirect2.com";
-
-// A struct for letting us store the actual parameters that were passed to
-// the install callback.
-struct InstallResult {
-  bool success = false;
-  std::string error;
-  webstore_install::Result result = webstore_install::RESULT_LAST;
-};
-
-}  // namespace
-
-class WebstoreInlineInstallerTest : public WebstoreInstallerTest {
- public:
-  WebstoreInlineInstallerTest()
-      : WebstoreInstallerTest(
-            kWebstoreDomain,
-            kTestDataPath,
-            kCrxFilename,
-            kAppDomain,
-            kNonAppDomain) {}
-};
-
-class ProgrammableInstallPrompt : public ExtensionInstallPrompt {
- public:
-  explicit ProgrammableInstallPrompt(WebContents* contents)
-      : ExtensionInstallPrompt(contents)
-  {}
-
-  ~ProgrammableInstallPrompt() override { g_done_callback = nullptr; }
-
-  void ShowDialog(
-      const ExtensionInstallPrompt::DoneCallback& done_callback,
-      const Extension* extension,
-      const SkBitmap* icon,
-      std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt,
-      std::unique_ptr<const extensions::PermissionSet> custom_permissions,
-      const ShowDialogCallback& show_dialog_callback) override {
-    done_callback_ = done_callback;
-    g_done_callback = &done_callback_;
-  }
-
-  static bool Ready() { return g_done_callback != nullptr; }
-
-  static void Accept() {
-    g_done_callback->Run(ExtensionInstallPrompt::Result::ACCEPTED);
-  }
-
-  static void Reject() {
-    g_done_callback->Run(ExtensionInstallPrompt::Result::USER_CANCELED);
-  }
-
- private:
-  static ExtensionInstallPrompt::DoneCallback* g_done_callback;
-
-  ExtensionInstallPrompt::DoneCallback done_callback_;
-
-  DISALLOW_COPY_AND_ASSIGN(ProgrammableInstallPrompt);
-};
-
-ExtensionInstallPrompt::DoneCallback*
-    ProgrammableInstallPrompt::g_done_callback = nullptr;
-
-// Fake inline installer which creates a programmable prompt in place of
-// the normal dialog UI.
-class WebstoreInlineInstallerForTest : public WebstoreInlineInstaller {
- public:
-  WebstoreInlineInstallerForTest(WebContents* contents,
-                                 content::RenderFrameHost* host,
-                                 const std::string& extension_id,
-                                 const GURL& requestor_url,
-                                 const Callback& callback,
-                                 bool enable_safebrowsing_redirects)
-      : WebstoreInlineInstaller(
-            contents,
-            host,
-            kTestExtensionId,
-            requestor_url,
-            base::Bind(&WebstoreInlineInstallerForTest::InstallCallback,
-                       base::Unretained(this))),
-        install_result_target_(nullptr),
-        enable_safebrowsing_redirects_(enable_safebrowsing_redirects),
-        programmable_prompt_(nullptr) {}
-
-  std::unique_ptr<ExtensionInstallPrompt> CreateInstallUI() override {
-    programmable_prompt_ = new ProgrammableInstallPrompt(web_contents());
-    return base::WrapUnique(programmable_prompt_);
-  }
-
-  // Added here to make it public so that test cases can call it below.
-  bool CheckRequestorAlive() const override {
-    return WebstoreInlineInstaller::CheckRequestorAlive();
-  }
-
-  bool SafeBrowsingNavigationEventsEnabled() const override {
-    return enable_safebrowsing_redirects_;
-  }
-
-  // Tests that care about the actual arguments to the install callback can use
-  // this to receive a copy in |install_result_target|.
-  void set_install_result_target(
-      std::unique_ptr<InstallResult>* install_result_target) {
-    install_result_target_ = install_result_target;
-  }
-
- private:
-  ~WebstoreInlineInstallerForTest() override {}
-
-  friend class base::RefCountedThreadSafe<WebstoreStandaloneInstaller>;
-
-  void InstallCallback(bool success,
-                       const std::string& error,
-                       webstore_install::Result result) {
-    if (install_result_target_) {
-      install_result_target_->reset(new InstallResult);
-      (*install_result_target_)->success = success;
-      (*install_result_target_)->error = error;
-      (*install_result_target_)->result = result;
-    }
-  }
-
-  // This can be set by tests that want to get the actual install callback
-  // arguments.
-  std::unique_ptr<InstallResult>* install_result_target_;
-
-  // This can be set by tests that want to use the new SafeBrowsing redirect
-  // tracker.
-  bool enable_safebrowsing_redirects_;
-
-  ProgrammableInstallPrompt* programmable_prompt_;
-};
-
-class WebstoreInlineInstallerForTestFactory :
-    public WebstoreInlineInstallerFactory {
- public:
-  WebstoreInlineInstallerForTestFactory()
-      : last_installer_(nullptr), enable_safebrowsing_redirects_(false) {}
-  explicit WebstoreInlineInstallerForTestFactory(
-      bool enable_safebrowsing_redirects)
-      : last_installer_(nullptr),
-        enable_safebrowsing_redirects_(enable_safebrowsing_redirects) {}
-  ~WebstoreInlineInstallerForTestFactory() override {}
-
-  WebstoreInlineInstallerForTest* last_installer() { return last_installer_; }
-
-  WebstoreInlineInstaller* CreateInstaller(
-      WebContents* contents,
-      content::RenderFrameHost* host,
-      const std::string& webstore_item_id,
-      const GURL& requestor_url,
-      const WebstoreStandaloneInstaller::Callback& callback) override {
-    last_installer_ = new WebstoreInlineInstallerForTest(
-        contents, host, webstore_item_id, requestor_url, callback,
-        enable_safebrowsing_redirects_);
-    return last_installer_;
-  }
-
- private:
-  // The last installer that was created.
-  WebstoreInlineInstallerForTest* last_installer_;
-
-  bool enable_safebrowsing_redirects_;
-};
-
-IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest,
-                       CloseTabBeforeInstallConfirmation) {
-  GURL install_url = GenerateTestServerUrl(kAppDomain, "install.html");
-  ui_test_utils::NavigateToURL(browser(), install_url);
-  WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  TabHelper* tab_helper = TabHelper::FromWebContents(web_contents);
-  tab_helper->SetWebstoreInlineInstallerFactoryForTests(
-      new WebstoreInlineInstallerForTestFactory());
-  RunTestAsync("runTest");
-  while (!ProgrammableInstallPrompt::Ready())
-    base::RunLoop().RunUntilIdle();
-  web_contents->Close();
-  ProgrammableInstallPrompt::Accept();
-}
-
-IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest,
-                       NavigateBeforeInstallConfirmation) {
-  GURL install_url = GenerateTestServerUrl(kAppDomain, "install.html");
-  ui_test_utils::NavigateToURL(browser(), install_url);
-  WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  TabHelper* tab_helper = TabHelper::FromWebContents(web_contents);
-  WebstoreInlineInstallerForTestFactory* factory =
-      new WebstoreInlineInstallerForTestFactory();
-  tab_helper->SetWebstoreInlineInstallerFactoryForTests(factory);
-  RunTestAsync("runTest");
-  while (!ProgrammableInstallPrompt::Ready())
-    base::RunLoop().RunUntilIdle();
-  GURL new_url = GenerateTestServerUrl(kNonAppDomain, "empty.html");
-  web_contents->GetController().LoadURL(
-      new_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
-  EXPECT_TRUE(content::WaitForLoadStop(web_contents));
-  ASSERT_NE(factory->last_installer(), nullptr);
-  EXPECT_NE(factory->last_installer()->web_contents(), nullptr);
-  EXPECT_FALSE(factory->last_installer()->CheckRequestorAlive());
-
-  // Right now the way we handle navigations away from the frame that began the
-  // inline install is to just declare the requestor to be dead, but not to
-  // kill the prompt (that would be a better UX, but more complicated to
-  // implement). If we ever do change things to kill the prompt in this case,
-  // the following code can be removed (it verifies that clicking ok on the
-  // dialog does not result in an install).
-  std::unique_ptr<InstallResult> install_result;
-  factory->last_installer()->set_install_result_target(&install_result);
-  ASSERT_TRUE(ProgrammableInstallPrompt::Ready());
-  ProgrammableInstallPrompt::Accept();
-  ASSERT_NE(install_result.get(), nullptr);
-  EXPECT_EQ(install_result->success, false);
-  EXPECT_EQ(install_result->result, webstore_install::ABORTED);
-}
-
-// Flaky: https://crbug.com/537526.
-IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest,
-                       DISABLED_ShouldBlockInlineInstallFromPopupWindow) {
-  GURL install_url =
-      GenerateTestServerUrl(kAppDomain, "install_from_popup.html");
-  // Disable popup blocking for the test url.
-  HostContentSettingsMapFactory::GetForProfile(browser()->profile())
-      ->SetContentSettingDefaultScope(install_url, GURL(),
-                                      CONTENT_SETTINGS_TYPE_POPUPS,
-                                      std::string(), CONTENT_SETTING_ALLOW);
-  ui_test_utils::NavigateToURL(browser(), install_url);
-  // The test page opens a popup which is a new |browser| window.
-  Browser* popup_browser =
-      chrome::FindLastActiveWithProfile(browser()->profile());
-  WebContents* popup_contents =
-      popup_browser->tab_strip_model()->GetActiveWebContents();
-  EXPECT_EQ(base::ASCIIToUTF16("POPUP"), popup_contents->GetTitle());
-  RunTest(popup_contents, "runTest");
-}
-
-// Allow inline install while in browser fullscreen mode. Browser fullscreen
-// is initiated by the user using F11 (windows), ctrl+cmd+F (mac) or the green
-// maximize window button on mac. This will be allowed since it cannot be
-// initiated by an API and because of the nuance with mac windows.
-IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest,
-                       AllowInlineInstallFromFullscreenForBrowser) {
-  const GURL install_url = GenerateTestServerUrl(kAppDomain, "install.html");
-  ui_test_utils::NavigateToURL(browser(), install_url);
-  AutoAcceptInstall();
-
-  // Enter browser fullscreen mode.
-  FullscreenController* controller =
-      browser()->exclusive_access_manager()->fullscreen_controller();
-  controller->ToggleBrowserFullscreenMode();
-
-  RunTest("runTest");
-
-  // Ensure extension is installed.
-  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
-  EXPECT_TRUE(
-      registry->GenerateInstalledExtensionsSet()->Contains(kTestExtensionId));
-}
-
-// Prevent inline install while in tab fullscreen mode. Tab fullscreen is
-// initiated using the browser API.
-IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest,
-                       BlockInlineInstallFromFullscreenForTab) {
-  const GURL install_url =
-      GenerateTestServerUrl(kAppDomain, "install_from_fullscreen.html");
-  ui_test_utils::NavigateToURL(browser(), install_url);
-  AutoAcceptInstall();
-  WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  FullscreenController* controller =
-      browser()->exclusive_access_manager()->fullscreen_controller();
-
-  // Enter tab fullscreen mode.
-  controller->EnterFullscreenModeForTab(web_contents, install_url);
-
-  RunTest("runTest");
-
-  // Ensure extension is not installed.
-  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
-  EXPECT_FALSE(
-      registry->GenerateInstalledExtensionsSet()->Contains(kTestExtensionId));
-}
-
-// Flaky on Linux ASan LSan (https://crbug.com/889804)
-#if defined(OS_LINUX) && defined(ADDRESS_SANITIZER)
-#define MAYBE_ReinstallDisabledExtension DISABLED_ReinstallDisabledExtension
-#else
-#define MAYBE_ReinstallDisabledExtension ReinstallDisabledExtension
-#endif
-// Ensure that inline-installing a disabled extension simply re-enables it.
-IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest,
-                       MAYBE_ReinstallDisabledExtension) {
-  // Install an extension via inline install, and confirm it is successful.
-  AutoAcceptInstall();
-  ui_test_utils::NavigateToURL(
-      browser(), GenerateTestServerUrl(kAppDomain, "install.html"));
-  RunTest("runTest");
-  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
-  ASSERT_TRUE(registry->enabled_extensions().GetByID(kTestExtensionId));
-
-  // Disable the extension.
-  ExtensionService* extension_service =
-      ExtensionSystem::Get(browser()->profile())->extension_service();
-  extension_service->DisableExtension(kTestExtensionId,
-                                      disable_reason::DISABLE_USER_ACTION);
-  EXPECT_TRUE(registry->disabled_extensions().GetByID(kTestExtensionId));
-
-  // Revisit the inline install site and reinstall the extension. It should
-  // simply be re-enabled, rather than try to install again.
-  ui_test_utils::NavigateToURL(
-      browser(), GenerateTestServerUrl(kAppDomain, "install.html"));
-  RunTest("runTest");
-  EXPECT_TRUE(registry->enabled_extensions().GetByID(kTestExtensionId));
-  // Since it was disabled by user action, the prompt should have just been the
-  // inline install prompt.
-  EXPECT_EQ(ExtensionInstallPrompt::INLINE_INSTALL_PROMPT,
-            ExtensionInstallPrompt::g_last_prompt_type_for_tests);
-
-  // Disable the extension due to a permissions increase.
-  extension_service->DisableExtension(
-      kTestExtensionId, disable_reason::DISABLE_PERMISSIONS_INCREASE);
-  EXPECT_TRUE(registry->disabled_extensions().GetByID(kTestExtensionId));
-  ui_test_utils::NavigateToURL(
-      browser(), GenerateTestServerUrl(kAppDomain, "install.html"));
-  RunTest("runTest");
-  EXPECT_TRUE(registry->enabled_extensions().GetByID(kTestExtensionId));
-  // The displayed prompt should be for the permissions increase, versus a
-  // normal inline install prompt.
-  EXPECT_EQ(ExtensionInstallPrompt::RE_ENABLE_PROMPT,
-            ExtensionInstallPrompt::g_last_prompt_type_for_tests);
-
-  ExtensionInstallPrompt::g_last_prompt_type_for_tests =
-      ExtensionInstallPrompt::UNSET_PROMPT_TYPE;
-  ui_test_utils::NavigateToURL(
-      browser(), GenerateTestServerUrl(kAppDomain, "install.html"));
-  RunTest("runTest");
-  // If the extension was already enabled, we should still display an inline
-  // install prompt (until we come up with something better).
-  EXPECT_EQ(ExtensionInstallPrompt::INLINE_INSTALL_PROMPT,
-            ExtensionInstallPrompt::g_last_prompt_type_for_tests);
-}
-
-// Test calling chrome.webstore.install() twice without waiting for the first to
-// finish.
-IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest, DoubleInlineInstallTest) {
-  ui_test_utils::NavigateToURL(
-      browser(), GenerateTestServerUrl(kAppDomain, "double_install.html"));
-  RunTest("runTest");
-}
-
-class WebstoreInlineInstallerRedirectTest
-    : public WebstoreInlineInstallerTest,
-      public ::testing::WithParamInterface<bool> {
- public:
-  WebstoreInlineInstallerRedirectTest() : cws_request_received_(false) {}
-  ~WebstoreInlineInstallerRedirectTest() override {}
-
-  void SetUpOnMainThread() override {
-    WebstoreInstallerTest::SetUpOnMainThread();
-    host_resolver()->AddRule(kRedirect1Domain, "127.0.0.1");
-    host_resolver()->AddRule(kRedirect2Domain, "127.0.0.1");
-  }
-
-  void ProcessServerRequest(
-      const net::test_server::HttpRequest& request) override {
-    cws_request_received_ = true;
-    if (request.content.empty())
-      return;
-
-    cws_request_proto_ =
-        std::make_unique<safe_browsing::ExtensionWebStoreInstallRequest>();
-    if (!cws_request_proto_->ParseFromString(request.content))
-      cws_request_proto_.reset();
-  }
-
-  bool cws_request_received_;
-  std::unique_ptr<safe_browsing::ExtensionWebStoreInstallRequest>
-      cws_request_proto_;
-};
-
-// Test that an install from a page arrived at via redirects includes the
-// redirect information in the webstore request.
-IN_PROC_BROWSER_TEST_P(WebstoreInlineInstallerRedirectTest,
-                       IncludesRedirectProtoData) {
-  const bool using_safe_browsing_tracker = GetParam();
-  WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  TabHelper* tab_helper = TabHelper::FromWebContents(web_contents);
-  WebstoreInlineInstallerForTestFactory* factory =
-      new WebstoreInlineInstallerForTestFactory(using_safe_browsing_tracker);
-  tab_helper->SetWebstoreInlineInstallerFactoryForTests(factory);
-
-  net::HostPortPair host_port = embedded_test_server()->host_port_pair();
-
-  std::string final_url =
-      GenerateTestServerUrl(kAppDomain, "install.html").spec();
-  std::string redirect_url =
-      base::StringPrintf("http://%s:%d/server-redirect?%s", kRedirect2Domain,
-                         host_port.port(), final_url.c_str());
-  std::string install_url =
-      base::StringPrintf("http://%s:%d/server-redirect?%s", kRedirect1Domain,
-                         host_port.port(), redirect_url.c_str());
-  AutoAcceptInstall();
-  ui_test_utils::NavigateToURL(browser(), GURL(install_url));
-
-  RunTestAsync("runTest");
-  while (!ProgrammableInstallPrompt::Ready())
-    base::RunLoop().RunUntilIdle();
-  web_contents->Close();
-
-  EXPECT_TRUE(cws_request_received_);
-  if (!using_safe_browsing_tracker) {
-    ASSERT_EQ(nullptr, cws_request_proto_);
-    return;
-  }
-  ASSERT_NE(nullptr, cws_request_proto_);
-  ASSERT_EQ(1, cws_request_proto_->referrer_chain_size());
-
-  safe_browsing::ReferrerChainEntry referrer_entry =
-      cws_request_proto_->referrer_chain(0);
-
-  // Check that the expected domains are in the redirect list.
-  const std::set<std::string> expected_redirect_domains = {
-      kRedirect1Domain, kRedirect2Domain, kAppDomain};
-
-  EXPECT_EQ(final_url, referrer_entry.url());
-  EXPECT_EQ(safe_browsing::ReferrerChainEntry::CLIENT_REDIRECT,
-            referrer_entry.type());
-  EXPECT_EQ(3, referrer_entry.server_redirect_chain_size());
-  EXPECT_EQ(install_url, referrer_entry.server_redirect_chain(0).url());
-  EXPECT_EQ(redirect_url, referrer_entry.server_redirect_chain(1).url());
-  EXPECT_EQ(final_url, referrer_entry.server_redirect_chain(2).url());
-  EXPECT_TRUE(cws_request_proto_->referrer_chain_options()
-                  .has_recent_navigations_to_collect());
-}
-
-// Test that an install from a page arrived at via redirects does not include
-// redirect information when SafeBrowsing is disabled.
-IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerRedirectTest,
-                       NoRedirectDataWhenSafeBrowsingDisabled) {
-  PrefService* pref_service = browser()->profile()->GetPrefs();
-  EXPECT_TRUE(pref_service->GetBoolean(prefs::kSafeBrowsingEnabled));
-
-  // Disable SafeBrowsing.
-  pref_service->SetBoolean(prefs::kSafeBrowsingEnabled, false);
-
-  // Hand craft a url that will cause the test server to issue redirects.
-  const std::vector<std::string> redirects = {kRedirect1Domain,
-                                              kRedirect2Domain};
-  net::HostPortPair host_port = embedded_test_server()->host_port_pair();
-  std::string redirect_chain;
-  for (const auto& redirect : redirects) {
-    std::string redirect_url = base::StringPrintf(
-        "http://%s:%d/server-redirect?", redirect.c_str(), host_port.port());
-    redirect_chain += redirect_url;
-  }
-  const GURL install_url =
-      GURL(redirect_chain +
-           GenerateTestServerUrl(kAppDomain, "install.html").spec());
-
-  AutoAcceptInstall();
-  ui_test_utils::NavigateToURL(browser(), install_url);
-  RunTest("runTest");
-
-  EXPECT_TRUE(cws_request_received_);
-  ASSERT_EQ(nullptr, cws_request_proto_);
-}
-
-INSTANTIATE_TEST_CASE_P(NetRedirectTracking,
-                        WebstoreInlineInstallerRedirectTest,
-                        testing::Values(false));
-INSTANTIATE_TEST_CASE_P(SafeBrowsingRedirectTracking,
-                        WebstoreInlineInstallerRedirectTest,
-                        testing::Values(true));
-
-class WebstoreInlineInstallerListenerTest : public WebstoreInlineInstallerTest {
- public:
-  WebstoreInlineInstallerListenerTest() {}
-  ~WebstoreInlineInstallerListenerTest() override {}
-
- protected:
-  void RunTest(const std::string& file_name) {
-    AutoAcceptInstall();
-    ui_test_utils::NavigateToURL(browser(),
-                                 GenerateTestServerUrl(kAppDomain, file_name));
-    WebstoreInstallerTest::RunTest("runTest");
-  }
-};
-
-IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerListenerTest,
-                       InstallStageListenerTest) {
-  RunTest("install_stage_listener.html");
-}
-
-IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerListenerTest,
-                       DownloadProgressListenerTest) {
-  RunTest("download_progress_listener.html");
-}
-
-// Flaky on Linux ASan LSan (https://crbug.com/889804)
-#if defined(OS_LINUX) && defined(ADDRESS_SANITIZER)
-#define MAYBE_BothListenersTest DISABLED_BothListenersTest
-#else
-#define MAYBE_BothListenersTest BothListenersTest
-#endif
-IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerListenerTest,
-                       MAYBE_BothListenersTest) {
-  RunTest("both_listeners.html");
-  // The extension should be installed.
-  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
-  EXPECT_TRUE(registry->enabled_extensions().GetByID(kTestExtensionId));
-
-  // Rinse and repeat: uninstall the extension, open a new tab, and install it
-  // again. Regression test for crbug.com/613949.
-  extension_service()->UninstallExtension(
-      kTestExtensionId, UNINSTALL_REASON_FOR_TESTING, nullptr);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(registry->enabled_extensions().GetByID(kTestExtensionId));
-  int old_tab_index = browser()->tab_strip_model()->active_index();
-  ui_test_utils::NavigateToURLWithDisposition(
-      browser(), GenerateTestServerUrl(kAppDomain, "both_listeners.html"),
-      WindowOpenDisposition::NEW_FOREGROUND_TAB,
-      ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
-  DCHECK_NE(old_tab_index, browser()->tab_strip_model()->active_index());
-  browser()->tab_strip_model()->CloseWebContentsAt(old_tab_index,
-                                                   TabStripModel::CLOSE_NONE);
-  WebstoreInstallerTest::RunTest("runTest");
-  EXPECT_TRUE(registry->enabled_extensions().GetByID(kTestExtensionId));
-}
-
-}  // namespace extensions
diff --git a/chrome/browser/extensions/webstore_inline_installer_factory.cc b/chrome/browser/extensions/webstore_inline_installer_factory.cc
deleted file mode 100644
index 39e0b83a..0000000
--- a/chrome/browser/extensions/webstore_inline_installer_factory.cc
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/extensions/webstore_inline_installer_factory.h"
-
-#include <memory>
-
-#include "chrome/browser/extensions/webstore_inline_installer.h"
-#include "content/public/browser/web_contents.h"
-
-namespace extensions {
-
-WebstoreInlineInstaller* WebstoreInlineInstallerFactory::CreateInstaller(
-    content::WebContents* contents,
-    content::RenderFrameHost* host,
-    const std::string& webstore_item_id,
-    const GURL& requestor_url,
-    const WebstoreStandaloneInstaller::Callback& callback) {
-  return new WebstoreInlineInstaller(contents, host, webstore_item_id,
-                                     requestor_url, callback);
-}
-
-}  // namespace extensions
diff --git a/chrome/browser/extensions/webstore_inline_installer_factory.h b/chrome/browser/extensions/webstore_inline_installer_factory.h
deleted file mode 100644
index 97dcc8f9..0000000
--- a/chrome/browser/extensions/webstore_inline_installer_factory.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_EXTENSIONS_WEBSTORE_INLINE_INSTALLER_FACTORY_H_
-#define CHROME_BROWSER_EXTENSIONS_WEBSTORE_INLINE_INSTALLER_FACTORY_H_
-
-#include <memory>
-#include <string>
-
-#include "chrome/browser/extensions/extension_install_prompt.h"
-#include "chrome/browser/extensions/webstore_standalone_installer.h"
-
-namespace content {
-class WebContents;
-}
-
-class GURL;
-
-namespace extensions {
-
-class WebstoreInlineInstaller;
-
-class WebstoreInlineInstallerFactory {
- public:
-  virtual ~WebstoreInlineInstallerFactory() {}
-
-  // Create a new WebstoreInlineInstallerInstance to be owned by the caller.
-  virtual WebstoreInlineInstaller* CreateInstaller(
-      content::WebContents* contents,
-      content::RenderFrameHost* host,
-      const std::string& webstore_item_id,
-      const GURL& requestor_url,
-      const WebstoreStandaloneInstaller::Callback& callback);
-};
-
-}  // namespace extensions
-
-#endif  // CHROME_BROWSER_EXTENSIONS_WEBSTORE_INLINE_INSTALLER_FACTORY_H_
diff --git a/chrome/browser/extensions/webstore_inline_installer_unittest.cc b/chrome/browser/extensions/webstore_inline_installer_unittest.cc
deleted file mode 100644
index 881528bc..0000000
--- a/chrome/browser/extensions/webstore_inline_installer_unittest.cc
+++ /dev/null
@@ -1,228 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <utility>
-#include <vector>
-
-#include "chrome/browser/extensions/webstore_inline_installer.h"
-#include "chrome/common/extensions/webstore_install_result.h"
-#include "chrome/test/base/chrome_render_view_host_test_harness.h"
-#include "content/public/browser/web_contents.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "url/gurl.h"
-
-namespace extensions {
-
-namespace {
-
-// Wraps WebstoreInlineInstaller to provide access to domain verification
-// methods for testing.
-class TestWebstoreInlineInstaller : public WebstoreInlineInstaller {
- public:
-  explicit TestWebstoreInlineInstaller(content::WebContents* contents,
-                                       const std::string& requestor_url);
-
-  bool TestCheckRequestorPermitted(const base::DictionaryValue& webstore_data) {
-    std::string error;
-    return CheckRequestorPermitted(webstore_data, &error);
-  }
-
- protected:
-  ~TestWebstoreInlineInstaller() override;
-};
-
-void TestInstallerCallback(bool success,
-                           const std::string& error,
-                           webstore_install::Result result) {}
-
-TestWebstoreInlineInstaller::TestWebstoreInlineInstaller(
-    content::WebContents* contents,
-    const std::string& requestor_url)
-    : WebstoreInlineInstaller(contents,
-                              contents->GetMainFrame(),
-                              "",
-                              GURL(requestor_url),
-                              base::Bind(&TestInstallerCallback)) {
-}
-
-TestWebstoreInlineInstaller::~TestWebstoreInlineInstaller() {}
-
-// We inherit from ChromeRenderViewHostTestHarness only for
-// CreateTestWebContents, because we need a mock WebContents to support the
-// underlying WebstoreInlineInstaller in each test case.
-class WebstoreInlineInstallerTest : public ChromeRenderViewHostTestHarness {
- public:
-  // testing::Test
-  void SetUp() override;
-  void TearDown() override;
-
-  bool TestSingleVerifiedSite(const std::string& requestor_url,
-                              const std::string& verified_site);
-
-  bool TestMultipleVerifiedSites(
-      const std::string& requestor_url,
-      const std::vector<std::string>& verified_sites);
-
- protected:
-  std::unique_ptr<content::WebContents> web_contents_;
-};
-
-void WebstoreInlineInstallerTest::SetUp() {
-  ChromeRenderViewHostTestHarness::SetUp();
-  web_contents_ = CreateTestWebContents();
-}
-
-void WebstoreInlineInstallerTest::TearDown() {
-  web_contents_.reset(NULL);
-  ChromeRenderViewHostTestHarness::TearDown();
-}
-
-// Simulates a test against the verified site string from a Webstore item's
-// "verified_site" manifest entry.
-bool WebstoreInlineInstallerTest::TestSingleVerifiedSite(
-    const std::string& requestor_url,
-    const std::string& verified_site) {
-  base::DictionaryValue webstore_data;
-  webstore_data.SetString("verified_site", verified_site);
-
-  scoped_refptr<TestWebstoreInlineInstaller> installer =
-    new TestWebstoreInlineInstaller(web_contents_.get(), requestor_url);
-  return installer->TestCheckRequestorPermitted(webstore_data);
-}
-
-// Simulates a test against a list of verified site strings from a Webstore
-// item's "verified_sites" manifest entry.
-bool WebstoreInlineInstallerTest::TestMultipleVerifiedSites(
-    const std::string& requestor_url,
-    const std::vector<std::string>& verified_sites) {
-  auto sites = std::make_unique<base::ListValue>();
-  for (auto it = verified_sites.begin(); it != verified_sites.end(); ++it) {
-    sites->AppendString(*it);
-  }
-  base::DictionaryValue webstore_data;
-  webstore_data.Set("verified_sites", std::move(sites));
-
-  scoped_refptr<TestWebstoreInlineInstaller> installer =
-    new TestWebstoreInlineInstaller(web_contents_.get(), requestor_url);
-  return installer->TestCheckRequestorPermitted(webstore_data);
-}
-
-}  // namespace
-
-TEST_F(WebstoreInlineInstallerTest, DomainVerification) {
-  // Exact domain match.
-  EXPECT_TRUE(TestSingleVerifiedSite("http://example.com", "example.com"));
-
-  // The HTTPS scheme is allowed.
-  EXPECT_TRUE(TestSingleVerifiedSite("https://example.com", "example.com"));
-
-  // The file: scheme is not allowed.
-  EXPECT_FALSE(TestSingleVerifiedSite("file:///example.com", "example.com"));
-
-  // Trailing slash in URL.
-  EXPECT_TRUE(TestSingleVerifiedSite("http://example.com/", "example.com"));
-
-  // Page on the domain.
-  EXPECT_TRUE(TestSingleVerifiedSite("http://example.com/page.html",
-                                     "example.com"));
-
-  // Page on a subdomain.
-  EXPECT_TRUE(TestSingleVerifiedSite("http://sub.example.com/page.html",
-                                     "example.com"));
-
-  // Root domain when only a subdomain is verified.
-  EXPECT_FALSE(TestSingleVerifiedSite("http://example.com/",
-                                      "sub.example.com"));
-
-  // Different subdomain when only a subdomain is verified.
-  EXPECT_FALSE(TestSingleVerifiedSite("http://www.example.com/",
-                                      "sub.example.com"));
-
-  // Port matches.
-  EXPECT_TRUE(TestSingleVerifiedSite("http://example.com:123/",
-                                     "example.com:123"));
-
-  // Port doesn't match.
-  EXPECT_FALSE(TestSingleVerifiedSite("http://example.com:456/",
-                                      "example.com:123"));
-
-  // Port is missing in the requestor URL.
-  EXPECT_FALSE(TestSingleVerifiedSite("http://example.com/",
-                                      "example.com:123"));
-
-  // Port is missing in the verified site (any port matches).
-  EXPECT_TRUE(TestSingleVerifiedSite("http://example.com:123/", "example.com"));
-
-  // Path matches.
-  EXPECT_TRUE(TestSingleVerifiedSite("http://example.com/path",
-                                     "example.com/path"));
-
-  // Path doesn't match.
-  EXPECT_FALSE(TestSingleVerifiedSite("http://example.com/foo",
-                                      "example.com/path"));
-
-  // Path is missing.
-  EXPECT_FALSE(TestSingleVerifiedSite("http://example.com",
-                                      "example.com/path"));
-
-  // Path matches (with trailing slash).
-  EXPECT_TRUE(TestSingleVerifiedSite("http://example.com/path/",
-                                     "example.com/path"));
-
-  // Path matches (is a file under the path).
-  EXPECT_TRUE(TestSingleVerifiedSite("http://example.com/path/page.html",
-                                     "example.com/path"));
-
-  // Path and port match.
-  EXPECT_TRUE(TestSingleVerifiedSite(
-      "http://example.com:123/path/page.html", "example.com:123/path"));
-
-  // Match specific valid schemes
-  EXPECT_TRUE(TestSingleVerifiedSite("http://example.com",
-                                     "http://example.com"));
-  EXPECT_TRUE(TestSingleVerifiedSite("https://example.com",
-                                     "https://example.com"));
-
-  // Mismatch specific vaild schemes
-  EXPECT_FALSE(TestSingleVerifiedSite("https://example.com",
-                                      "http://example.com"));
-  EXPECT_FALSE(TestSingleVerifiedSite("http://example.com",
-                                      "https://example.com"));
-
-  // Invalid scheme spec
-  EXPECT_FALSE(TestSingleVerifiedSite("file://example.com",
-                                      "file://example.com"));
-
-  std::vector<std::string> verified_sites;
-  verified_sites.push_back("foo.example.com");
-  verified_sites.push_back("bar.example.com:123");
-  verified_sites.push_back("example.com/unicorns");
-
-  // Test valid examples against the site list.
-
-  EXPECT_TRUE(TestMultipleVerifiedSites("http://foo.example.com",
-                                        verified_sites));
-
-  EXPECT_TRUE(TestMultipleVerifiedSites("http://bar.example.com:123",
-                                        verified_sites));
-
-  EXPECT_TRUE(TestMultipleVerifiedSites(
-      "http://cooking.example.com/unicorns/bacon.html", verified_sites));
-
-  // Test invalid examples against the site list.
-
-  EXPECT_FALSE(TestMultipleVerifiedSites("http://example.com",
-                                         verified_sites));
-
-  EXPECT_FALSE(TestMultipleVerifiedSites("file://foo.example.com",
-                                         verified_sites));
-
-  EXPECT_FALSE(TestMultipleVerifiedSites("http://baz.example.com",
-                                         verified_sites));
-
-  EXPECT_FALSE(TestMultipleVerifiedSites("http://bar.example.com:456",
-                                         verified_sites));
-}
-
-}  // namespace extensions
diff --git a/chrome/browser/extensions/webstore_install_with_prompt.cc b/chrome/browser/extensions/webstore_install_with_prompt.cc
index aab742f..4e60cd55 100644
--- a/chrome/browser/extensions/webstore_install_with_prompt.cc
+++ b/chrome/browser/extensions/webstore_install_with_prompt.cc
@@ -50,10 +50,6 @@
   return !parent_window_tracker_->WasNativeWindowClosed();
 }
 
-const GURL& WebstoreInstallWithPrompt::GetRequestorURL() const {
-  return dummy_requestor_url_;
-}
-
 std::unique_ptr<ExtensionInstallPrompt::Prompt>
 WebstoreInstallWithPrompt::CreateInstallPrompt() const {
   return std::make_unique<ExtensionInstallPrompt::Prompt>(
@@ -79,20 +75,4 @@
   return dummy_web_contents_.get();
 }
 
-bool WebstoreInstallWithPrompt::CheckInlineInstallPermitted(
-    const base::DictionaryValue& webstore_data,
-    std::string* error) const {
-  // Assume the requestor is trusted.
-  *error = std::string();
-  return true;
-}
-
-bool WebstoreInstallWithPrompt::CheckRequestorPermitted(
-    const base::DictionaryValue& webstore_data,
-    std::string* error) const {
-  // Assume the requestor is trusted.
-  *error = std::string();
-  return true;
-}
-
 }  // namespace extensions
diff --git a/chrome/browser/extensions/webstore_install_with_prompt.h b/chrome/browser/extensions/webstore_install_with_prompt.h
index ce4976ad..483ad49 100644
--- a/chrome/browser/extensions/webstore_install_with_prompt.h
+++ b/chrome/browser/extensions/webstore_install_with_prompt.h
@@ -52,23 +52,16 @@
 
   // extensions::WebstoreStandaloneInstaller overrides:
   bool CheckRequestorAlive() const override;
-  const GURL& GetRequestorURL() const override;
   bool ShouldShowPostInstallUI() const override;
   bool ShouldShowAppInstalledBubble() const override;
   content::WebContents* GetWebContents() const override;
   std::unique_ptr<ExtensionInstallPrompt::Prompt> CreateInstallPrompt()
       const override;
   std::unique_ptr<ExtensionInstallPrompt> CreateInstallUI() override;
-  bool CheckInlineInstallPermitted(const base::DictionaryValue& webstore_data,
-                                   std::string* error) const override;
-  bool CheckRequestorPermitted(const base::DictionaryValue& webstore_data,
-                               std::string* error) const override;
 
  private:
   bool show_post_install_ui_;
 
-  GURL dummy_requestor_url_;
-
   // A non-visible WebContents used to download data from the webstore.
   std::unique_ptr<content::WebContents> dummy_web_contents_;
 
diff --git a/chrome/browser/extensions/webstore_installer.h b/chrome/browser/extensions/webstore_installer.h
index ce7e85f..2dc86e3a 100644
--- a/chrome/browser/extensions/webstore_installer.h
+++ b/chrome/browser/extensions/webstore_installer.h
@@ -58,6 +58,9 @@
   enum InstallSource {
     // Inline installs trigger slightly different behavior (install source
     // is different, download referrers are the item's page in the gallery).
+    // TODO(ackermanb): Remove once server side metrics (omaha) tracking with
+    // this enum is figured out with any of the subclasses of
+    // WebstoreStandaloneInstaller.
     INSTALL_SOURCE_INLINE,
     INSTALL_SOURCE_APP_LAUNCHER,
     INSTALL_SOURCE_OTHER
diff --git a/chrome/browser/extensions/webstore_installer_test.cc b/chrome/browser/extensions/webstore_installer_test.cc
index 7bdca8c9..8318d22 100644
--- a/chrome/browser/extensions/webstore_installer_test.cc
+++ b/chrome/browser/extensions/webstore_installer_test.cc
@@ -8,8 +8,6 @@
 #include "chrome/browser/download/download_prefs.h"
 #include "chrome/browser/extensions/extension_install_prompt.h"
 #include "chrome/browser/extensions/tab_helper.h"
-#include "chrome/browser/extensions/webstore_inline_installer.h"
-#include "chrome/browser/extensions/webstore_inline_installer_factory.h"
 #include "chrome/browser/extensions/webstore_installer_test.h"
 #include "chrome/browser/extensions/webstore_standalone_installer.h"
 #include "chrome/browser/profiles/profile.h"
@@ -33,8 +31,6 @@
 using content::WebContents;
 using extensions::Extension;
 using extensions::TabHelper;
-using extensions::WebstoreInlineInstaller;
-using extensions::WebstoreInlineInstallerFactory;
 using extensions::WebstoreStandaloneInstaller;
 
 using net::test_server::HttpRequest;
diff --git a/chrome/browser/extensions/webstore_installer_unittest.cc b/chrome/browser/extensions/webstore_installer_unittest.cc
index 3befa733..9adee7ab 100644
--- a/chrome/browser/extensions/webstore_installer_unittest.cc
+++ b/chrome/browser/extensions/webstore_installer_unittest.cc
@@ -26,8 +26,8 @@
 TEST(WebstoreInstallerTest, PlatformParams) {
   std::string id = crx_file::id_util::GenerateId("some random string");
   std::string source = "inline";
-  GURL url = WebstoreInstaller::GetWebstoreInstallURL(id,
-      WebstoreInstaller::INSTALL_SOURCE_INLINE);
+  GURL url = WebstoreInstaller::GetWebstoreInstallURL(
+      id, WebstoreInstaller::INSTALL_SOURCE_INLINE);
   std::string query = url.query();
   EXPECT_TRUE(
       Contains(query, StringPrintf("os=%s", UpdateQueryParams::GetOS())));
diff --git a/chrome/browser/extensions/webstore_reinstaller.cc b/chrome/browser/extensions/webstore_reinstaller.cc
index 720b69e6..61fcf8eb 100644
--- a/chrome/browser/extensions/webstore_reinstaller.cc
+++ b/chrome/browser/extensions/webstore_reinstaller.cc
@@ -43,10 +43,6 @@
   return web_contents() != NULL;
 }
 
-const GURL& WebstoreReinstaller::GetRequestorURL() const {
-  return GURL::EmptyGURL();
-}
-
 std::unique_ptr<ExtensionInstallPrompt::Prompt>
 WebstoreReinstaller::CreateInstallPrompt() const {
   std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt(
@@ -71,18 +67,6 @@
   return web_contents();
 }
 
-bool WebstoreReinstaller::CheckInlineInstallPermitted(
-    const base::DictionaryValue& webstore_data,
-    std::string* error) const {
-  return true;
-}
-
-bool WebstoreReinstaller::CheckRequestorPermitted(
-    const base::DictionaryValue& webstore_data,
-    std::string* error) const {
-  return true;
-}
-
 void WebstoreReinstaller::WebContentsDestroyed() {
   // Run the callback now, because AbortInstall() doesn't do it.
   RunCallback(false, kTabClosed, webstore_install::ABORTED);
diff --git a/chrome/browser/extensions/webstore_reinstaller.h b/chrome/browser/extensions/webstore_reinstaller.h
index 19996fcf..24904a9b 100644
--- a/chrome/browser/extensions/webstore_reinstaller.h
+++ b/chrome/browser/extensions/webstore_reinstaller.h
@@ -30,16 +30,11 @@
 
   // WebstoreStandaloneInstaller:
   bool CheckRequestorAlive() const override;
-  const GURL& GetRequestorURL() const override;
   bool ShouldShowPostInstallUI() const override;
   bool ShouldShowAppInstalledBubble() const override;
   content::WebContents* GetWebContents() const override;
   std::unique_ptr<ExtensionInstallPrompt::Prompt> CreateInstallPrompt()
       const override;
-  bool CheckInlineInstallPermitted(const base::DictionaryValue& webstore_data,
-                                   std::string* error) const override;
-  bool CheckRequestorPermitted(const base::DictionaryValue& webstore_data,
-                               std::string* error) const override;
   void OnInstallPromptDone(ExtensionInstallPrompt::Result result) override;
 
   // content::WebContentsObserver:
diff --git a/chrome/browser/extensions/webstore_standalone_installer.cc b/chrome/browser/extensions/webstore_standalone_installer.cc
index a4499af4..e8517e9 100644
--- a/chrome/browser/extensions/webstore_standalone_installer.cc
+++ b/chrome/browser/extensions/webstore_standalone_installer.cc
@@ -38,8 +38,7 @@
       install_source_(WebstoreInstaller::INSTALL_SOURCE_INLINE),
       show_user_count_(true),
       average_rating_(0.0),
-      rating_count_(0) {
-}
+      rating_count_(0) {}
 
 void WebstoreStandaloneInstaller::BeginInstall() {
   // Add a ref to keep this alive for WebstoreDataFetcher.
@@ -63,10 +62,7 @@
   // Use the requesting page as the referrer both since that is more correct
   // (it is the page that caused this request to happen) and so that we can
   // track top sites that trigger inline install requests.
-  webstore_data_fetcher_.reset(
-      new WebstoreDataFetcher(this, GetRequestorURL(), id_));
-
-  webstore_data_fetcher_->SetPostData(GetPostData());
+  webstore_data_fetcher_.reset(new WebstoreDataFetcher(this, GURL(), id_));
 
   webstore_data_fetcher_->Start(
       content::BrowserContext::GetDefaultStoragePartition(profile_)
@@ -155,10 +151,6 @@
   return localized_extension_for_display_.get();
 }
 
-std::string WebstoreStandaloneInstaller::GetPostData() {
-  return std::string();
-}
-
 void WebstoreStandaloneInstaller::OnManifestParsed() {
   ProceedWithInstallPrompt();
 }
@@ -243,16 +235,6 @@
 
   std::string error;
 
-  if (!CheckInlineInstallPermitted(*webstore_data, &error)) {
-    CompleteInstall(webstore_install::NOT_PERMITTED, error);
-    return;
-  }
-
-  if (!CheckRequestorPermitted(*webstore_data, &error)) {
-    CompleteInstall(webstore_install::NOT_PERMITTED, error);
-    return;
-  }
-
   // Manifest, number of users, average rating and rating count are required.
   std::string manifest;
   if (!webstore_data->GetString(kManifestKey, &manifest) ||
diff --git a/chrome/browser/extensions/webstore_standalone_installer.h b/chrome/browser/extensions/webstore_standalone_installer.h
index d2989a1..c52695c 100644
--- a/chrome/browser/extensions/webstore_standalone_installer.h
+++ b/chrome/browser/extensions/webstore_standalone_installer.h
@@ -20,8 +20,6 @@
 #include "net/url_request/url_fetcher_delegate.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
-class GURL;
-
 namespace base {
 class DictionaryValue;
 }
@@ -87,20 +85,11 @@
 
   // Template Method's hooks to be implemented by subclasses.
 
-  // Gives subclasses an opportunity to provide extra post data in the form of
-  // serialized proto to the webstore data request before sending. The default
-  // implementation returns an empty string.
-  virtual std::string GetPostData();
-
   // Called at certain check points of the workflow to decide whether it makes
   // sense to proceed with installation. A requestor can be a website that
   // initiated an inline installation, or a command line option.
   virtual bool CheckRequestorAlive() const = 0;
 
-  // Requestor's URL, if any. Should be an empty GURL if URL is meaningless
-  // (e.g. for a command line option).
-  virtual const GURL& GetRequestorURL() const = 0;
-
   // Should a new tab be opened after installation to show the newly installed
   // extension's icon?
   virtual bool ShouldShowPostInstallUI() const = 0;
@@ -120,20 +109,6 @@
   virtual std::unique_ptr<ExtensionInstallPrompt::Prompt> CreateInstallPrompt()
       const = 0;
 
-  // Perform all necessary checks to make sure inline install is permitted,
-  // e.g. in the extension's properties in the store. The implementation may
-  // choose to ignore such properties.
-  virtual bool CheckInlineInstallPermitted(
-      const base::DictionaryValue& webstore_data,
-      std::string* error) const = 0;
-
-  // Perform all necessary checks to make sure that requestor is allowed to
-  // initiate this install (e.g. that the requestor's URL matches the verified
-  // author's site specified in the extension's properties in the store).
-  virtual bool CheckRequestorPermitted(
-      const base::DictionaryValue& webstore_data,
-      std::string* error) const = 0;
-
   // Will be called after the extension's manifest has been successfully parsed.
   // Subclasses can perform asynchronous checks at this point and call
   // ProceedWithInstallPrompt() to proceed with the install or otherwise call
diff --git a/chrome/browser/extensions/webstore_startup_installer.cc b/chrome/browser/extensions/webstore_startup_installer.cc
deleted file mode 100644
index 010821a..0000000
--- a/chrome/browser/extensions/webstore_startup_installer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/extensions/webstore_startup_installer.h"
-
-#include <memory>
-
-namespace extensions {
-
-WebstoreStartupInstaller::WebstoreStartupInstaller(
-    const std::string& webstore_item_id,
-    Profile* profile,
-    bool show_prompt,
-    const Callback& callback)
-    : WebstoreInstallWithPrompt(webstore_item_id, profile, callback),
-      show_prompt_(show_prompt) {
-  set_install_source(WebstoreInstaller::INSTALL_SOURCE_INLINE);
-  set_show_post_install_ui(false);
-}
-
-WebstoreStartupInstaller::~WebstoreStartupInstaller() {}
-
-std::unique_ptr<ExtensionInstallPrompt::Prompt>
-WebstoreStartupInstaller::CreateInstallPrompt() const {
-  if (show_prompt_) {
-    return std::make_unique<ExtensionInstallPrompt::Prompt>(
-        ExtensionInstallPrompt::INSTALL_PROMPT);
-  }
-  return NULL;
-}
-
-}  // namespace extensions
diff --git a/chrome/browser/extensions/webstore_startup_installer.h b/chrome/browser/extensions/webstore_startup_installer.h
deleted file mode 100644
index 594582d6..0000000
--- a/chrome/browser/extensions/webstore_startup_installer.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_EXTENSIONS_WEBSTORE_STARTUP_INSTALLER_H_
-#define CHROME_BROWSER_EXTENSIONS_WEBSTORE_STARTUP_INSTALLER_H_
-
-#include "base/gtest_prod_util.h"
-#include "base/macros.h"
-#include "chrome/browser/extensions/webstore_install_with_prompt.h"
-
-namespace extensions {
-
-// Manages inline installs requested to be performed at startup, e.g. via a
-// command line option: downloads and parses metadata from the webstore,
-// optionally shows an install UI, starts the download once the user
-// confirms.
-//
-// Clients will be notified of success or failure via the |callback| argument
-// passed into the constructor.
-class WebstoreStartupInstaller : public WebstoreInstallWithPrompt {
- public:
-  WebstoreStartupInstaller(const std::string& webstore_item_id,
-                           Profile* profile,
-                           bool show_prompt,
-                           const Callback& callback);
-
- protected:
-  friend class base::RefCountedThreadSafe<WebstoreStartupInstaller>;
-  FRIEND_TEST_ALL_PREFIXES(WebstoreStartupInstallerTest, DomainVerification);
-
-  ~WebstoreStartupInstaller() override;
-
-  // Implementations of WebstoreStandaloneInstaller Template Method's hooks.
-  std::unique_ptr<ExtensionInstallPrompt::Prompt> CreateInstallPrompt()
-      const override;
-
- private:
-  bool show_prompt_;
-
-  DISALLOW_IMPLICIT_CONSTRUCTORS(WebstoreStartupInstaller);
-};
-
-}  // namespace extensions
-
-#endif  // CHROME_BROWSER_EXTENSIONS_WEBSTORE_STARTUP_INSTALLER_H_
diff --git a/chrome/browser/extensions/webstore_startup_installer_browsertest.cc b/chrome/browser/extensions/webstore_startup_installer_browsertest.cc
deleted file mode 100644
index 498ab5c..0000000
--- a/chrome/browser/extensions/webstore_startup_installer_browsertest.cc
+++ /dev/null
@@ -1,247 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <utility>
-
-#include "base/command_line.h"
-#include "base/scoped_observer.h"
-#include "build/build_config.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/webstore_installer_test.h"
-#include "chrome/browser/infobars/infobar_service.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/common/chrome_switches.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/browser_test_utils.h"
-#include "extensions/browser/extension_host.h"
-#include "extensions/browser/extension_registry.h"
-#include "extensions/browser/extension_system.h"
-#include "extensions/browser/install/extension_install_ui.h"
-#include "extensions/common/extension_builder.h"
-#include "extensions/common/value_builder.h"
-#include "net/dns/mock_host_resolver.h"
-#include "url/gurl.h"
-
-#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
-#include "chrome/browser/supervised_user/supervised_user_constants.h"
-#endif
-
-#if defined(OS_CHROMEOS)
-#include "chromeos/chromeos_switches.h"
-#endif
-
-using content::WebContents;
-using extensions::DictionaryBuilder;
-using extensions::Extension;
-using extensions::ExtensionBuilder;
-using extensions::ListBuilder;
-
-const char kWebstoreDomain[] = "cws.com";
-const char kAppDomain[] = "app.com";
-const char kNonAppDomain[] = "nonapp.com";
-const char kTestExtensionId[] = "ecglahbcnmdpdciemllbhojghbkagdje";
-const char kTestDataPath[] = "extensions/api_test/webstore_inline_install";
-const char kCrxFilename[] = "extension.crx";
-
-class WebstoreStartupInstallerTest : public WebstoreInstallerTest {
- public:
-  WebstoreStartupInstallerTest()
-      : WebstoreInstallerTest(
-            kWebstoreDomain,
-            kTestDataPath,
-            kCrxFilename,
-            kAppDomain,
-            kNonAppDomain) {}
-};
-
-IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest, Install) {
-  AutoAcceptInstall();
-
-  ui_test_utils::NavigateToURL(
-      browser(), GenerateTestServerUrl(kAppDomain, "install.html"));
-
-  RunTest("runTest");
-
-  extensions::ExtensionRegistry* registry =
-      extensions::ExtensionRegistry::Get(browser()->profile());
-  const extensions::Extension* extension =
-      registry->enabled_extensions().GetByID(kTestExtensionId);
-  EXPECT_TRUE(extension);
-}
-
-IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest,
-    InstallNotAllowedFromNonVerifiedDomains) {
-  AutoCancelInstall();
-  ui_test_utils::NavigateToURL(
-      browser(),
-      GenerateTestServerUrl(kNonAppDomain, "install_non_verified_domain.html"));
-
-  RunTest("runTest1");
-  RunTest("runTest2");
-}
-
-IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest, FindLink) {
-  ui_test_utils::NavigateToURL(
-      browser(), GenerateTestServerUrl(kAppDomain, "find_link.html"));
-
-  RunTest("runTest");
-}
-
-// Flakes on all platforms: http://crbug.com/95713, http://crbug.com/229947
-IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest,
-                       DISABLED_ArgumentValidation) {
-  AutoCancelInstall();
-
-  // Each of these tests has to run separately, since one page/tab can
-  // only have one in-progress install request. These tests don't all pass
-  // callbacks to install, so they have no way to wait for the installation
-  // to complete before starting the next test.
-  bool is_finished = false;
-  for (int i = 0; !is_finished; ++i) {
-    ui_test_utils::NavigateToURL(
-        browser(),
-        GenerateTestServerUrl(kAppDomain, "argument_validation.html"));
-    is_finished = !RunIndexedTest("runTest", i);
-  }
-}
-
-IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest, MultipleInstallCalls) {
-  AutoCancelInstall();
-
-  ui_test_utils::NavigateToURL(
-      browser(),
-      GenerateTestServerUrl(kAppDomain, "multiple_install_calls.html"));
-  RunTest("runTest");
-}
-
-IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest, InstallNotSupported) {
-  AutoCancelInstall();
-  ui_test_utils::NavigateToURL(
-      browser(),
-      GenerateTestServerUrl(kAppDomain, "install_not_supported.html"));
-
-  ui_test_utils::WindowedTabAddedNotificationObserver observer(
-      content::NotificationService::AllSources());
-  RunTest("runTest");
-  observer.Wait();
-
-  // The inline install should fail, and a store-provided URL should be opened
-  // in a new tab.
-  WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  EXPECT_EQ(GURL("http://cws.com/show-me-the-money"), web_contents->GetURL());
-}
-
-// Regression test for http://crbug.com/144991.
-IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest, InstallFromHostedApp) {
-  AutoAcceptInstall();
-
-  const GURL kInstallUrl = GenerateTestServerUrl(kAppDomain, "install.html");
-
-  // We're forced to construct a hosted app dynamically because we need the
-  // app to run on a declared URL, but we don't know the port ahead of time.
-  scoped_refptr<const Extension> hosted_app =
-      ExtensionBuilder()
-          .SetManifest(
-              DictionaryBuilder()
-                  .Set("name", "hosted app")
-                  .Set("version", "1")
-                  .Set(
-                      "app",
-                      DictionaryBuilder()
-                          .Set("urls",
-                               ListBuilder().Append(kInstallUrl.spec()).Build())
-                          .Set("launch", DictionaryBuilder()
-                                             .Set("web_url", kInstallUrl.spec())
-                                             .Build())
-                          .Build())
-                  .Set("manifest_version", 2)
-                  .Build())
-          .Build();
-  ASSERT_TRUE(hosted_app.get());
-
-  extensions::ExtensionService* extension_service =
-      extensions::ExtensionSystem::Get(browser()->profile())
-          ->extension_service();
-  extensions::ExtensionRegistry* registry =
-      extensions::ExtensionRegistry::Get(browser()->profile());
-
-  extension_service->AddExtension(hosted_app.get());
-  EXPECT_TRUE(registry->enabled_extensions().GetByID(hosted_app->id()));
-
-  ui_test_utils::NavigateToURL(browser(), kInstallUrl);
-
-  EXPECT_FALSE(registry->enabled_extensions().GetByID(kTestExtensionId));
-  RunTest("runTest");
-  EXPECT_TRUE(registry->enabled_extensions().GetByID(kTestExtensionId));
-}
-
-#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
-class WebstoreStartupInstallerSupervisedUsersTest
-    : public WebstoreStartupInstallerTest {
- public:
-  // InProcessBrowserTest overrides:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    WebstoreStartupInstallerTest::SetUpCommandLine(command_line);
-    command_line->AppendSwitchASCII(switches::kSupervisedUserId,
-                                    supervised_users::kChildAccountSUID);
-#if defined(OS_CHROMEOS)
-    command_line->AppendSwitchASCII(
-        chromeos::switches::kLoginUser,
-        "supervised_user@locally-managed.localhost");
-    command_line->AppendSwitchASCII(chromeos::switches::kLoginProfile, "hash");
-#endif
-  }
-};
-
-IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerSupervisedUsersTest,
-                       InstallProhibited) {
-  AutoAcceptInstall();
-
-  ui_test_utils::NavigateToURL(
-      browser(), GenerateTestServerUrl(kAppDomain, "install_prohibited.html"));
-
-  RunTest("runTest");
-
-  // No error infobar should show up.
-  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
-  InfoBarService* infobar_service = InfoBarService::FromWebContents(contents);
-  EXPECT_EQ(0u, infobar_service->infobar_count());
-}
-#endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS)
-
-// The unpack failure test needs to use a different install .crx, which is
-// specified via a command-line flag, so it needs its own test subclass.
-class WebstoreStartupInstallUnpackFailureTest
-    : public WebstoreStartupInstallerTest {
- public:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    WebstoreStartupInstallerTest::SetUpCommandLine(command_line);
-
-    GURL crx_url = GenerateTestServerUrl(
-        kWebstoreDomain, "malformed_extension.crx");
-    base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-        switches::kAppsGalleryUpdateURL, crx_url.spec());
-  }
-
-  void SetUpInProcessBrowserTestFixture() override {
-    WebstoreStartupInstallerTest::SetUpInProcessBrowserTestFixture();
-    extensions::ExtensionInstallUI::set_disable_ui_for_tests();
-  }
-};
-
-IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallUnpackFailureTest,
-    WebstoreStartupInstallUnpackFailureTest) {
-  AutoAcceptInstall();
-
-  ui_test_utils::NavigateToURL(browser(),
-      GenerateTestServerUrl(kAppDomain, "install_unpack_failure.html"));
-
-  RunTest("runTest");
-}
diff --git a/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc b/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc
index dc25f92..8c94cdf 100644
--- a/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc
+++ b/chrome/browser/loader/chrome_resource_dispatcher_host_delegate.cc
@@ -700,8 +700,15 @@
   if (data_reduction_proxy_io_data && previews_decider_impl) {
     previews::PreviewsUserData::Create(url_request,
                                        previews_decider_impl->GeneratePageId());
-    if (data_reduction_proxy_io_data->ShouldAcceptServerPreview(
-            *url_request, previews_decider_impl)) {
+    if (url_request->url().SchemeIsHTTPOrHTTPS() &&
+        previews_decider_impl->ShouldAllowPreviewAtECT(
+            *url_request, previews::PreviewsType::LITE_PAGE,
+            net::EFFECTIVE_CONNECTION_TYPE_4G, std::vector<std::string>(),
+            true) &&
+        previews_decider_impl->ShouldAllowPreviewAtECT(
+            *url_request, previews::PreviewsType::LOFI,
+            net::EFFECTIVE_CONNECTION_TYPE_4G, std::vector<std::string>(),
+            true)) {
       previews_state |= content::SERVER_LOFI_ON;
       previews_state |= content::SERVER_LITE_PAGE_ON;
     }
diff --git a/chrome/browser/media/encrypted_media_browsertest.cc b/chrome/browser/media/encrypted_media_browsertest.cc
index 9e4abd92..af8bb6d 100644
--- a/chrome/browser/media/encrypted_media_browsertest.cc
+++ b/chrome/browser/media/encrypted_media_browsertest.cc
@@ -299,9 +299,7 @@
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitchASCII(
-        switches::kAutoplayPolicy,
-        switches::autoplay::kNoUserGestureRequiredPolicy);
+    MediaBrowserTest::SetUpCommandLine(command_line);
     command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
                                     "EncryptedMediaHdcpPolicyCheck");
   }
@@ -317,11 +315,12 @@
 
   void SetUpCommandLineForKeySystem(const std::string& key_system,
                                     base::CommandLine* command_line) {
-    if (GetServerConfig(key_system))
+    if (GetServerConfig(key_system)) {
       // Since the web and license servers listen on different ports, we need to
       // disable web-security to send license requests to the license server.
       // TODO(shadi): Add port forwarding to the test web server configuration.
       command_line->AppendSwitch(switches::kDisableWebSecurity);
+    }
 
 #if BUILDFLAG(ENABLE_LIBRARY_CDMS)
     if (IsExternalClearKey(key_system)) {
diff --git a/chrome/browser/media/media_browsertest.cc b/chrome/browser/media/media_browsertest.cc
index cbc9e42..47a2d406 100644
--- a/chrome/browser/media/media_browsertest.cc
+++ b/chrome/browser/media/media_browsertest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/media/media_browsertest.h"
 
+#include "base/command_line.h"
 #include "base/i18n/time_formatting.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
@@ -14,6 +15,7 @@
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test_utils.h"
+#include "media/base/media_switches.h"
 #include "media/base/test_data_util.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 
@@ -21,6 +23,15 @@
 
 MediaBrowserTest::~MediaBrowserTest() {}
 
+void MediaBrowserTest::SetUpCommandLine(base::CommandLine* command_line) {
+  command_line->AppendSwitchASCII(
+      switches::kAutoplayPolicy,
+      switches::autoplay::kNoUserGestureRequiredPolicy);
+  // Disable fallback after decode error to avoid unexpected test pass on the
+  // fallback path.
+  scoped_feature_list_.InitAndDisableFeature(media::kFallbackAfterDecodeError);
+}
+
 void MediaBrowserTest::RunMediaTestPage(const std::string& html_page,
                                         const base::StringPairs& query_params,
                                         const std::string& expected_title,
diff --git a/chrome/browser/media/media_browsertest.h b/chrome/browser/media/media_browsertest.h
index 9c50ba2..1ecc195 100644
--- a/chrome/browser/media/media_browsertest.h
+++ b/chrome/browser/media/media_browsertest.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/test/scoped_feature_list.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "media/base/test_data_util.h"
 
@@ -22,6 +23,9 @@
   MediaBrowserTest();
   ~MediaBrowserTest() override;
 
+  // InProcessBrowserTest implementation.
+  void SetUpCommandLine(base::CommandLine* command_line) override;
+
   // Runs a html page with a list of URL query parameters.
   // If http is true, the test starts a local http test server to load the test
   // page, otherwise a local file URL is loaded inside the content shell.
@@ -36,6 +40,9 @@
   std::string RunTest(const GURL& gurl, const std::string& expected);
 
   virtual void AddWaitForTitles(content::TitleWatcher* title_watcher);
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 #endif  // CHROME_BROWSER_MEDIA_MEDIA_BROWSERTEST_H_
diff --git a/chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings.cc b/chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings.cc
index 82e1225..ff73cb7 100644
--- a/chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings.cc
+++ b/chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings.cc
@@ -17,6 +17,8 @@
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
+#include "chrome/browser/previews/previews_service.h"
+#include "chrome/browser/previews/previews_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/pref_names.h"
@@ -30,6 +32,7 @@
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
+#include "components/previews/content/previews_ui_service.h"
 #include "components/proxy_config/proxy_config_pref_names.h"
 #include "components/proxy_config/proxy_prefs.h"
 #include "content/public/browser/browser_thread.h"
@@ -175,8 +178,8 @@
 
 DataReductionProxyChromeSettings::DataReductionProxyChromeSettings()
     : data_reduction_proxy::DataReductionProxySettings(),
-      data_reduction_proxy_enabled_pref_name_(prefs::kDataSaverEnabled) {
-}
+      data_reduction_proxy_enabled_pref_name_(prefs::kDataSaverEnabled),
+      profile_(nullptr) {}
 
 DataReductionProxyChromeSettings::~DataReductionProxyChromeSettings() {
 }
@@ -192,10 +195,12 @@
     data_reduction_proxy::DataReductionProxyIOData* io_data,
     PrefService* profile_prefs,
     net::URLRequestContextGetter* request_context_getter,
+    Profile* profile,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
     std::unique_ptr<data_reduction_proxy::DataStore> store,
     const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner,
     const scoped_refptr<base::SequencedTaskRunner>& db_task_runner) {
+  profile_ = profile;
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 #if defined(OS_ANDROID)
   // On mobile we write Data Reduction Proxy prefs directly to the pref service.
@@ -231,6 +236,18 @@
   MigrateDataReductionProxyOffProxyPrefs(profile_prefs);
 }
 
+void DataReductionProxyChromeSettings::SetIgnoreLongTermBlackListRules(
+    bool ignore_long_term_black_list_rules) {
+  // |previews_service| is null if |profile_| is off the record.
+  PreviewsService* previews_service =
+      PreviewsServiceFactory::GetForProfile(profile_);
+  if (previews_service && previews_service->previews_ui_service()) {
+    previews_service->previews_ui_service()
+        ->SetIgnoreLongTermBlackListForServerPreviews(
+            ignore_long_term_black_list_rules);
+  }
+}
+
 // static
 data_reduction_proxy::Client DataReductionProxyChromeSettings::GetClient() {
 #if defined(OS_ANDROID)
diff --git a/chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings.h b/chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings.h
index 49189b5..2fa3d69 100644
--- a/chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings.h
+++ b/chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings.h
@@ -14,6 +14,7 @@
 #include "components/keyed_service/core/keyed_service.h"
 
 class PrefService;
+class Profile;
 
 namespace base {
 class SequencedTaskRunner;
@@ -72,6 +73,7 @@
       data_reduction_proxy::DataReductionProxyIOData* io_data,
       PrefService* profile_prefs,
       net::URLRequestContextGetter* request_context_getter,
+      Profile* profile,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
       std::unique_ptr<data_reduction_proxy::DataStore> store,
       const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner,
@@ -90,6 +92,9 @@
     data_reduction_proxy_enabled_pref_name_ = pref_name;
   }
 
+  void SetIgnoreLongTermBlackListRules(
+      bool ignore_long_term_black_list_rules) override;
+
  private:
   // Helper method for migrating the Data Reduction Proxy away from using the
   // proxy pref. Returns the ProxyPrefMigrationResult value indicating the
@@ -99,6 +104,9 @@
 
   std::string data_reduction_proxy_enabled_pref_name_;
 
+  // Null before InitDataReductionProxySettings is called.
+  Profile* profile_;
+
   DISALLOW_COPY_AND_ASSIGN(DataReductionProxyChromeSettings);
 };
 
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
index 65ddccc..bc645c4 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
@@ -243,5 +243,8 @@
   }
   // page_transition_ fits in a uint32_t, so we can safely cast to int64_t.
   builder.SetNavigation_PageTransition(static_cast<int64_t>(page_transition_));
+  // info.page_end_reason fits in a uint32_t, so we can safely cast to int64_t.
+  builder.SetNavigation_PageEndReason(
+      static_cast<int64_t>(info.page_end_reason));
   builder.Record(ukm::UkmRecorder::Get());
 }
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
index abdccd4..a60b4096 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
@@ -123,6 +123,9 @@
         kv.second.get(), PageLoad::kNavigation_PageTransitionName,
         ui::PAGE_TRANSITION_LINK);
     test_ukm_recorder().ExpectEntryMetric(
+        kv.second.get(), PageLoad::kNavigation_PageEndReasonName,
+        page_load_metrics::END_CLOSE);
+    test_ukm_recorder().ExpectEntryMetric(
         kv.second.get(), PageLoad::kParseTiming_NavigationToParseStartName,
         100);
     test_ukm_recorder().ExpectEntryMetric(
@@ -178,6 +181,9 @@
         kv.second.get(), PageLoad::kNavigation_PageTransitionName,
         ui::PAGE_TRANSITION_LINK);
     test_ukm_recorder().ExpectEntryMetric(
+        kv.second.get(), PageLoad::kNavigation_PageEndReasonName,
+        page_load_metrics::END_PROVISIONAL_LOAD_FAILED);
+    test_ukm_recorder().ExpectEntryMetric(
         kv.second.get(),
         PageLoad::kNet_EffectiveConnectionType2_OnNavigationStartName,
         metrics::SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G);
@@ -386,6 +392,9 @@
   ASSERT_NE(entry2, nullptr);
 
   test_ukm_recorder().ExpectEntrySourceHasUrl(entry1, GURL(kTestUrl1));
+  test_ukm_recorder().ExpectEntryMetric(entry1,
+                                        PageLoad::kNavigation_PageEndReasonName,
+                                        page_load_metrics::END_NEW_NAVIGATION);
   test_ukm_recorder().ExpectEntryMetric(
       entry1, PageLoad::kPaintTiming_NavigationToFirstContentfulPaintName, 200);
   EXPECT_FALSE(test_ukm_recorder().EntryHasMetric(
@@ -396,6 +405,9 @@
       entry1, PageLoad::kPageTiming_ForegroundDurationName));
 
   test_ukm_recorder().ExpectEntrySourceHasUrl(entry2, GURL(kTestUrl2));
+  test_ukm_recorder().ExpectEntryMetric(entry2,
+                                        PageLoad::kNavigation_PageEndReasonName,
+                                        page_load_metrics::END_CLOSE);
   EXPECT_FALSE(test_ukm_recorder().EntryHasMetric(
       entry2, PageLoad::kParseTiming_NavigationToParseStartName));
   EXPECT_FALSE(test_ukm_recorder().EntryHasMetric(
diff --git a/chrome/browser/previews/previews_infobar_delegate_unittest.cc b/chrome/browser/previews/previews_infobar_delegate_unittest.cc
index 36b760f..013b8b5 100644
--- a/chrome/browser/previews/previews_infobar_delegate_unittest.cc
+++ b/chrome/browser/previews/previews_infobar_delegate_unittest.cc
@@ -35,6 +35,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
 #include "components/blacklist/opt_out_blacklist/opt_out_blacklist_data.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_config_test_utils.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.h"
@@ -185,7 +186,7 @@
             drp_test_context_->GetDataReductionProxyEnabledPrefName());
     data_reduction_proxy_settings->InitDataReductionProxySettings(
         drp_test_context_->io_data(), drp_test_context_->pref_service(),
-        drp_test_context_->request_context_getter(),
+        drp_test_context_->request_context_getter(), profile(),
         base::MakeRefCounted<network::TestSharedURLLoaderFactory>(),
         base::WrapUnique(new data_reduction_proxy::DataStore()),
         base::ThreadTaskRunnerHandle::Get(),
diff --git a/chrome/browser/previews/previews_ui_tab_helper_unittest.cc b/chrome/browser/previews/previews_ui_tab_helper_unittest.cc
index a82e3f54e..cc2ef9dff 100644
--- a/chrome/browser/previews/previews_ui_tab_helper_unittest.cc
+++ b/chrome/browser/previews/previews_ui_tab_helper_unittest.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings_factory.h"
 #include "chrome/browser/previews/previews_ui_tab_helper.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "chrome/test/base/testing_profile.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_compression_stats.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h"
@@ -80,7 +81,7 @@
             drp_test_context_->GetDataReductionProxyEnabledPrefName());
     data_reduction_proxy_settings->InitDataReductionProxySettings(
         drp_test_context_->io_data(), drp_test_context_->pref_service(),
-        drp_test_context_->request_context_getter(),
+        drp_test_context_->request_context_getter(), profile(),
         base::MakeRefCounted<network::TestSharedURLLoaderFactory>(),
         base::WrapUnique(new data_reduction_proxy::DataStore()),
         base::ThreadTaskRunnerHandle::Get(),
diff --git a/chrome/browser/profiles/profile_impl_io_data.cc b/chrome/browser/profiles/profile_impl_io_data.cc
index 790785ed..dede943a 100644
--- a/chrome/browser/profiles/profile_impl_io_data.cc
+++ b/chrome/browser/profiles/profile_impl_io_data.cc
@@ -244,6 +244,7 @@
           base::FeatureList::IsEnabled(network::features::kNetworkService)
               ? nullptr
               : main_request_context_getter_.get(),
+          profile_,
           content::BrowserContext::GetDefaultStoragePartition(profile_)
               ->GetURLLoaderFactoryForBrowserProcess(),
           std::move(store),
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu_unittest.cc b/chrome/browser/renderer_context_menu/render_view_context_menu_unittest.cc
index 449100e3..4f73e9d0 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu_unittest.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu_unittest.cc
@@ -439,7 +439,7 @@
         drp_test_context_->GetDataReductionProxyEnabledPrefName());
     settings->InitDataReductionProxySettings(
         drp_test_context_->io_data(), drp_test_context_->pref_service(),
-        drp_test_context_->request_context_getter(),
+        drp_test_context_->request_context_getter(), profile(),
         base::MakeRefCounted<network::TestSharedURLLoaderFactory>(),
         std::make_unique<data_reduction_proxy::DataStore>(),
         base::ThreadTaskRunnerHandle::Get(),
diff --git a/chrome/browser/resources/chromeos/switch_access/automation_manager.js b/chrome/browser/resources/chromeos/switch_access/automation_manager.js
index dff1ccd..9f2238cd 100644
--- a/chrome/browser/resources/chromeos/switch_access/automation_manager.js
+++ b/chrome/browser/resources/chromeos/switch_access/automation_manager.js
@@ -251,8 +251,9 @@
   getRelevantMenuActions_() {
     // TODO(crbug/881080): determine relevant actions programmatically.
     let actions = [
-      ContextMenuManager.Action.CLICK, ContextMenuManager.Action.OPTIONS,
-      ContextMenuManager.Action.SCROLL_UP, ContextMenuManager.Action.SCROLL_DOWN
+      ContextMenuManager.Action.CLICK, ContextMenuManager.Action.DICTATION,
+      ContextMenuManager.Action.OPTIONS, ContextMenuManager.Action.SCROLL_UP,
+      ContextMenuManager.Action.SCROLL_DOWN
     ];
     return actions;
   }
diff --git a/chrome/browser/resources/chromeos/switch_access/context_menu_manager.js b/chrome/browser/resources/chromeos/switch_access/context_menu_manager.js
index ae8e4b6e..476569a 100644
--- a/chrome/browser/resources/chromeos/switch_access/context_menu_manager.js
+++ b/chrome/browser/resources/chromeos/switch_access/context_menu_manager.js
@@ -194,6 +194,8 @@
 
     if (event.data === ContextMenuManager.Action.CLICK)
       this.automationManager_.selectCurrentNode();
+    else if (event.data === ContextMenuManager.Action.DICTATION)
+      chrome.accessibilityPrivate.toggleDictation();
     else if (event.data === ContextMenuManager.Action.OPTIONS)
       window.switchAccess.showOptionsPage();
     else if (
@@ -228,6 +230,7 @@
  */
 ContextMenuManager.Action = {
   CLICK: 'click',
+  DICTATION: 'dictation',
   OPTIONS: 'options',
   SCROLL_BACKWARD: 'scroll-backward',
   SCROLL_DOWN: 'scroll-down',
diff --git a/chrome/browser/resources/chromeos/switch_access/panel.html b/chrome/browser/resources/chromeos/switch_access/panel.html
index c1b834a..57d062f 100644
--- a/chrome/browser/resources/chromeos/switch_access/panel.html
+++ b/chrome/browser/resources/chromeos/switch_access/panel.html
@@ -19,6 +19,7 @@
     <button class="action" id="scroll-up" disabled>Scroll up</button>
     <button class="action" id="scroll-right" disabled>Scroll right</button>
     <button class="action" id="scroll-left" disabled>Scroll left</button>
+    <button class="action" id="dictation">Dictation</button>
     <button class="action" id="options">Options</button>
   </div>
 </body>
diff --git a/chrome/browser/resources/md_extensions/options_dialog.js b/chrome/browser/resources/md_extensions/options_dialog.js
index 817862f..29f082c6 100644
--- a/chrome/browser/resources/md_extensions/options_dialog.js
+++ b/chrome/browser/resources/md_extensions/options_dialog.js
@@ -84,7 +84,13 @@
           preferredSize = e;
           if (!this.$.dialog.open)
             this.$.dialog.showModal();
-          this.updateDialogSize_(preferredSize.width, preferredSize.height);
+          // Updating the dialog size can result in a preferred size change, so
+          // wait until request animation frame fires before updating the dialog
+          // size. This hysteresis prevents the preferred size from oscillating
+          // (see: https://crbug.com/882835).
+          requestAnimationFrame(() => {
+            this.updateDialogSize_(preferredSize.width, preferredSize.height);
+          });
         };
 
         this.boundResizeListener_ = () => {
diff --git a/chrome/browser/resources/ntp4/new_tab.js b/chrome/browser/resources/ntp4/new_tab.js
index 98066bfe..f47884a 100644
--- a/chrome/browser/resources/ntp4/new_tab.js
+++ b/chrome/browser/resources/ntp4/new_tab.js
@@ -221,14 +221,16 @@
    */
   function layoutFooter() {
     // We need the image to be loaded.
-    var logo = $('logo-img');
-    var logoImg = logo.querySelector('img');
-    if (!logoImg.complete) {
+    let logo = $('logo-img');
+    let logoImg = logo.querySelector('img');
+
+    // Only compare the width after the footer image successfully loaded.
+    if (!logoImg.complete || logoImg.width === 0) {
       logoImg.onload = layoutFooter;
       return;
     }
 
-    var menu = $('footer-menu-container');
+    let menu = $('footer-menu-container');
     if (menu.clientWidth > logoImg.width)
       logo.style.WebkitFlex = '0 1 ' + menu.clientWidth + 'px';
     else
diff --git a/chrome/browser/resources/print_preview/new/advanced_options_settings.html b/chrome/browser/resources/print_preview/new/advanced_options_settings.html
index 8165993..a7f252c 100644
--- a/chrome/browser/resources/print_preview/new/advanced_options_settings.html
+++ b/chrome/browser/resources/print_preview/new/advanced_options_settings.html
@@ -22,7 +22,7 @@
       }
     </style>
     <print-preview-settings-section>
-      <span slot="title">$i18n{advancedOptionsLabel}</span>
+      <span slot="title"></span>
       <div slot="controls">
         <paper-button id="button" disabled$="[[disabled]]"
             on-click="onButtonClick_">
diff --git a/chrome/browser/resources/print_preview/new/app.html b/chrome/browser/resources/print_preview/new/app.html
index 8236c723c..c89d234 100644
--- a/chrome/browser/resources/print_preview/new/app.html
+++ b/chrome/browser/resources/print_preview/new/app.html
@@ -69,20 +69,12 @@
         padding-bottom: 16px;
       }
 
-      #container > *,
-      #container iron-collapse > * {
+      .settings-section {
         display: block;
         margin-bottom: 16px;
         margin-top: 16px;
       }
 
-      #container print-preview-more-settings,
-      #container iron-collapse,
-      #container print-preview-link-container {
-        margin-bottom: 0;
-        margin-top: 0;
-      }
-
       #preview-area-container {
         align-items: center;
         background-color: var(--google-grey-200);
@@ -108,24 +100,24 @@
             invitation-store="[[invitationStore_]]"
             disabled="[[controlsDisabled_]]" state="[[state]]"
             recent-destinations="[[recentDestinations_]]"
-            user-info="{{userInfo_}}" available>
+            user-info="{{userInfo_}}" available class="settings-section">
         </print-preview-destination-settings>
         <print-preview-pages-settings settings="{{settings}}"
             document-info="[[documentInfo_]]"
             disabled="[[controlsDisabled_]]"
-            hidden$="[[!settings.pages.available]]">
+            hidden$="[[!settings.pages.available]]" class="settings-section">
         </print-preview-pages-settings>
         <print-preview-copies-settings settings="{{settings}}"
             disabled="[[controlsDisabled_]]"
-            hidden$="[[!settings.copies.available]]">
+            hidden$="[[!settings.copies.available]]" class="settings-section">
         </print-preview-copies-settings>
         <print-preview-layout-settings settings="{{settings}}"
             disabled="[[controlsDisabled_]]"
-            hidden$="[[!settings.layout.available]]">
+            hidden$="[[!settings.layout.available]]" class="settings-section">
         </print-preview-layout-settings>
         <print-preview-color-settings settings="{{settings}}"
             disabled="[[controlsDisabled_]]"
-            hidden$="[[!settings.color.available]]">
+            hidden$="[[!settings.color.available]]" class="settings-section">
         </print-preview-color-settings>
         <print-preview-more-settings
             settings-expanded-by-user="{{settingsExpandedByUser_}}"
@@ -138,35 +130,41 @@
           <print-preview-media-size-settings settings="{{settings}}"
               capability="[[destination_.capabilities.printer.media_size]]"
               disabled="[[controlsDisabled_]]"
-              hidden$="[[!settings.mediaSize.available]]">
+              hidden$="[[!settings.mediaSize.available]]"
+              class="settings-section">
           </print-preview-media-size-settings>
           <template is="dom-if" if="[[showPagesPerSheet_]]">
             <print-preview-pages-per-sheet-settings settings="{{settings}}"
                 disabled="[[controlsDisabled_]]"
-                hidden$="[[!settings.pagesPerSheet.available]]">
+                hidden$="[[!settings.pagesPerSheet.available]]"
+                class="settings-section">
             </print-preview-pages-per-sheet-settings>
           </template>
           <print-preview-margins-settings settings="{{settings}}"
               disabled="[[controlsDisabled_]]"
-              hidden$="[[!settings.margins.available]]">
+              hidden$="[[!settings.margins.available]]"
+              class="settings-section">
           </print-preview-margins-settings>
           <print-preview-dpi-settings settings="{{settings}}"
               capability="[[destination_.capabilities.printer.dpi]]"
               disabled="[[controlsDisabled_]]"
-              hidden$="[[!settings.dpi.available]]">
+              hidden$="[[!settings.dpi.available]]" class="settings-section">
           </print-preview-dpi-settings>
           <print-preview-scaling-settings settings="{{settings}}"
               document-info="[[documentInfo_]]" disabled="[[controlsDisabled_]]"
-              hidden$="[[!settings.scaling.available]]">
+              hidden$="[[!settings.scaling.available]]"
+              class="settings-section">
           </print-preview-scaling-settings>
           <print-preview-other-options-settings settings="{{settings}}"
               disabled="[[controlsDisabled_]]"
-              hidden$="[[!settings.otherOptions.available]]">
+              hidden$="[[!settings.otherOptions.available]]"
+              class="settings-section">
           </print-preview-other-options-settings>
           <print-preview-advanced-options-settings
               settings="{{settings}}" destination="[[destination_]]"
               disabled="[[controlsDisabled_]]"
-              hidden$="[[!settings.vendorItems.available]]">
+              hidden$="[[!settings.vendorItems.available]]"
+              class="settings-section">
           </print-preview-advanced-options-settings>
         </iron-collapse>
 <if expr="not chromeos">
diff --git a/chrome/browser/resources/print_preview/new/destination_dialog.html b/chrome/browser/resources/print_preview/new/destination_dialog.html
index 93933594..71de939 100644
--- a/chrome/browser/resources/print_preview/new/destination_dialog.html
+++ b/chrome/browser/resources/print_preview/new/destination_dialog.html
@@ -74,16 +74,20 @@
         flex-direction: column;
       }
 
-      /* Height = 3 * destination item (28px) + 10px padding + 1 line text */
+      /* Height = 3 * destination item + 10px padding + 1 line text */
+      #recentList,
+      #printList {
+        min-height:
+            calc(3 * var(--destination-item-height) + 10px + 20 / 13 * 1rem);
+      }
+
       #recentList {
         flex: 0;
-        min-height: calc(94px + 20 / 13 * 1rem);
         padding-bottom: 18px;
       }
 
       #printList {
         flex: 1;
-        min-height: calc(94px + 20 / 13 * 1rem);
         padding-bottom: 0;
       }
 
diff --git a/chrome/browser/resources/print_preview/new/destination_list.html b/chrome/browser/resources/print_preview/new/destination_list.html
index 7a989b3..f86f651 100644
--- a/chrome/browser/resources/print_preview/new/destination_list.html
+++ b/chrome/browser/resources/print_preview/new/destination_list.html
@@ -26,7 +26,7 @@
 
       #listContainer {
         flex: 1;
-        min-height: 84px;
+        min-height: calc(3 * var(--destination-item-height));
       }
 
       .title,
diff --git a/chrome/browser/resources/print_preview/new/destination_list.js b/chrome/browser/resources/print_preview/new/destination_list.js
index 16f8ab4..35effe3 100644
--- a/chrome/browser/resources/print_preview/new/destination_list.js
+++ b/chrome/browser/resources/print_preview/new/destination_list.js
@@ -65,7 +65,7 @@
 
       const entry = assert(entries[0]);
       // Don't set maxHeight below the minimum height.
-      const fullHeight = Math.max(entry.contentRect.height, 84);
+      const fullHeight = Math.max(entry.contentRect.height, 96);
       this.$.list.style.maxHeight = `${fullHeight}px`;
       this.forceIronResize();
     });
diff --git a/chrome/browser/resources/print_preview/new/destination_list_item.html b/chrome/browser/resources/print_preview/new/destination_list_item.html
index 0c1f640..294788b1 100644
--- a/chrome/browser/resources/print_preview/new/destination_list_item.html
+++ b/chrome/browser/resources/print_preview/new/destination_list_item.html
@@ -20,7 +20,7 @@
         cursor: default;
         display: flex;
         font-size: calc(12/13 * 1em);
-        min-height: 28px;
+        min-height: var(--destination-item-height);
         opacity: .87;
         padding-bottom: 2px;
         padding-inline-end: 2px;
diff --git a/chrome/browser/resources/print_preview/new/destination_settings.html b/chrome/browser/resources/print_preview/new/destination_settings.html
index 7d4c271..7b5da3c2 100644
--- a/chrome/browser/resources/print_preview/new/destination_settings.html
+++ b/chrome/browser/resources/print_preview/new/destination_settings.html
@@ -22,13 +22,9 @@
 <dom-module id="print-preview-destination-settings">
   <template>
     <style include="print-preview-shared paper-button-style throbber cr-hidden-style">
-      print-preview-settings-section {
-        margin-top: 16px;
-      }
-
       paper-button {
         margin: 2px;
-        margin-top: 12px;
+        margin-top: 8px;
         width: 89px;
       }
 
@@ -40,7 +36,6 @@
       .throbber-container {
         align-items: center;
         display: flex;
-        min-height: 28px;
         overflow: hidden;
       }
 
@@ -56,16 +51,16 @@
         overflow: hidden;
       }
 
+      .destination-name {
+        line-height: calc(20/13 * 1em);
+      }
+
       .destination-location,
       .destination-connection-status {
         color: var(--google-grey-700);
         font-size: calc(12/13 * 1em);
       }
 
-      .destination-name {
-        margin-bottom: 4px;
-      }
-
       .destination-info-wrapper > div,
       .destination-throbber-name {
         flex: 1;
@@ -78,7 +73,7 @@
         opacity: 0.4;
       }
     </style>
-    <print-preview-settings-section class="multirow-controls">
+    <print-preview-settings-section>
       <span slot="title">$i18n{destinationLabel}</span>
       <div slot="controls">
         <div class="throbber-container" hidden="[[!loadingDestination_]]">
@@ -96,6 +91,11 @@
             </div>
           </div>
         </div>
+      </div>
+    </print-preview-settings-section>
+    <print-preview-settings-section>
+      <div slot="title"></div>
+      <div slot="controls">
         <paper-button
           disabled$="[[shouldDisableButton_(destinationStore, disabled,
                                             state)]]"
diff --git a/chrome/browser/resources/print_preview/new/link_container.html b/chrome/browser/resources/print_preview/new/link_container.html
index db63ae11..061c4f4 100644
--- a/chrome/browser/resources/print_preview/new/link_container.html
+++ b/chrome/browser/resources/print_preview/new/link_container.html
@@ -9,6 +9,14 @@
 <dom-module id="print-preview-link-container">
   <template>
     <style include="print-preview-shared throbber cr-hidden-style">
+      :host paper-icon-button-light {
+        --cr-paper-icon-button-margin: {
+          margin-inline-end: -6px;
+          margin-inline-start: 0;
+        };
+        --cr-icon-size: 16px;
+      }
+
       .link:not([actionable]) {
         pointer-events: none;
       }
diff --git a/chrome/browser/resources/print_preview/new/pages_settings.html b/chrome/browser/resources/print_preview/new/pages_settings.html
index 1903cc45..e41aeab9 100644
--- a/chrome/browser/resources/print_preview/new/pages_settings.html
+++ b/chrome/browser/resources/print_preview/new/pages_settings.html
@@ -35,10 +35,14 @@
         cursor: default;
         height: 100%;
       }
+
+      :host #title {
+        justify-content: flex-start;
+        padding-top: 9px;
+      }
     </style>
-    <print-preview-settings-section
-        class="input-settings-section multirow-controls">
-      <span slot="title">$i18n{pagesLabel}</span>
+    <print-preview-settings-section>
+      <span id="title" slot="title">$i18n{pagesLabel}</span>
       <div slot="controls" id="controls">
         <paper-radio-group selectable="cr-radio-button" class="radio"
             disabled$="[[controlsDisabled_]]"
diff --git a/chrome/browser/resources/print_preview/new/print_preview_shared_css.html b/chrome/browser/resources/print_preview/new/print_preview_shared_css.html
index 44495d0..a1d893cc 100644
--- a/chrome/browser/resources/print_preview/new/print_preview_shared_css.html
+++ b/chrome/browser/resources/print_preview/new/print_preview_shared_css.html
@@ -17,6 +17,7 @@
         --print-preview-dialog-margin: 34px;
         --cr-form-field-label-height: 1.5rem;
         --cr-form-field-label-line-height: .75rem;
+        --destination-item-height: 32px;
       }
 
       /* Default state ********************************************************/
diff --git a/chrome/browser/resources/print_preview/new/settings_section.html b/chrome/browser/resources/print_preview/new/settings_section.html
index 475ce1b..a01102f 100644
--- a/chrome/browser/resources/print_preview/new/settings_section.html
+++ b/chrome/browser/resources/print_preview/new/settings_section.html
@@ -34,16 +34,12 @@
         color: var(--google-grey-900);
         flex: none;
         font-size: 1em;
+        line-height: calc(20/13 * 1em);
         padding-inline-end: var(--policy-icon-padding);
         width: calc(75px - var(--policy-icon-padding));
         word-break: break-word;
       }
 
-      :host(.multirow-controls) ::slotted([slot=title]) {
-        justify-content: flex-start;
-        padding-top: 10px;
-      }
-
       :host([managed]:not([show-policy-on-end])) ::slotted([slot=title]) {
         width: calc(75px - var(--policy-icon-size)
                     - var(--policy-icon-padding));
diff --git a/chrome/browser/signin/chrome_signin_proxying_url_loader_factory.cc b/chrome/browser/signin/chrome_signin_proxying_url_loader_factory.cc
index d6215cc..e44c4264 100644
--- a/chrome/browser/signin/chrome_signin_proxying_url_loader_factory.cc
+++ b/chrome/browser/signin/chrome_signin_proxying_url_loader_factory.cc
@@ -452,10 +452,8 @@
 
   // This proxy should only be installed for subresource requests from a frame
   // that is rendering the GAIA signon realm.
-  if (!render_frame_host ||
-      !gaia::IsGaiaSignonRealm(request_initiator.GetURL())) {
+  if (!gaia::IsGaiaSignonRealm(request_initiator.GetURL()))
     return false;
-  }
 
   auto* web_contents =
       content::WebContents::FromRenderFrameHost(render_frame_host);
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 8d42b00..a9bbebb 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1400,8 +1400,6 @@
       "ash/ash_shell_init.h",
       "ash/ash_util.cc",
       "ash/ash_util.h",
-      "ash/browser_image_registrar.cc",
-      "ash/browser_image_registrar.h",
       "ash/cast_config_client_media_router.cc",
       "ash/cast_config_client_media_router.h",
       "ash/chrome_accessibility_delegate.cc",
diff --git a/chrome/browser/ui/app_list/app_list_syncable_service.cc b/chrome/browser/ui/app_list/app_list_syncable_service.cc
index 599d0477..5c30dfd 100644
--- a/chrome/browser/ui/app_list/app_list_syncable_service.cc
+++ b/chrome/browser/ui/app_list/app_list_syncable_service.cc
@@ -396,7 +396,7 @@
   apps_builder_ = std::make_unique<ExtensionAppModelBuilder>(controller);
   if (arc::IsArcAllowedForProfile(profile_))
     arc_apps_builder_ = std::make_unique<ArcAppModelBuilder>(controller);
-  if (IsCrostiniUIAllowedForProfile(profile_)) {
+  if (crostini::IsCrostiniUIAllowedForProfile(profile_)) {
     crostini_apps_builder_ =
         std::make_unique<CrostiniAppModelBuilder>(controller);
   }
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 359e479..7f01f005 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
@@ -23,7 +23,7 @@
 void CrostiniAppContextMenu::BuildMenu(ui::SimpleMenuModel* menu_model) {
   app_list::AppContextMenu::BuildMenu(menu_model);
 
-  if (app_id() == kCrostiniTerminalId) {
+  if (app_id() == crostini::kCrostiniTerminalId) {
     if (!features::IsTouchableAppContextMenuEnabled())
       menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
 
@@ -36,12 +36,12 @@
 
 bool CrostiniAppContextMenu::IsCommandIdEnabled(int command_id) const {
   if (command_id == ash::UNINSTALL) {
-    if (app_id() == kCrostiniTerminalId) {
-      return IsCrostiniEnabled(profile());
+    if (app_id() == crostini::kCrostiniTerminalId) {
+      return crostini::IsCrostiniEnabled(profile());
     }
   } else if (command_id == ash::MENU_CLOSE) {
-    if (app_id() == kCrostiniTerminalId) {
-      return IsCrostiniRunning(profile());
+    if (app_id() == crostini::kCrostiniTerminalId) {
+      return crostini::IsCrostiniRunning(profile());
     }
   }
   return app_list::AppContextMenu::IsCommandIdEnabled(command_id);
@@ -50,16 +50,17 @@
 void CrostiniAppContextMenu::ExecuteCommand(int command_id, int event_flags) {
   switch (command_id) {
     case ash::UNINSTALL:
-      if (app_id() == kCrostiniTerminalId) {
-        ShowCrostiniUninstallerView(profile(), CrostiniUISurface::kAppList);
+      if (app_id() == crostini::kCrostiniTerminalId) {
+        crostini::ShowCrostiniUninstallerView(
+            profile(), crostini::CrostiniUISurface::kAppList);
         return;
       }
       break;
 
     case ash::MENU_CLOSE:
-      if (app_id() == kCrostiniTerminalId) {
+      if (app_id() == crostini::kCrostiniTerminalId) {
         crostini::CrostiniManager::GetForProfile(profile())->StopVm(
-            kCrostiniDefaultVmName, base::DoNothing());
+            crostini::kCrostiniDefaultVmName, base::DoNothing());
         return;
       }
       break;
diff --git a/chrome/browser/ui/app_list/crostini/crostini_app_icon.cc b/chrome/browser/ui/app_list/crostini/crostini_app_icon.cc
index e2d09411..fc7e1d87 100644
--- a/chrome/browser/ui/app_list/crostini/crostini_app_icon.cc
+++ b/chrome/browser/ui/app_list/crostini/crostini_app_icon.cc
@@ -100,7 +100,7 @@
 
   // Host loads icon asynchronously, so use default icon so far.
   int resource_id;
-  if (host_ && host_->app_id() == kCrostiniTerminalId) {
+  if (host_ && host_->app_id() == crostini::kCrostiniTerminalId) {
     // Don't initiate the icon request from the container because we have this
     // one already.
     resource_id = IDR_LOGO_CROSTINI_TERMINAL;
diff --git a/chrome/browser/ui/app_list/crostini/crostini_app_item.cc b/chrome/browser/ui/app_list/crostini/crostini_app_item.cc
index 972fda1..96441c5 100644
--- a/chrome/browser/ui/app_list/crostini/crostini_app_item.cc
+++ b/chrome/browser/ui/app_list/crostini/crostini_app_item.cc
@@ -38,7 +38,7 @@
 
     // Crostini app is created from scratch. Move it to default folder.
     DCHECK(folder_id().empty());
-    SetChromeFolderId(kCrostiniFolderId);
+    SetChromeFolderId(crostini::kCrostiniFolderId);
   }
 
   // Set model updater last to avoid being called during construction.
diff --git a/chrome/browser/ui/app_list/crostini/crostini_app_model_builder.cc b/chrome/browser/ui/app_list/crostini/crostini_app_model_builder.cc
index 1999fed..2f982a4 100644
--- a/chrome/browser/ui/app_list/crostini/crostini_app_model_builder.cc
+++ b/chrome/browser/ui/app_list/crostini/crostini_app_model_builder.cc
@@ -44,7 +44,8 @@
 void CrostiniAppModelBuilder::InsertCrostiniAppItem(
     const crostini::CrostiniRegistryService* registry_service,
     const std::string& app_id) {
-  if (app_id == kCrostiniTerminalId && !IsCrostiniEnabled(profile())) {
+  if (app_id == crostini::kCrostiniTerminalId &&
+      !crostini::IsCrostiniEnabled(profile())) {
     // If Crostini isn't enabled, don't show the Terminal item until it
     // becomes enabled.
     return;
@@ -107,15 +108,15 @@
 
 void CrostiniAppModelBuilder::OnCrostiniEnabledChanged() {
   const bool unsynced_change = false;
-  if (IsCrostiniEnabled(profile())) {
+  if (crostini::IsCrostiniEnabled(profile())) {
     // If Terminal has been installed before and has not been cleaned up
     // correctly, it needs to be removed.
-    RemoveApp(kCrostiniTerminalId, unsynced_change);
+    RemoveApp(crostini::kCrostiniTerminalId, unsynced_change);
     crostini::CrostiniRegistryService* registry_service =
         crostini::CrostiniRegistryServiceFactory::GetForProfile(profile());
-    InsertCrostiniAppItem(registry_service, kCrostiniTerminalId);
+    InsertCrostiniAppItem(registry_service, crostini::kCrostiniTerminalId);
   } else {
-    RemoveApp(kCrostiniTerminalId, unsynced_change);
+    RemoveApp(crostini::kCrostiniTerminalId, unsynced_change);
   }
 }
 
@@ -125,13 +126,13 @@
 
   root_folder_created_ = true;
   const app_list::AppListSyncableService::SyncItem* sync_item =
-      GetSyncItem(kCrostiniFolderId);
+      GetSyncItem(crostini::kCrostiniFolderId);
   if (sync_item)
     return;
 
   std::unique_ptr<ChromeAppListItem> crositini_folder =
-      std::make_unique<ChromeAppListItem>(profile(), kCrostiniFolderId,
-                                          model_updater());
+      std::make_unique<ChromeAppListItem>(
+          profile(), crostini::kCrostiniFolderId, model_updater());
   crositini_folder->SetChromeIsFolder(true);
   crositini_folder->SetName(
       l10n_util::GetStringUTF8(IDS_APP_LIST_CROSTINI_DEFAULT_FOLDER_NAME));
diff --git a/chrome/browser/ui/app_list/crostini/crostini_app_model_builder_unittest.cc b/chrome/browser/ui/app_list/crostini/crostini_app_model_builder_unittest.cc
index 9f8606d8..9a6b96d 100644
--- a/chrome/browser/ui/app_list/crostini/crostini_app_model_builder_unittest.cc
+++ b/chrome/browser/ui/app_list/crostini/crostini_app_model_builder_unittest.cc
@@ -73,7 +73,7 @@
 std::vector<std::string> AppendRootFolderId(
     const std::vector<std::string> ids) {
   std::vector<std::string> result = ids;
-  result.emplace_back(kCrostiniFolderId);
+  result.emplace_back(crostini::kCrostiniFolderId);
   return result;
 }
 
@@ -126,18 +126,18 @@
 
 // Test that the Terminal app is only shown when Crostini is enabled
 TEST_F(CrostiniAppModelBuilderTest, EnableCrostini) {
-  SetCrostiniUIAllowedForTesting(true);
+  crostini::SetCrostiniUIAllowedForTesting(true);
   EXPECT_EQ(0u, model_updater_->ItemCount());
 
   CrostiniTestHelper::EnableCrostini(profile());
   // Root folder + terminal app.
-  EXPECT_THAT(
-      GetAppIds(model_updater_.get()),
-      testing::UnorderedElementsAre(kCrostiniFolderId, kCrostiniTerminalId));
+  EXPECT_THAT(GetAppIds(model_updater_.get()),
+              testing::UnorderedElementsAre(crostini::kCrostiniFolderId,
+                                            crostini::kCrostiniTerminalId));
   EXPECT_THAT(GetAppNames(model_updater_.get()),
               testing::UnorderedElementsAre(kRootFolderName,
                                             GetFullName(TerminalAppName())));
-  SetCrostiniUIAllowedForTesting(false);
+  crostini::SetCrostiniUIAllowedForTesting(false);
 }
 
 TEST_F(CrostiniAppModelBuilderTest, AppInstallation) {
@@ -180,7 +180,7 @@
   EXPECT_EQ(3u, model_updater_->ItemCount());
   EXPECT_THAT(GetAppIds(model_updater_.get()),
               testing::UnorderedElementsAre(
-                  kCrostiniFolderId, kCrostiniTerminalId,
+                  crostini::kCrostiniFolderId, crostini::kCrostiniTerminalId,
                   CrostiniTestHelper::GenerateAppId(kDummpyApp2Id)));
 
   // Setting NoDisplay to false should unhide an app.
@@ -230,8 +230,9 @@
 
   // The uninstall flow removes all apps before setting the CrostiniEnabled pref
   // to false, so we need to do that explicitly too.
-  RegistryService()->ClearApplicationList(kCrostiniDefaultVmName,
-                                          kCrostiniDefaultContainerName);
+  RegistryService()->ClearApplicationList(
+      crostini::kCrostiniDefaultVmName,
+      crostini::kCrostiniDefaultContainerName);
   CrostiniTestHelper::DisableCrostini(profile());
   // Root folder is left. We rely on default handling of empty folder.
   EXPECT_EQ(1u, model_updater_->ItemCount());
diff --git a/chrome/browser/ui/app_list/search/app_search_provider.cc b/chrome/browser/ui/app_list/search/app_search_provider.cc
index 3dc210f..2cea38b 100644
--- a/chrome/browser/ui/app_list/search/app_search_provider.cc
+++ b/chrome/browser/ui/app_list/search/app_search_provider.cc
@@ -475,7 +475,8 @@
 
       // Until it's been installed, the Terminal is hidden unless you search
       // for 'Terminal' exactly (case insensitive).
-      if (app_id == kCrostiniTerminalId && !IsCrostiniEnabled(profile())) {
+      if (app_id == crostini::kCrostiniTerminalId &&
+          !crostini::IsCrostiniEnabled(profile())) {
         apps->back()->set_recommendable(false);
         apps->back()->set_require_exact_match(true);
       }
@@ -521,7 +522,7 @@
       std::make_unique<ExtensionDataSource>(profile, this));
   if (arc::IsArcAllowedForProfile(profile))
     data_sources_.emplace_back(std::make_unique<ArcDataSource>(profile, this));
-  if (IsCrostiniUIAllowedForProfile(profile)) {
+  if (crostini::IsCrostiniUIAllowedForProfile(profile)) {
     data_sources_.emplace_back(
         std::make_unique<CrostiniDataSource>(profile, this));
   }
diff --git a/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc b/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc
index 6ec094a7..bef3dee6 100644
--- a/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc
+++ b/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc
@@ -45,6 +45,8 @@
   void SetSelectToSpeakState(ash::mojom::SelectToSpeakState state) override {}
   void SetSelectToSpeakEventHandlerDelegate(
       ash::mojom::SelectToSpeakEventHandlerDelegatePtr delegate) override {}
+  void ToggleDictationFromSource(
+      ash::mojom::DictationToggleSource source) override {}
 
   bool was_client_set() const { return was_client_set_; }
 
diff --git a/chrome/browser/ui/ash/browser_image_registrar.cc b/chrome/browser/ui/ash/browser_image_registrar.cc
deleted file mode 100644
index 5b23fa2..0000000
--- a/chrome/browser/ui/ash/browser_image_registrar.cc
+++ /dev/null
@@ -1,148 +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 "chrome/browser/ui/ash/browser_image_registrar.h"
-
-#include <map>
-
-#include "ash/public/interfaces/client_image_registry.mojom.h"
-#include "ash/public/interfaces/constants.mojom.h"
-#include "base/macros.h"
-#include "content/public/common/service_manager_connection.h"
-#include "services/service_manager/public/cpp/connector.h"
-
-namespace {
-
-class BrowserImageRegistrarImpl {
- public:
-  BrowserImageRegistrarImpl();
-  ~BrowserImageRegistrarImpl();
-
-  scoped_refptr<ImageRegistration> RegisterImage(const gfx::ImageSkia& image);
-  void ForgetImage(const base::UnguessableToken& token);
-
-  const std::map<base::UnguessableToken, const void*>& tokens() const {
-    return tokens_;
-  }
-  const std::map<const void*, ImageRegistration*>& images() const {
-    return images_;
-  }
-
-  // Initializes the singleton instance, if necessary. This will only init
-  // |g_registrar| one time; after that it's a no-op.
-  static void InitOnce();
-
- private:
-  // The void* in both of the maps is the object that backs the associated
-  // ImageSkia. This pointer is guaranteed to remain valid as long as an
-  // ImageSkia references it, which is guaranteed by the existence of the
-  // ImageRegistration.
-  std::map<base::UnguessableToken, const void*> tokens_;
-
-  // The ImageRegistration pointers here are weak. When the object is destroyed,
-  // it will ask to be removed from this map.
-  std::map<const void*, ImageRegistration*> images_;
-
-  // The connection to Ash, which may be null in tests.
-  ash::mojom::ClientImageRegistryPtr registry_;
-
-  DISALLOW_COPY_AND_ASSIGN(BrowserImageRegistrarImpl);
-};
-
-BrowserImageRegistrarImpl* g_registrar = nullptr;
-
-BrowserImageRegistrarImpl::BrowserImageRegistrarImpl() {
-  auto* connection = content::ServiceManagerConnection::GetForProcess();
-  if (connection) {
-    connection->GetConnector()->BindInterface(ash::mojom::kServiceName,
-                                              &registry_);
-  }
-}
-
-BrowserImageRegistrarImpl::~BrowserImageRegistrarImpl() {
-  DCHECK(images_.empty());
-  DCHECK(tokens_.empty());
-}
-
-scoped_refptr<ImageRegistration> BrowserImageRegistrarImpl::RegisterImage(
-    const gfx::ImageSkia& image) {
-  auto iter = images_.find(image.GetBackingObject());
-  if (iter != images_.end())
-    return base::WrapRefCounted(iter->second);
-
-  // Keep a local record.
-  auto token = base::UnguessableToken::Create();
-  tokens_[token] = image.GetBackingObject();
-  auto refcounted = base::MakeRefCounted<ImageRegistration>(token, image);
-  images_.insert(std::make_pair(image.GetBackingObject(), refcounted.get()));
-
-  // Register with Ash.
-  if (registry_)
-    registry_->RegisterImage(token, image);
-
-  return refcounted;
-}
-
-void BrowserImageRegistrarImpl::ForgetImage(
-    const base::UnguessableToken& token) {
-  auto iter = tokens_.find(token);
-  DCHECK(iter != tokens_.end());
-
-  images_.erase(iter->second);
-  tokens_.erase(iter);
-
-  // Un-register with Ash.
-  if (registry_)
-    registry_->ForgetImage(token);
-}
-
-// static
-void BrowserImageRegistrarImpl::InitOnce() {
-  static bool registrar_initialized = false;
-  if (!registrar_initialized) {
-    DCHECK(!g_registrar);
-    g_registrar = new BrowserImageRegistrarImpl();
-    registrar_initialized = true;
-  }
-
-  DCHECK(g_registrar);
-}
-
-}  // namespace
-
-// static
-void BrowserImageRegistrar::Shutdown() {
-  delete g_registrar;
-  g_registrar = nullptr;
-}
-
-ImageRegistration::ImageRegistration(const base::UnguessableToken& token,
-                                     const gfx::ImageSkia& image)
-    : token_(token), image_(image) {}
-
-ImageRegistration::~ImageRegistration() {
-  DCHECK(g_registrar);
-  g_registrar->ForgetImage(token_);
-}
-
-// static
-scoped_refptr<ImageRegistration> BrowserImageRegistrar::RegisterImage(
-    const gfx::ImageSkia& image) {
-  BrowserImageRegistrarImpl::InitOnce();
-  return g_registrar->RegisterImage(image);
-}
-
-// static
-std::vector<ImageRegistration*>
-BrowserImageRegistrar::GetActiveRegistrationsForTesting() {
-  BrowserImageRegistrarImpl::InitOnce();
-
-  DCHECK_EQ(g_registrar->images().size(), g_registrar->tokens().size());
-  std::vector<ImageRegistration*> registrations;
-  for (auto iter : g_registrar->images()) {
-    registrations.push_back(iter.second);
-    DCHECK_EQ(1U, g_registrar->tokens().count(registrations.back()->token()));
-  }
-  return registrations;
-}
diff --git a/chrome/browser/ui/ash/browser_image_registrar.h b/chrome/browser/ui/ash/browser_image_registrar.h
deleted file mode 100644
index 16b6455..0000000
--- a/chrome/browser/ui/ash/browser_image_registrar.h
+++ /dev/null
@@ -1,60 +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 CHROME_BROWSER_UI_ASH_BROWSER_IMAGE_REGISTRAR_H_
-#define CHROME_BROWSER_UI_ASH_BROWSER_IMAGE_REGISTRAR_H_
-
-#include "base/macros.h"
-
-#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "base/unguessable_token.h"
-#include "ui/gfx/image/image_skia.h"
-
-// This class represents an image that's been registered with Ash. It is ref
-// counted so that when multiple callsites want to use the same image, they can
-// all hold a reference, and when they all release the reference the
-// registration will destruct.
-class ImageRegistration : public base::RefCounted<ImageRegistration> {
- public:
-  ImageRegistration(const base::UnguessableToken& token,
-                    const gfx::ImageSkia& image);
-
-  const base::UnguessableToken& token() const { return token_; }
-
- protected:
-  friend class base::RefCounted<ImageRegistration>;
-
-  virtual ~ImageRegistration();
-
- private:
-  const base::UnguessableToken token_;
-  const gfx::ImageSkia image_;
-
-  DISALLOW_COPY_AND_ASSIGN(ImageRegistration);
-};
-
-// A collection of functions to register and unregister images with Ash. This is
-// used in Mash to minimize the duplication of images sent over mojo.
-class BrowserImageRegistrar {
- public:
-  // Must be called once when the browser process is exiting.
-  static void Shutdown();
-
-  // Gets or creates a registration for the given image. This registers the
-  // image and token with Ash. The caller should hold onto the returned
-  // object as long as the image is in use. When all refs to a given
-  // registration are released, Ash will be informed and the associated token
-  // will no longer be useful. This function also serves as a way to lazily
-  // initialize the implementation object.
-  static scoped_refptr<ImageRegistration> RegisterImage(
-      const gfx::ImageSkia& image) WARN_UNUSED_RESULT;
-
-  static std::vector<ImageRegistration*> GetActiveRegistrationsForTesting();
-
- private:
-  DISALLOW_IMPLICIT_CONSTRUCTORS(BrowserImageRegistrar);
-};
-
-#endif  // CHROME_BROWSER_UI_ASH_BROWSER_IMAGE_REGISTRAR_H_
diff --git a/chrome/browser/ui/ash/browser_image_registrar_unittest.cc b/chrome/browser/ui/ash/browser_image_registrar_unittest.cc
deleted file mode 100644
index 81c5459..0000000
--- a/chrome/browser/ui/ash/browser_image_registrar_unittest.cc
+++ /dev/null
@@ -1,62 +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 "chrome/browser/ui/ash/browser_image_registrar.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/gfx/image/image_unittest_util.h"
-
-namespace {
-
-using BrowserImageRegistrarTest = testing::Test;
-
-TEST_F(BrowserImageRegistrarTest, Basic) {
-  auto GetRegistrations =
-      &BrowserImageRegistrar::GetActiveRegistrationsForTesting;
-
-  EXPECT_EQ(0U, GetRegistrations().size());
-
-  // Register an image.
-  gfx::ImageSkia image1 = gfx::test::CreateImageSkia(15, 20);
-  scoped_refptr<ImageRegistration> registration1 =
-      BrowserImageRegistrar::RegisterImage(image1);
-  EXPECT_EQ(1U, GetRegistrations().size());
-
-  // Register a different image.
-  gfx::ImageSkia image2 = gfx::test::CreateImageSkia(15, 20);
-  EXPECT_NE(image2.GetBackingObject(), image1.GetBackingObject());
-  scoped_refptr<ImageRegistration> registration2 =
-      BrowserImageRegistrar::RegisterImage(image2);
-  EXPECT_EQ(2U, GetRegistrations().size());
-  EXPECT_NE(registration2->token(), registration1->token());
-
-  // Register an image that's a copy of one which is already registered.
-  gfx::ImageSkia image3 = image1;
-  EXPECT_EQ(image3.GetBackingObject(), image1.GetBackingObject());
-  scoped_refptr<ImageRegistration> registration3 =
-      BrowserImageRegistrar::RegisterImage(image3);
-  EXPECT_EQ(2U, GetRegistrations().size());
-  EXPECT_EQ(registration3->token(), registration1->token());
-
-  // Get rid of one reference to the twice-registered image and verify the
-  // registration is still there.
-  registration1 = nullptr;
-  EXPECT_EQ(2U, GetRegistrations().size());
-
-  registration3 = nullptr;
-  EXPECT_EQ(1U, GetRegistrations().size());
-
-  // Verify that resetting an image that was registered, thus dropping the
-  // backing store's refcount, before releasing the registration ref, won't
-  // cause a crash or any change in the registration list.
-  image2 = gfx::ImageSkia();
-  EXPECT_EQ(1U, GetRegistrations().size());
-
-  registration2 = nullptr;
-  EXPECT_EQ(0U, GetRegistrations().size());
-
-  BrowserImageRegistrar::Shutdown();
-}
-
-}  // namespace
diff --git a/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.cc b/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.cc
index f7774c8f..5adb58f 100644
--- a/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.cc
+++ b/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.cc
@@ -357,7 +357,7 @@
     // Crostini Terminals always have their own item.
     // TODO(rjwright): We shouldn't need to special-case Crostini here.
     // https://crbug.com/846546
-    if (CrostiniAppIdFromAppName(browser->app_name()))
+    if (crostini::CrostiniAppIdFromAppName(browser->app_name()))
       return false;
 
     // V1 App popup windows may have their own item.
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
index e53289312..657bb19 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller.cc
@@ -250,7 +250,7 @@
   app_window_controllers_.push_back(std::move(extension_app_window_controller));
   app_window_controllers_.push_back(
       std::make_unique<ArcAppWindowLauncherController>(this));
-  if (IsCrostiniUIAllowedForProfile(profile)) {
+  if (crostini::IsCrostiniUIAllowedForProfile(profile)) {
     std::unique_ptr<CrostiniAppWindowShelfController> crostini_controller =
         std::make_unique<CrostiniAppWindowShelfController>(this);
     crostini_app_window_shelf_controller_ = crostini_controller.get();
@@ -1143,7 +1143,7 @@
           profile_, extension_misc::EXTENSION_ICON_MEDIUM, this);
   app_icon_loaders_.push_back(std::move(internal_app_icon_loader));
 
-  if (IsCrostiniUIAllowedForProfile(profile_)) {
+  if (crostini::IsCrostiniUIAllowedForProfile(profile_)) {
     std::unique_ptr<AppIconLoader> crostini_app_icon_loader =
         std::make_unique<CrostiniAppIconLoader>(
             profile_, extension_misc::EXTENSION_ICON_MEDIUM, this);
@@ -1177,7 +1177,7 @@
     app_updaters_.push_back(std::move(arc_app_updater));
   }
 
-  if (IsCrostiniUIAllowedForProfile(profile())) {
+  if (crostini::IsCrostiniUIAllowedForProfile(profile())) {
     std::unique_ptr<LauncherAppUpdater> crostini_app_updater(
         new LauncherCrostiniAppUpdater(this, profile()));
     app_updaters_.push_back(std::move(crostini_app_updater));
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
index 648127e9..ae700a0 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
@@ -921,7 +921,7 @@
             result += "Platform_App";
           } else if (app == arc_support_host_->id()) {
             result += "Play Store";
-          } else if (app == kCrostiniTerminalId) {
+          } else if (app == crostini::kCrostiniTerminalId) {
             result += "Terminal";
           } else {
             bool arc_app_found = false;
@@ -4638,7 +4638,7 @@
 TEST_F(ChromeLauncherControllerTest, CrostiniTerminalPinUnpin) {
   InitLauncherController();
 
-  const std::string app_id = kCrostiniTerminalId;
+  const std::string app_id = crostini::kCrostiniTerminalId;
   EXPECT_FALSE(launcher_controller_->IsAppPinned(app_id));
 
   // Load pinned Terminal from prefs without Crostini UI being allowed
diff --git a/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc b/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc
index 6d8cf08..53005b20 100644
--- a/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc
+++ b/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc
@@ -165,7 +165,7 @@
   Browser* browser = chrome::FindBrowserWithWindow(window);
   if (browser) {
     base::Optional<std::string> app_id =
-        CrostiniAppIdFromAppName(browser->app_name());
+        crostini::CrostiniAppIdFromAppName(browser->app_name());
     if (!app_id)
       return;
     RegisterAppWindow(window, app_id.value());
diff --git a/chrome/browser/ui/ash/launcher/crostini_shelf_context_menu.cc b/chrome/browser/ui/ash/launcher/crostini_shelf_context_menu.cc
index 2c6dfed7..abf0117 100644
--- a/chrome/browser/ui/ash/launcher/crostini_shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/launcher/crostini_shelf_context_menu.cc
@@ -59,7 +59,8 @@
     return;
 
   if (command_id == ash::MENU_NEW_WINDOW) {
-    LaunchCrostiniApp(controller()->profile(), item().id.app_id, display_id());
+    crostini::LaunchCrostiniApp(controller()->profile(), item().id.app_id,
+                                display_id());
     return;
   }
   NOTREACHED();
diff --git a/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc b/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
index 216526b..e728018 100644
--- a/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
+++ b/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
@@ -174,7 +174,7 @@
   crostini::CrostiniRegistryService* registry_service =
       crostini::CrostiniRegistryServiceFactory::GetForProfile(profile_);
   if (registry_service && registry_service->IsCrostiniShelfAppId(id)) {
-    return IsCrostiniUIAllowedForProfile(profile_) &&
+    return crostini::IsCrostiniUIAllowedForProfile(profile_) &&
            registry_service->GetRegistration(id).has_value();
   }
 
@@ -218,7 +218,7 @@
     // This expects a valid app list id, which is fine as we only get here for
     // shelf entries associated with an actual app and not arbitrary Crostini
     // windows.
-    LaunchCrostiniApp(profile_, app_id, display_id);
+    crostini::LaunchCrostiniApp(profile_, app_id, display_id);
     return;
   }
 
diff --git a/chrome/browser/ui/cocoa/main_menu_builder.h b/chrome/browser/ui/cocoa/main_menu_builder.h
index c399457..03a1837 100644
--- a/chrome/browser/ui/cocoa/main_menu_builder.h
+++ b/chrome/browser/ui/cocoa/main_menu_builder.h
@@ -93,6 +93,9 @@
   // the one specified here is used instead.
   MenuItemBuilder& key_equivalent(NSString* key_equivalent,
                                   NSEventModifierFlags flags) {
+    DCHECK((flags & NSEventModifierFlagShift) == 0)
+        << "The shift modifier flag should be directly applied to the key "
+           "equivalent.";
     key_equivalent_ = key_equivalent;
     key_equivalent_flags_ = flags;
     return *this;
diff --git a/chrome/browser/ui/cocoa/main_menu_builder.mm b/chrome/browser/ui/cocoa/main_menu_builder.mm
index 5e5167e..740e15d4 100644
--- a/chrome/browser/ui/cocoa/main_menu_builder.mm
+++ b/chrome/browser/ui/cocoa/main_menu_builder.mm
@@ -141,8 +141,7 @@
                 Item(IDS_PASTE_MATCH_STYLE_MAC)
                     .action(@selector(pasteAndMatchStyle:))
                     .is_alternate()
-                    .key_equivalent(@"v", NSEventModifierFlagCommand |
-                                              NSEventModifierFlagShift |
+                    .key_equivalent(@"V", NSEventModifierFlagCommand |
                                               NSEventModifierFlagOption),
                 Item(IDS_EDIT_DELETE_MAC)
                     .tag(IDC_CONTENT_CONTEXT_DELETE)
diff --git a/chrome/browser/ui/cocoa/nsmenuitem_additions.h b/chrome/browser/ui/cocoa/nsmenuitem_additions.h
index 05129d4..5544e2ab0 100644
--- a/chrome/browser/ui/cocoa/nsmenuitem_additions.h
+++ b/chrome/browser/ui/cocoa/nsmenuitem_additions.h
@@ -19,5 +19,6 @@
 // Used by tests to set internal state without having to change global input
 // source.
 void SetIsInputSourceDvorakQwertyForTesting(bool is_dvorak_qwerty);
+void SetIsInputSourceCzechForTesting(bool is_czech);
 
 #endif  // CHROME_BROWSER_UI_COCOA_NSMENUITEM_ADDITIONS_H_
diff --git a/chrome/browser/ui/cocoa/nsmenuitem_additions.mm b/chrome/browser/ui/cocoa/nsmenuitem_additions.mm
index d2a667b..92bba46 100644
--- a/chrome/browser/ui/cocoa/nsmenuitem_additions.mm
+++ b/chrome/browser/ui/cocoa/nsmenuitem_additions.mm
@@ -12,12 +12,17 @@
 
 namespace {
 bool g_is_input_source_dvorak_qwerty = false;
+bool g_is_input_source_czech = false;
 }  // namespace
 
 void SetIsInputSourceDvorakQwertyForTesting(bool is_dvorak_qwerty) {
   g_is_input_source_dvorak_qwerty = is_dvorak_qwerty;
 }
 
+void SetIsInputSourceCzechForTesting(bool is_czech) {
+  g_is_input_source_czech = is_czech;
+}
+
 @interface KeyboardInputSourceListener : NSObject
 @end
 
@@ -47,6 +52,9 @@
       inputSource.get(), kTISPropertyInputSourceID);
   g_is_input_source_dvorak_qwerty =
       [inputSourceID isEqualToString:@"com.apple.keylayout.DVORAK-QWERTYCMD"];
+  g_is_input_source_czech =
+      [inputSourceID rangeOfString:@"com.apple.keylayout.Czech"].location !=
+      NSNotFound;
 }
 
 - (void)inputSourceDidChange:(NSNotification*)notification {
@@ -78,6 +86,23 @@
   NSUInteger eventModifiers =
       [event modifierFlags] & NSDeviceIndependentModifierFlagsMask;
 
+  // cmd-opt-a gives some weird char as characters and "a" as
+  // charactersWithoutModifiers with an US layout, but an "a" as characters and
+  // a weird char as "charactersWithoutModifiers" with a cyrillic layout. Oh,
+  // Cocoa! Instead of getting the current layout from Text Input Services,
+  // and then requesting the kTISPropertyUnicodeKeyLayoutData and looking in
+  // there, let's try a pragmatic hack.
+  if ([eventString length] == 0 ||
+      ([eventString characterAtIndex:0] > 0x7f &&
+       [[event characters] length] > 0 &&
+       [[event characters] characterAtIndex:0] <= 0x7f)) {
+    eventString = [event characters];
+
+    // Process the shift if necessary.
+    if (eventModifiers & NSShiftKeyMask)
+      eventString = [eventString uppercaseString];
+  }
+
   if ([eventString length] == 0 || [[self keyEquivalent] length] == 0)
     return NO;
 
@@ -110,22 +135,6 @@
     eventModifiers |= NSFunctionKeyMask;
   }
 
-  // cmd-opt-a gives some weird char as characters and "a" as
-  // charactersWithoutModifiers with an US layout, but an "a" as characters and
-  // a weird char as "charactersWithoutModifiers" with a cyrillic layout. Oh,
-  // Cocoa! Instead of getting the current layout from Text Input Services,
-  // and then requesting the kTISPropertyUnicodeKeyLayoutData and looking in
-  // there, let's try a pragmatic hack.
-  if ([eventString characterAtIndex:0] > 0x7f &&
-      [[event characters] length] > 0 &&
-      [[event characters] characterAtIndex:0] <= 0x7f) {
-    eventString = [event characters];
-
-    // Process the shift if necessary.
-    if (eventModifiers & NSShiftKeyMask)
-      eventString = [eventString uppercaseString];
-  }
-
   // We intentionally leak this object.
   static __attribute__((unused)) KeyboardInputSourceListener* listener =
       [[KeyboardInputSourceListener alloc] init];
@@ -165,6 +174,17 @@
                     NSAlternateKeyMask |
                     NSShiftKeyMask;
 
+  // On Czech keyboards, we want to interpret cmd + '+' as cmd + '1'.
+  // htts://crbug.com/889424. We don't need special handling for other numeric
+  // keys because they produce non-ASCII characters, and we already have logic
+  // that ignores non-ASCII characters in favor of modified characters.
+  if (g_is_input_source_czech) {
+    if (eventModifiers == NSCommandKeyMask &&
+        [eventString isEqualToString:@"+"]) {
+      eventString = @"1";
+    }
+  }
+
   return [eventString isEqualToString:[self keyEquivalent]]
       && eventModifiers == [self keyEquivalentModifierMask];
 }
diff --git a/chrome/browser/ui/cocoa/nsmenuitem_additions_unittest.mm b/chrome/browser/ui/cocoa/nsmenuitem_additions_unittest.mm
index 5bd4a29..2060d3d 100644
--- a/chrome/browser/ui/cocoa/nsmenuitem_additions_unittest.mm
+++ b/chrome/browser/ui/cocoa/nsmenuitem_additions_unittest.mm
@@ -331,6 +331,26 @@
                           /*compareCocoa=*/false);
   ExpectKeyFiresItem(key, MenuItem(@"T", NSCommandKeyMask),
                      /*compareCocoa=*/false);
+
+  // On Czech layout, cmd + '+' should instead trigger cmd + '1'.
+  key = KeyEvent(0x100108, @"1", @"+", 18);
+  ExpectKeyDoesntFireItem(key, MenuItem(@"1", NSCommandKeyMask),
+                          /*compareCocoa=*/false);
+  SetIsInputSourceCzechForTesting(true);
+  ExpectKeyFiresItem(key, MenuItem(@"1", NSCommandKeyMask),
+                     /*compareCocoa=*/false);
+  SetIsInputSourceCzechForTesting(false);
+  ExpectKeyDoesntFireItem(key, MenuItem(@"1", NSCommandKeyMask),
+                          /*compareCocoa=*/false);
+
+  // On Vietnamese layout, cmd + '' [vkeycode = 18] should instead trigger cmd +
+  // '1'. Ditto for other number keys.
+  key = KeyEvent(0x100108, @"1", @"", 18);
+  ExpectKeyFiresItem(key, MenuItem(@"1", NSCommandKeyMask),
+                     /*compareCocoa=*/false);
+  key = KeyEvent(0x100108, @"4", @"", 21);
+  ExpectKeyFiresItem(key, MenuItem(@"4", NSCommandKeyMask),
+                     /*compareCocoa=*/false);
 }
 
 NSString* keyCodeToCharacter(NSUInteger keyCode,
diff --git a/chrome/browser/ui/views/crostini/crostini_browser_test_util.cc b/chrome/browser/ui/views/crostini/crostini_browser_test_util.cc
index 370b911..7f5854b0 100644
--- a/chrome/browser/ui/views/crostini/crostini_browser_test_util.cc
+++ b/chrome/browser/ui/views/crostini/crostini_browser_test_util.cc
@@ -74,7 +74,7 @@
 
 void CrostiniDialogBrowserTest::SetUp() {
   InitCrosTermina();
-  SetCrostiniUIAllowedForTesting(true);
+  crostini::SetCrostiniUIAllowedForTesting(true);
   DialogBrowserTest::SetUp();
 }
 
diff --git a/chrome/browser/ui/views/crostini/crostini_installer_view.cc b/chrome/browser/ui/views/crostini/crostini_installer_view.cc
index 137f2f9..b3354d2 100644
--- a/chrome/browser/ui/views/crostini/crostini_installer_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_installer_view.cc
@@ -81,14 +81,16 @@
 
 }  // namespace
 
-void ShowCrostiniInstallerView(Profile* profile, CrostiniUISurface ui_surface) {
+void crostini::ShowCrostiniInstallerView(
+    Profile* profile,
+    crostini::CrostiniUISurface ui_surface) {
   base::UmaHistogramEnumeration(kCrostiniSetupSourceHistogram, ui_surface,
-                                CrostiniUISurface::kCount);
+                                crostini::CrostiniUISurface::kCount);
   return CrostiniInstallerView::Show(profile);
 }
 
 void CrostiniInstallerView::Show(Profile* profile) {
-  DCHECK(IsCrostiniUIAllowedForProfile(profile));
+  DCHECK(crostini::IsCrostiniUIAllowedForProfile(profile));
   if (!g_crostini_installer_view) {
     g_crostini_installer_view = new CrostiniInstallerView(profile);
     views::DialogDelegate::CreateDialogWidget(g_crostini_installer_view,
@@ -162,7 +164,8 @@
   // Kick off the Crostini Restart sequence. We will be added as an observer.
   restart_id_ =
       crostini::CrostiniManager::GetForProfile(profile_)->RestartCrostini(
-          kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
+          crostini::kCrostiniDefaultVmName,
+          crostini::kCrostiniDefaultContainerName,
           base::BindOnce(&CrostiniInstallerView::MountContainerFinished,
                          weak_ptr_factory_.GetWeakPtr()),
           this);
@@ -448,9 +451,9 @@
 
   crostini::CrostiniManager* crostini_manager =
       crostini::CrostiniManager::GetForProfile(profile_);
-  crostini_manager->LaunchContainerTerminal(kCrostiniDefaultVmName,
-                                            kCrostiniDefaultContainerName,
-                                            std::vector<std::string>());
+  crostini_manager->LaunchContainerTerminal(
+      crostini::kCrostiniDefaultVmName, crostini::kCrostiniDefaultContainerName,
+      std::vector<std::string>());
 
   StepProgress();
   RecordSetupResultHistogram(SetupResult::kSuccess);
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 433398ec3..10ba78ba 100644
--- a/chrome/browser/ui/views/crostini/crostini_installer_view_browsertest.cc
+++ b/chrome/browser/ui/views/crostini/crostini_installer_view_browsertest.cc
@@ -91,7 +91,7 @@
   // CrostiniDialogBrowserTest:
   void ShowUi(const std::string& name) override {
     ShowCrostiniInstallerView(browser()->profile(),
-                              CrostiniUISurface::kSettings);
+                              crostini::CrostiniUISurface::kSettings);
   }
 
   void SetUpOnMainThread() override {
diff --git a/chrome/browser/ui/views/crostini/crostini_uninstaller_view.cc b/chrome/browser/ui/views/crostini/crostini_uninstaller_view.cc
index 031dc5c8..34e4de2a 100644
--- a/chrome/browser/ui/views/crostini/crostini_uninstaller_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_uninstaller_view.cc
@@ -31,15 +31,16 @@
 
 }  // namespace
 
-void ShowCrostiniUninstallerView(Profile* profile,
-                                 CrostiniUISurface ui_surface) {
+void crostini::ShowCrostiniUninstallerView(
+    Profile* profile,
+    crostini::CrostiniUISurface ui_surface) {
   base::UmaHistogramEnumeration(kCrostiniUninstallSourceHistogram, ui_surface,
-                                CrostiniUISurface::kCount);
+                                crostini::CrostiniUISurface::kCount);
   return CrostiniUninstallerView::Show(profile);
 }
 
 void CrostiniUninstallerView::Show(Profile* profile) {
-  DCHECK(IsCrostiniUIAllowedForProfile(profile));
+  DCHECK(crostini::IsCrostiniUIAllowedForProfile(profile));
   if (!g_crostini_uninstaller_view) {
     g_crostini_uninstaller_view = new CrostiniUninstallerView(profile);
     views::DialogDelegate::CreateDialogWidget(g_crostini_uninstaller_view,
@@ -85,7 +86,7 @@
 
   // Kick off the Crostini Remove sequence.
   crostini::CrostiniManager::GetForProfile(profile_)->RemoveCrostini(
-      kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
+      crostini::kCrostiniDefaultVmName, crostini::kCrostiniDefaultContainerName,
       base::BindOnce(&CrostiniUninstallerView::UninstallCrostiniFinished,
                      weak_ptr_factory_.GetWeakPtr()));
 
diff --git a/chrome/browser/ui/views/crostini/crostini_uninstaller_view_browsertest.cc b/chrome/browser/ui/views/crostini/crostini_uninstaller_view_browsertest.cc
index ab6a48e..4b8f1d9b 100644
--- a/chrome/browser/ui/views/crostini/crostini_uninstaller_view_browsertest.cc
+++ b/chrome/browser/ui/views/crostini/crostini_uninstaller_view_browsertest.cc
@@ -60,7 +60,7 @@
   // DialogBrowserTest:
   void ShowUi(const std::string& name) override {
     ShowCrostiniUninstallerView(browser()->profile(),
-                                CrostiniUISurface::kSettings);
+                                crostini::CrostiniUISurface::kSettings);
   }
 
   CrostiniUninstallerView* ActiveView() {
diff --git a/chrome/browser/ui/views/crostini/crostini_upgrade_view.cc b/chrome/browser/ui/views/crostini/crostini_upgrade_view.cc
index d5f7641..c1cec7a2 100644
--- a/chrome/browser/ui/views/crostini/crostini_upgrade_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_upgrade_view.cc
@@ -29,14 +29,15 @@
 
 }  // namespace
 
-void ShowCrostiniUpgradeView(Profile* profile, CrostiniUISurface ui_surface) {
+void crostini::ShowCrostiniUpgradeView(Profile* profile,
+                                       crostini::CrostiniUISurface ui_surface) {
   base::UmaHistogramEnumeration(kCrostiniUpgradeSourceHistogram, ui_surface,
-                                CrostiniUISurface::kCount);
+                                crostini::CrostiniUISurface::kCount);
   return CrostiniUpgradeView::Show(profile);
 }
 
 void CrostiniUpgradeView::Show(Profile* profile) {
-  DCHECK(IsCrostiniUIAllowedForProfile(profile));
+  DCHECK(crostini::IsCrostiniUIAllowedForProfile(profile));
   if (!g_crostini_upgrade_view) {
     g_crostini_upgrade_view = new CrostiniUpgradeView;
     CreateDialogWidget(g_crostini_upgrade_view, nullptr, nullptr);
diff --git a/chrome/browser/ui/views/crostini/crostini_upgrade_view_browsertest.cc b/chrome/browser/ui/views/crostini/crostini_upgrade_view_browsertest.cc
index a46eec5..4b3e41a 100644
--- a/chrome/browser/ui/views/crostini/crostini_upgrade_view_browsertest.cc
+++ b/chrome/browser/ui/views/crostini/crostini_upgrade_view_browsertest.cc
@@ -26,7 +26,8 @@
 
   // DialogBrowserTest:
   void ShowUi(const std::string& name) override {
-    ShowCrostiniUpgradeView(browser()->profile(), CrostiniUISurface::kAppList);
+    ShowCrostiniUpgradeView(browser()->profile(),
+                            crostini::CrostiniUISurface::kAppList);
   }
 
   CrostiniUpgradeView* ActiveView() {
@@ -86,7 +87,9 @@
 
   histogram_tester.ExpectUniqueSample(
       "Crostini.UpgradeSource",
-      static_cast<base::HistogramBase::Sample>(CrostiniUISurface::kAppList), 1);
+      static_cast<base::HistogramBase::Sample>(
+          crostini::CrostiniUISurface::kAppList),
+      1);
 }
 
 IN_PROC_BROWSER_TEST_F(CrostiniUpgradeViewBrowserTest,
@@ -98,7 +101,8 @@
   ExpectNoView();
 
   UnregisterTermina();
-  LaunchCrostiniApp(browser()->profile(), kCrostiniTerminalId, 0);
+  crostini::LaunchCrostiniApp(browser()->profile(),
+                              crostini::kCrostiniTerminalId, 0);
   ExpectNoView();
 }
 
@@ -112,7 +116,8 @@
   ExpectNoView();
 
   UnregisterTermina();
-  LaunchCrostiniApp(browser()->profile(), kCrostiniTerminalId, 0);
+  crostini::LaunchCrostiniApp(browser()->profile(),
+                              crostini::kCrostiniTerminalId, 0);
   ExpectView();
 
   ActiveView()->GetDialogClientView()->AcceptWindow();
@@ -122,5 +127,7 @@
 
   histogram_tester.ExpectUniqueSample(
       "Crostini.UpgradeSource",
-      static_cast<base::HistogramBase::Sample>(CrostiniUISurface::kAppList), 1);
+      static_cast<base::HistogramBase::Sample>(
+          crostini::CrostiniUISurface::kAppList),
+      1);
 }
diff --git a/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc b/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
index 1adb1ff..c3f7c23 100644
--- a/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
@@ -168,20 +168,12 @@
     const ExtensionInstallPrompt::DoneCallback& done_callback,
     std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  bool use_tab_modal_dialog = prompt->ShouldUseTabModalDialog();
   ExtensionInstallDialogView* dialog = new ExtensionInstallDialogView(
       show_params->profile(), show_params->GetParentWebContents(),
       done_callback, std::move(prompt));
-  if (use_tab_modal_dialog) {
-    content::WebContents* parent_web_contents =
-        show_params->GetParentWebContents();
-    if (parent_web_contents)
-      constrained_window::ShowWebModalDialogViews(dialog, parent_web_contents);
-  } else {
-    constrained_window::CreateBrowserModalDialogViews(
-        dialog, show_params->GetParentWindow())
-        ->Show();
-  }
+  constrained_window::CreateBrowserModalDialogViews(
+      dialog, show_params->GetParentWindow())
+      ->Show();
 }
 
 // A custom scrollable view implementation for the dialog.
@@ -437,8 +429,7 @@
 }
 
 ui::ModalType ExtensionInstallDialogView::GetModalType() const {
-  return prompt_->ShouldUseTabModalDialog() ? ui::MODAL_TYPE_CHILD
-                                            : ui::MODAL_TYPE_WINDOW;
+  return ui::MODAL_TYPE_WINDOW;
 }
 
 void ExtensionInstallDialogView::LinkClicked(views::Link* source,
diff --git a/chrome/browser/ui/views/extensions/extension_install_dialog_view_browsertest.cc b/chrome/browser/ui/views/extensions/extension_install_dialog_view_browsertest.cc
index af5870f..ca6fd7b8 100644
--- a/chrome/browser/ui/views/extensions/extension_install_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/extensions/extension_install_dialog_view_browsertest.cc
@@ -294,7 +294,7 @@
 
  private:
   ExtensionInstallPrompt::PromptType type_ =
-      ExtensionInstallPrompt::INLINE_INSTALL_PROMPT;
+      ExtensionInstallPrompt::INSTALL_PROMPT;
   bool from_webstore_ = false;
   PermissionMessages permissions_;
   std::vector<base::FilePath> retained_files_;
@@ -330,12 +330,14 @@
 
 IN_PROC_BROWSER_TEST_F(ExtensionInstallDialogViewInteractiveBrowserTest,
                        InvokeUi_FromWebstore) {
+  set_type(ExtensionInstallPrompt::WEBSTORE_WIDGET_PROMPT);
   set_from_webstore();
   ShowAndVerifyUi();
 }
 
 IN_PROC_BROWSER_TEST_F(ExtensionInstallDialogViewInteractiveBrowserTest,
                        InvokeUi_FromWebstoreWithPermission) {
+  set_type(ExtensionInstallPrompt::WEBSTORE_WIDGET_PROMPT);
   set_from_webstore();
   AddPermission("Example permission");
   ShowAndVerifyUi();
@@ -421,7 +423,7 @@
       "Testing with %d ratings, %f average rating. Expected text: '%s'.",
       num_ratings, average_rating, expected_text.c_str()));
   std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt =
-      CreatePrompt(ExtensionInstallPrompt::INLINE_INSTALL_PROMPT);
+      CreatePrompt(ExtensionInstallPrompt::REPAIR_PROMPT);
   prompt->SetWebstoreData("1,234", true, average_rating, num_ratings);
 
   ExtensionInstallDialogView* dialog = new ExtensionInstallDialogView(
diff --git a/chrome/browser/ui/views/frame/browser_frame_mash.cc b/chrome/browser/ui/views/frame/browser_frame_mash.cc
index c0059ae..15f9be8 100644
--- a/chrome/browser/ui/views/frame/browser_frame_mash.cc
+++ b/chrome/browser/ui/views/frame/browser_frame_mash.cc
@@ -58,9 +58,9 @@
   params.delegate = browser_view_;
   std::map<std::string, std::vector<uint8_t>> properties =
       views::MusClient::ConfigurePropertiesFromParams(params);
-  // Indicates mash shouldn't handle immersive, rather we will.
-  properties[ws::mojom::WindowManager::kDisableImmersive_InitProperty] =
-      mojo::ConvertTo<std::vector<uint8_t>>(true);
+
+  // The client will draw the frame.
+  params.remove_standard_frame = true;
 
   // ChromeLauncherController manages the browser shortcut shelf item; set the
   // window's shelf item type property to be ignored by ash::ShelfWindowWatcher.
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
index 4360480..9b397fc 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
@@ -186,17 +186,6 @@
       static_cast<int>(browser->is_app() ? ash::AppType::CHROME_APP
                                          : ash::AppType::BROWSER));
 
-  // In tablet mode, to prevent accidental taps of the window controls, and to
-  // give more horizontal space for tabs and the new tab button especially in
-  // splitscreen view, we hide the window controls. We only do this when the
-  // Home Launcher feature is enabled, since it gives the user the ability to
-  // minimize all windows when pressing the Launcher button on the shelf.
-  window->SetProperty(
-      ash::kHideCaptionButtonsInTabletModeKey,
-      (browser->is_app() || !app_list_features::IsHomeLauncherEnabled())
-          ? false
-          : true);
-
   window_observer_.Add(GetFrameWindow());
 
   // To preserve privacy, tag incognito windows so that they won't be included
@@ -452,8 +441,6 @@
 
 void BrowserNonClientFrameViewAsh::ChildPreferredSizeChanged(
     views::View* child) {
-  // This is required even when Ash provides the header (as in Mash) for the
-  // |hosted_app_button_container_|.
   if (browser_view()->initialized()) {
     InvalidateLayout();
     frame()->GetRootView()->Layout();
@@ -744,9 +731,15 @@
 // BrowserNonClientFrameViewAsh, private:
 
 bool BrowserNonClientFrameViewAsh::ShouldShowCaptionButtons() const {
-  if (frame()->GetNativeWindow()->GetProperty(
-          ash::kHideCaptionButtonsInTabletModeKey) &&
-      TabletModeClient::Get() &&
+  // In tablet mode, to prevent accidental taps of the window controls, and to
+  // give more horizontal space for tabs and the new tab button especially in
+  // splitscreen view, we hide the window controls. We only do this when the
+  // Home Launcher feature is enabled, since it gives the user the ability to
+  // minimize all windows when pressing the Launcher button on the shelf.
+  const bool hide_caption_buttons_in_tablet_mode =
+      !browser_view()->browser()->is_app() &&
+      app_list_features::IsHomeLauncherEnabled();
+  if (hide_caption_buttons_in_tablet_mode && TabletModeClient::Get() &&
       TabletModeClient::Get()->tablet_mode_enabled()) {
     return false;
   }
@@ -823,11 +816,8 @@
   base::Optional<SkColor> theme_color =
       browser->hosted_app_controller()->GetThemeColor();
   if (theme_color) {
-    frame()->GetNativeWindow()->SetProperty(
-        ash::kFrameIsThemedByHostedAppKey,
-        !!browser->hosted_app_controller()->GetThemeColor());
+    header->set_button_color_mode(ash::FrameCaptionButton::ColorMode::kThemed);
     header->SetFrameColors(*theme_color, *theme_color);
-
     active_color = ash::FrameCaptionButton::GetButtonColor(
         ash::FrameCaptionButton::ColorMode::kThemed, *theme_color);
   }
@@ -852,7 +842,9 @@
                  IsForExperimentalHostedAppBrowser(browser_view()->browser())) {
     active_color =
         browser_view()->browser()->hosted_app_controller()->GetThemeColor();
-    window->SetProperty(ash::kFrameIsThemedByHostedAppKey, !!active_color);
+    frame_header_->set_button_color_mode(
+        active_color ? ash::FrameCaptionButton::ColorMode::kThemed
+                     : ash::FrameCaptionButton::ColorMode::kDefault);
   } else if (!browser_view()->browser()->is_app()) {
     active_color = kMdWebUiFrameColor;
   }
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
index d816efb9..eee2cbb 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
@@ -247,11 +247,6 @@
   // Owned by views hierarchy.
   HostedAppButtonContainer* hosted_app_button_container_ = nullptr;
 
-  // A view that contains the extra views used for hosted apps
-  // (|hosted_app_button_container_| and |hosted_app_origin_text_|).
-  // Only used in Mash.
-  views::View* hosted_app_extras_container_ = nullptr;
-
   // Ash's mojom::SplitViewController.
   ash::mojom::SplitViewControllerPtr split_view_controller_;
 
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
index 3ebc0bd..733c986e 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
@@ -144,22 +144,17 @@
         GetOmniboxStateAlpha(OmniboxPartState::SELECTED));
   }
 
-  // Bubbles are given the full internal height of the location bar so that all
-  // child views in the location bar have the same height. The visible height of
-  // the bubble should be smaller, so use an empty border to shrink down the
-  // content bounds so the background gets painted correctly.
-  SetBorder(views::CreateEmptyBorder(
-      gfx::Insets(GetLayoutConstant(LOCATION_BAR_BUBBLE_VERTICAL_PADDING),
-                  GetLayoutInsets(LOCATION_BAR_ICON_INTERIOR_PADDING).left())));
+  UpdateBorder();
 
   set_notify_enter_exit_on_child(true);
 
   // Flip the canvas in RTL so the separator is drawn on the correct side.
   separator_view_->EnableCanvasFlippingForRTLUI(true);
+
+  md_observer_.Add(ui::MaterialDesignController::GetInstance());
 }
 
-IconLabelBubbleView::~IconLabelBubbleView() {
-}
+IconLabelBubbleView::~IconLabelBubbleView() {}
 
 void IconLabelBubbleView::InkDropAnimationStarted() {
   separator_view_->UpdateOpacity();
@@ -218,6 +213,16 @@
   return false;
 }
 
+void IconLabelBubbleView::UpdateBorder() {
+  // Bubbles are given the full internal height of the location bar so that all
+  // child views in the location bar have the same height. The visible height of
+  // the bubble should be smaller, so use an empty border to shrink down the
+  // content bounds so the background gets painted correctly.
+  SetBorder(views::CreateEmptyBorder(
+      gfx::Insets(GetLayoutConstant(LOCATION_BAR_BUBBLE_VERTICAL_PADDING),
+                  GetLayoutInsets(LOCATION_BAR_ICON_INTERIOR_PADDING).left())));
+}
+
 gfx::Size IconLabelBubbleView::CalculatePreferredSize() const {
   // Height will be ignored by the LocationBarView.
   return GetSizeForLabelWidth(label_->GetPreferredSize().width());
@@ -419,6 +424,15 @@
   AnimationEnded(animation);
 }
 
+void IconLabelBubbleView::OnMdModeChanged() {
+  UpdateBorder();
+
+  // PreferredSizeChanged() incurs an expensive layout of the location bar, so
+  // only call it when this view is showing.
+  if (visible())
+    PreferredSizeChanged();
+}
+
 SkColor IconLabelBubbleView::GetParentBackgroundColor() const {
   return GetNativeTheme()->GetSystemColor(
       ui::NativeTheme::kColorId_TextfieldDefaultBackground);
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
index dfe8240..2b8600f 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
@@ -10,7 +10,9 @@
 
 #include "base/macros.h"
 #include "base/optional.h"
+#include "base/scoped_observer.h"
 #include "base/strings/string16.h"
+#include "ui/base/material_design/material_design_controller_observer.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/views/animation/ink_drop_host_view.h"
@@ -33,7 +35,8 @@
 // base for the classes that handle the location icon (including the EV bubble),
 // tab-to-search UI, and content settings.
 class IconLabelBubbleView : public views::InkDropObserver,
-                            public views::Button {
+                            public views::Button,
+                            public ui::MaterialDesignControllerObserver {
  public:
   static constexpr int kTrailingPaddingPreMd = 2;
 
@@ -122,6 +125,9 @@
   // prevent the bubble from reshowing on a mouse release.
   virtual bool IsBubbleShowing() const;
 
+  // Sets the border padding around this view.
+  virtual void UpdateBorder();
+
   // views::InkDropHostView:
   gfx::Size CalculatePreferredSize() const override;
   void Layout() override;
@@ -147,6 +153,9 @@
   void AnimationProgressed(const gfx::Animation* animation) override;
   void AnimationCanceled(const gfx::Animation* animation) override;
 
+  // ui::MaterialDesignControllerObserver:
+  void OnMdModeChanged() override;
+
   const gfx::FontList& font_list() const { return label_->font_list(); }
 
   SkColor GetParentBackgroundColor() const;
@@ -246,6 +255,10 @@
   double pause_animation_state_ = 0.0;
   double open_state_fraction_ = 0.0;
 
+  ScopedObserver<ui::MaterialDesignController,
+                 ui::MaterialDesignControllerObserver>
+      md_observer_{this};
+
   DISALLOW_COPY_AND_ASSIGN(IconLabelBubbleView);
 };
 
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc
index 2b2a3ba8..2d65470e 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -156,6 +156,7 @@
       bookmarks::prefs::kEditBookmarksEnabled, profile->GetPrefs(),
       base::Bind(&LocationBarView::UpdateWithoutTabRestore,
                  base::Unretained(this)));
+  md_observer_.Add(ui::MaterialDesignController::GetInstance());
 }
 
 LocationBarView::~LocationBarView() {}
@@ -659,10 +660,8 @@
 }
 
 void LocationBarView::ChildPreferredSizeChanged(views::View* child) {
-  if (child != page_action_icon_container_view_)
-    return;
-
   Layout();
+  SchedulePaint();
 }
 
 void LocationBarView::Update(const WebContents* contents) {
@@ -1330,6 +1329,15 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// LocationBarView, private ui::MaterialDesignControllerObserver implementation:
+
+void LocationBarView::OnMdModeChanged() {
+  RefreshLocationIcon();
+  Layout();
+  SchedulePaint();
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // LocationBarView, private static methods:
 
 // static
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.h b/chrome/browser/ui/views/location_bar/location_bar_view.h
index 64d636a..e441ae82 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.h
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.h
@@ -26,6 +26,7 @@
 #include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
 #include "components/prefs/pref_member.h"
 #include "components/security_state/core/security_state.h"
+#include "ui/base/material_design/material_design_controller_observer.h"
 #include "ui/gfx/animation/animation_delegate.h"
 #include "ui/gfx/animation/slide_animation.h"
 #include "ui/gfx/font.h"
@@ -78,7 +79,8 @@
                         public DropdownBarHostDelegate,
                         public views::ButtonListener,
                         public ContentSettingImageView::Delegate,
-                        public PageActionIconView::Delegate {
+                        public PageActionIconView::Delegate,
+                        public ui::MaterialDesignControllerObserver {
  public:
   class Delegate {
    public:
@@ -386,6 +388,9 @@
   // DropdownBarHostDelegate:
   void SetFocusAndSelection(bool select_all) override;
 
+  // ui::MaterialDesignControllerObserver:
+  void OnMdModeChanged() override;
+
   // Returns the total amount of space reserved above or below the content,
   // which is the vertical edge thickness plus the padding next to it.
   static int GetTotalVerticalPadding();
@@ -488,6 +493,10 @@
   // The focus ring, if one is in use.
   std::unique_ptr<views::FocusRing> focus_ring_;
 
+  ScopedObserver<ui::MaterialDesignController,
+                 ui::MaterialDesignControllerObserver>
+      md_observer_{this};
+
   // Used to scope the lifetime of asynchronous icon fetch callbacks to the
   // lifetime of the object. Weak pointers issued by this factory are
   // invalidated whenever we start a new icon fetch, so don't use this weak
diff --git a/chrome/browser/ui/views/media_router/media_router_views_ui.cc b/chrome/browser/ui/views/media_router/media_router_views_ui.cc
index 34382a41..ce0b6f0 100644
--- a/chrome/browser/ui/views/media_router/media_router_views_ui.cc
+++ b/chrome/browser/ui/views/media_router/media_router_views_ui.cc
@@ -147,6 +147,10 @@
                         : UIMediaSinkState::AVAILABLE;
     ui_sink.cast_modes = sink.cast_modes;
   }
+  if (ui_sink.icon_type == SinkIconType::HANGOUT &&
+      ui_sink.state == UIMediaSinkState::AVAILABLE && sink.sink.domain()) {
+    ui_sink.status_text = base::UTF8ToUTF16(*sink.sink.domain());
+  }
   if (issue && IssueMatches(*issue, ui_sink))
     ui_sink.issue = issue;
   return ui_sink;
diff --git a/chrome/browser/ui/views/media_router/media_router_views_ui.h b/chrome/browser/ui/views/media_router/media_router_views_ui.h
index a9a8286..2843a42c1 100644
--- a/chrome/browser/ui/views/media_router/media_router_views_ui.h
+++ b/chrome/browser/ui/views/media_router/media_router_views_ui.h
@@ -37,6 +37,7 @@
   FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, ConnectingState);
   FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, DisconnectingState);
   FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, AddAndRemoveIssue);
+  FRIEND_TEST_ALL_PREFIXES(MediaRouterViewsUITest, ShowDomainForHangouts);
 
   // MediaRouterUIBase:
   void InitCommon(content::WebContents* initiator) override;
diff --git a/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc b/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc
index fe5fdecb..56c929ab 100644
--- a/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc
+++ b/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc
@@ -279,4 +279,39 @@
   ui_->RemoveObserver(&observer);
 }
 
+TEST_F(MediaRouterViewsUITest, ShowDomainForHangouts) {
+  const std::string domain1 = "domain1.com";
+  const std::string domain2 = "domain2.com";
+  MediaSinkWithCastModes available_hangout(
+      MediaSink("sink1", "Hangout 1", SinkIconType::HANGOUT));
+  MediaSinkWithCastModes connected_hangout(
+      MediaSink("sink2", "Hangout 2", SinkIconType::HANGOUT));
+  available_hangout.sink.set_domain(domain1);
+  connected_hangout.sink.set_domain(domain2);
+  available_hangout.cast_modes = {MediaCastMode::TAB_MIRROR};
+  connected_hangout.cast_modes = {MediaCastMode::TAB_MIRROR};
+
+  MockControllerObserver observer;
+  ui_->AddObserver(&observer);
+  const std::string route_description = "route 1";
+  MediaRoute route(kRouteId, MediaSource(kSourceId), "sink2", route_description,
+                   true, true);
+  ui_->OnRoutesUpdated({route}, {});
+
+  // The domain should be used as the status text only if the sink is available.
+  // If the sink has a route, the route description is used.
+  EXPECT_CALL(observer, OnModelUpdated(_))
+      .WillOnce(WithArg<0>([&](const CastDialogModel& model) {
+        EXPECT_EQ(2u, model.media_sinks().size());
+        EXPECT_EQ(model.media_sinks()[0].id, available_hangout.sink.id());
+        EXPECT_EQ(base::UTF8ToUTF16(domain1),
+                  model.media_sinks()[0].status_text);
+        EXPECT_EQ(model.media_sinks()[1].id, connected_hangout.sink.id());
+        EXPECT_EQ(base::UTF8ToUTF16(route_description),
+                  model.media_sinks()[1].status_text);
+      }));
+  ui_->OnResultsUpdated({available_hangout, connected_hangout});
+  ui_->RemoveObserver(&observer);
+}
+
 }  // namespace media_router
diff --git a/chrome/browser/ui/views/page_action/page_action_icon_view.cc b/chrome/browser/ui/views/page_action/page_action_icon_view.cc
index 838e816..a7d3b64 100644
--- a/chrome/browser/ui/views/page_action/page_action_icon_view.cc
+++ b/chrome/browser/ui/views/page_action/page_action_icon_view.cc
@@ -53,8 +53,6 @@
       command_id_(command_id),
       active_(false),
       suppress_mouse_released_action_(false) {
-  SetBorder(views::CreateEmptyBorder(
-      GetLayoutInsets(LOCATION_BAR_ICON_INTERIOR_PADDING)));
   if (ui::MaterialDesignController::IsNewerMaterialUi()) {
     // Ink drop ripple opacity.
     set_ink_drop_visible_opacity(
@@ -246,6 +244,17 @@
   IconLabelBubbleView::OnBoundsChanged(previous_bounds);
 }
 
+void PageActionIconView::OnMdModeChanged() {
+  icon_size_ = GetLayoutConstant(LOCATION_BAR_ICON_SIZE);
+  UpdateIconImage();
+  IconLabelBubbleView::OnMdModeChanged();
+}
+
+void PageActionIconView::UpdateBorder() {
+  SetBorder(views::CreateEmptyBorder(
+      GetLayoutInsets(LOCATION_BAR_ICON_INTERIOR_PADDING)));
+}
+
 void PageActionIconView::SetIconColor(SkColor icon_color) {
   icon_color_ = icon_color;
   UpdateIconImage();
diff --git a/chrome/browser/ui/views/page_action/page_action_icon_view.h b/chrome/browser/ui/views/page_action/page_action_icon_view.h
index 0f6d3411..c868b517 100644
--- a/chrome/browser/ui/views/page_action/page_action_icon_view.h
+++ b/chrome/browser/ui/views/page_action/page_action_icon_view.h
@@ -120,8 +120,10 @@
   // Gets the given vector icon in the correct color and size based on |active|.
   virtual const gfx::VectorIcon& GetVectorIcon() const = 0;
 
-  // views::View:
+  // IconLabelBubbleView:
   void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
+  void OnMdModeChanged() override;
+  void UpdateBorder() override;
 
   // Updates the icon image after some state has changed.
   void UpdateIconImage();
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
index 99b01963..9f2ef77 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
@@ -759,7 +759,7 @@
   GetAttachedBrowserWidget()->GetGestureRecognizer()->TransferEventsTo(
       GetAttachedBrowserWidget()->GetNativeView(),
       target_tabstrip->GetWidget()->GetNativeView(),
-      ui::GestureRecognizer::ShouldCancelTouches::DontCancel);
+      ui::TransferTouchesBehavior::kDontCancel);
 #endif
 
   if (is_dragging_window_) {
@@ -1236,7 +1236,7 @@
   gfx::NativeView attached_native_view = attached_widget->GetNativeView();
   attached_widget->GetGestureRecognizer()->TransferEventsTo(
       attached_native_view, dragged_widget->GetNativeView(),
-      ui::GestureRecognizer::ShouldCancelTouches::DontCancel);
+      ui::TransferTouchesBehavior::kDontCancel);
 #endif
 
   Detach(can_release_capture_ ? RELEASE_CAPTURE : DONT_RELEASE_CAPTURE);
diff --git a/chrome/browser/ui/webui/extensions/extensions_internals_source.cc b/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
index 669c255..c11ff8f 100644
--- a/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
+++ b/chrome/browser/ui/webui/extensions/extensions_internals_source.cc
@@ -5,17 +5,22 @@
 #include "chrome/browser/ui/webui/extensions/extensions_internals_source.h"
 
 #include <string>
+#include <unordered_map>
 #include <utility>
 
 #include "base/json/json_writer.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted_memory.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/string_piece.h"
 #include "base/values.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/webui_url_constants.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_thread.h"
 #include "extensions/browser/activity.h"
+#include "extensions/browser/event_listener_map.h"
+#include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/process_manager.h"
 
@@ -77,11 +82,78 @@
   return "";
 }
 
+// The JSON we generate looks like this:
+//
+// [ {
+//    "event_listeners": {
+//       "count": 2,
+//       "events": [ {
+//          "name": "runtime.onInstalled"
+//       }, {
+//          "name": "runtime.onSuspend"
+//       } ]
+//    },
+//    "id": "bhloflhklmhfpedakmangadcdofhnnoh",
+//    "keepalive": {
+//       "activities": [ {
+//          "extra_data": "render-frame",
+//          "type": "PROCESS_MANAGER"
+//       } ],
+//       "count": 1
+//    },
+//    "location": "INTERNAL",
+//    "manifest_version": 2,
+//    "name": "Earth View from Google Earth",
+//    "path": "/user/Extensions/bhloflhklmhfpedakmangadcdofhnnoh/2.18.5_0",
+//    "type": "TYPE_EXTENSION",
+//    "version": "2.18.5"
+// } ]
+//
+// Which is:
+//
+// LIST
+//  DICT
+//    "event_listeners": DICT
+//      "count": INT
+//      "events": LIST
+//        DICT
+//          "name": STRING
+//          "filter": DICT
+//    "id": STRING
+//    "keepalive": DICT
+//      "activities": LIST
+//        DICT
+//          "extra_data": STRING
+//          "type": STRING
+//      "count": INT
+//    "location": STRING
+//    "manifest_version": INT
+//    "name": STRING
+//    "path": STRING
+//    "type": STRING
+//    "version": STRING
+
+constexpr base::StringPiece kActivitesKey = "activites";
+constexpr base::StringPiece kCountKey = "count";
+constexpr base::StringPiece kEventsKey = "events";
+constexpr base::StringPiece kEventsListenersKey = "event_listeners";
+constexpr base::StringPiece kExtraDataKey = "extra_data";
+constexpr base::StringPiece kFilterKey = "filter";
+constexpr base::StringPiece kKeepaliveKey = "keepalive";
+constexpr base::StringPiece kLocationKey = "location";
+constexpr base::StringPiece kIdKey = "id";
+constexpr base::StringPiece kManifestVersionKey = "manifest_version";
+constexpr base::StringPiece kNameKey = "name";
+constexpr base::StringPiece kPathKey = "path";
+constexpr base::StringPiece kTypeKey = "type";
+constexpr base::StringPiece kVersionKey = "version";
+
 base::Value FormatKeepaliveData(extensions::ProcessManager* process_manager,
                                 const extensions::Extension* extension) {
   base::Value keepalive_data(base::Value::Type::DICTIONARY);
   keepalive_data.SetKey(
-      "count", base::Value(process_manager->GetLazyKeepaliveCount(extension)));
+      kCountKey,
+      base::Value(process_manager->GetLazyKeepaliveCount(extension)));
   const extensions::ProcessManager::ActivitiesMultiset activities =
       process_manager->GetLazyKeepaliveActivities(extension);
   base::Value activities_data(base::Value::Type::LIST);
@@ -89,14 +161,64 @@
   for (const auto& activity : activities) {
     base::Value activities_entry(base::Value::Type::DICTIONARY);
     activities_entry.SetKey(
-        "type", base::Value(extensions::Activity::ToString(activity.first)));
-    activities_entry.SetKey("extra_data", base::Value(activity.second));
+        kTypeKey, base::Value(extensions::Activity::ToString(activity.first)));
+    activities_entry.SetKey(kExtraDataKey, base::Value(activity.second));
     activities_data.GetList().push_back(std::move(activities_entry));
   }
-  keepalive_data.SetKey("activites", std::move(activities_data));
+  keepalive_data.SetKey(kActivitesKey, std::move(activities_data));
   return keepalive_data;
 }
 
+void AddEventListenerData(extensions::EventRouter* event_router,
+                          base::Value* data) {
+  CHECK(data->is_list());
+  // A map of extension ID to the event data for that extension,
+  // which is of type LIST of DICTIONARY.
+  std::unordered_map<base::StringPiece, base::Value, base::StringPieceHash>
+      events_map;
+
+  // Build the map of extension IDs to the list of events.
+  for (const auto& entry : event_router->listeners().listeners()) {
+    for (const auto& listener_entry : entry.second) {
+      auto& events_list = events_map[listener_entry->extension_id()];
+      if (events_list.is_none()) {
+        // Not there, so make it a LIST.
+        events_list = base::Value(base::Value::Type::LIST);
+      }
+      // The data for each event is a dictionary, with a name and a
+      // filter.
+      base::Value event_data(base::Value::Type::DICTIONARY);
+      event_data.SetKey(kNameKey, base::Value(listener_entry->event_name()));
+      // Add the filter if one exists.
+      base::Value* const filter = listener_entry->filter();
+      if (filter != nullptr) {
+        event_data.SetKey(kFilterKey, filter->Clone());
+      }
+      events_list.GetList().push_back(std::move(event_data));
+    }
+  }
+
+  // Move all of the entries from the map into the output data.
+  for (auto& output_entry : data->GetList()) {
+    const base::Value* const value = output_entry.FindKey(kIdKey);
+    CHECK(value && value->is_string());
+    const auto it = events_map.find(value->GetString());
+    base::Value listeners(base::Value::Type::DICTIONARY);
+    if (it == events_map.end()) {
+      // We didn't find any events, so initialize an empty dictionary.
+      listeners.SetKey(kCountKey, base::Value(0));
+      listeners.SetKey(kEventsKey, base::Value(base::Value::Type::LIST));
+    } else {
+      // Set the count and the events values.
+      listeners.SetKey(
+          kCountKey,
+          base::Value(base::checked_cast<int>(it->second.GetList().size())));
+      listeners.SetKey(kEventsKey, std::move(it->second));
+    }
+    output_entry.SetKey(kEventsListenersKey, std::move(listeners));
+  }
+}
+
 }  // namespace
 
 ExtensionsInternalsSource::ExtensionsInternalsSource(Profile* profile)
@@ -131,23 +253,26 @@
   base::Value data(base::Value::Type::LIST);
   for (const auto& extension : *extensions) {
     base::Value extension_data(base::Value::Type::DICTIONARY);
-    extension_data.SetKey("id", base::Value(extension->id()));
+    extension_data.SetKey(kIdKey, base::Value(extension->id()));
     extension_data.SetKey(
-        "keepalive", FormatKeepaliveData(process_manager, extension.get()));
-    extension_data.SetKey("location",
+        kKeepaliveKey, FormatKeepaliveData(process_manager, extension.get()));
+    extension_data.SetKey(kLocationKey,
                           base::Value(LocationToString(extension->location())));
-    extension_data.SetKey("manifest_version",
+    extension_data.SetKey(kManifestVersionKey,
                           base::Value(extension->manifest_version()));
-    extension_data.SetKey("name", base::Value(extension->name()));
-    extension_data.SetKey("path",
+    extension_data.SetKey(kNameKey, base::Value(extension->name()));
+    extension_data.SetKey(kPathKey,
                           base::Value(extension->path().LossyDisplayName()));
-    extension_data.SetKey("type",
+    extension_data.SetKey(kTypeKey,
                           base::Value(TypeToString(extension->GetType())));
-    extension_data.SetKey("version",
+    extension_data.SetKey(kVersionKey,
                           base::Value(extension->GetVersionForDisplay()));
     data.GetList().push_back(std::move(extension_data));
   }
 
+  // Aggregate and add the data for the registered event listeners.
+  AddEventListenerData(extensions::EventRouter::Get(profile_), &data);
+
   std::string json;
   base::JSONWriter::WriteWithOptions(
       data, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
diff --git a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
index 4b10001..8672961 100644
--- a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
@@ -32,13 +32,13 @@
     const base::ListValue* args) {
   AllowJavascript();
   ShowCrostiniInstallerView(Profile::FromWebUI(web_ui()),
-                            CrostiniUISurface::kSettings);
+                            crostini::CrostiniUISurface::kSettings);
 }
 
 void CrostiniHandler::HandleRequestRemoveCrostini(const base::ListValue* args) {
   AllowJavascript();
   ShowCrostiniUninstallerView(Profile::FromWebUI(web_ui()),
-                              CrostiniUISurface::kSettings);
+                              crostini::CrostiniUISurface::kSettings);
 }
 
 }  // namespace settings
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 9900d621..e2b4af3 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc
@@ -328,7 +328,7 @@
 }
 
 void StorageHandler::UpdateCrostiniSize() {
-  if (!IsCrostiniEnabled(profile_)) {
+  if (!crostini::IsCrostiniEnabled(profile_)) {
     return;
   }
 
diff --git a/chrome/browser/ui/webui/settings/md_settings_ui.cc b/chrome/browser/ui/webui/settings/md_settings_ui.cc
index 769ed65e..f08a83f9 100644
--- a/chrome/browser/ui/webui/settings/md_settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_ui.cc
@@ -204,7 +204,7 @@
   }
   AddSettingsPageUIHandler(
       std::make_unique<chromeos::settings::ChangePictureHandler>());
-  if (IsCrostiniUIAllowedForProfile(profile)) {
+  if (crostini::IsCrostiniUIAllowedForProfile(profile)) {
     AddSettingsPageUIHandler(
         std::make_unique<chromeos::settings::CrostiniHandler>());
   }
@@ -323,7 +323,7 @@
                           ash::stylus_utils::HasInternalStylus());
 
   html_source->AddBoolean("showCrostini",
-                          IsCrostiniUIAllowedForProfile(profile));
+                          crostini::IsCrostiniUIAllowedForProfile(profile));
 
   // TODO(crbug.com/868747): Show an explanatory message instead of hiding the
   // storage management info.
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index ab7c907..ace252e 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -359,7 +359,6 @@
       "extensions/manifest_handlers/theme_handler.h",
       "extensions/manifest_handlers/ui_overrides_handler.cc",
       "extensions/manifest_handlers/ui_overrides_handler.h",
-      "extensions/mojom/inline_install_traits.h",
       "extensions/permissions/chrome_api_permissions.cc",
       "extensions/permissions/chrome_api_permissions.h",
       "extensions/permissions/chrome_permission_message_provider.cc",
@@ -375,7 +374,6 @@
     ]
     deps += [ "//chrome/common/apps/platform_apps" ]
     public_deps += [
-      "//chrome/common/extensions:mojo_bindings",
       "//chrome/common/extensions/api",
       "//chrome/common/extensions/api:extensions_features",
       "//device/usb",
diff --git a/chrome/common/common_message_generator.h b/chrome/common/common_message_generator.h
index a27108e3..8039857 100644
--- a/chrome/common/common_message_generator.h
+++ b/chrome/common/common_message_generator.h
@@ -44,7 +44,6 @@
 #ifndef CHROME_COMMON_EXTENSIONS_CHROME_EXTENSION_MESSAGES_H_
 #error "Failed to include chrome/common/extensions/chrome_extension_messages.h"
 #endif
-#include "chrome/common/extensions/mojom/inline_install_traits.h"
 #endif
 
 #if BUILDFLAG(ENABLE_PRINTING)
diff --git a/chrome/common/extensions/BUILD.gn b/chrome/common/extensions/BUILD.gn
index 676919f1..7cefd88 100644
--- a/chrome/common/extensions/BUILD.gn
+++ b/chrome/common/extensions/BUILD.gn
@@ -4,7 +4,6 @@
 
 import("//build/config/features.gni")
 import("//extensions/buildflags/buildflags.gni")
-import("//mojo/public/tools/bindings/mojom.gni")
 import("//tools/json_schema_compiler/json_features.gni")
 
 assert(enable_extensions)
@@ -22,9 +21,3 @@
     ":extension_features_unittest",
   ]
 }
-
-mojom("mojo_bindings") {
-  sources = [
-    "mojom/inline_install.mojom",
-  ]
-}
diff --git a/chrome/common/extensions/api/accessibility_private.json b/chrome/common/extensions/api/accessibility_private.json
index 40924acb..eec66faa 100644
--- a/chrome/common/extensions/api/accessibility_private.json
+++ b/chrome/common/extensions/api/accessibility_private.json
@@ -225,6 +225,13 @@
           }
         ],
         "platforms": ["chromeos"]
+      },
+      {
+        "name": "toggleDictation",
+        "type": "function",
+        "description": "Toggles dictation between active and inactive states.",
+        "parameters": [],
+        "platforms": ["chromeos"]
       }
     ],
     "events": [
diff --git a/chrome/common/extensions/mojom/OWNERS b/chrome/common/extensions/mojom/OWNERS
deleted file mode 100644
index 229dacdd..0000000
--- a/chrome/common/extensions/mojom/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-per-file *.mojom=set noparent
-per-file *.mojom=file://ipc/SECURITY_OWNERS
-per-file *.typemap=set noparent
-per-file *.typemap=file://ipc/SECURITY_OWNERS
diff --git a/chrome/common/extensions/mojom/inline_install.mojom b/chrome/common/extensions/mojom/inline_install.mojom
deleted file mode 100644
index e298424..0000000
--- a/chrome/common/extensions/mojom/inline_install.mojom
+++ /dev/null
@@ -1,31 +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.
-
-module extensions.mojom;
-
-[Native]
-enum WebstoreInstallResult;
-
-[Native]
-enum WebstoreInstallStage;
-
-interface InlineInstallProgressListener {
-  // Notifies the renderer when install stage updates were requested for an
-  // inline install.
-  InlineInstallStageChanged(WebstoreInstallStage stage);
-
-  // Notifies the renderer when download progress updates were requested for an
-  // inline install.
-  InlineInstallDownloadProgress(int32 percent_downloaded);
-};
-
-interface InlineInstaller {
-  // Sent by the renderer to implement chrome.webstore.install() and notifies
-  // the renderer once the installation is complete.
-  DoInlineInstall(string webstore_item_id, int32 listeners_mask,
-      InlineInstallProgressListener install_progress_listener) =>
-      (bool success, string error, WebstoreInstallResult result);
-};
-
-
diff --git a/chrome/common/extensions/mojom/inline_install.typemap b/chrome/common/extensions/mojom/inline_install.typemap
deleted file mode 100644
index 3a08ce5..0000000
--- a/chrome/common/extensions/mojom/inline_install.typemap
+++ /dev/null
@@ -1,14 +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.
-
-mojom = "//chrome/common/extensions/mojom/inline_install.mojom"
-public_headers = [
-  "//chrome/common/extensions/api/webstore/webstore_api_constants.h",
-  "//chrome/common/extensions/webstore_install_result.h",
-]
-traits_headers = [ "//chrome/common/extensions/mojom/inline_install_traits.h" ]
-type_mappings = [
-  "extensions.mojom.WebstoreInstallStage=::extensions::api::webstore::InstallStage",
-  "extensions.mojom.WebstoreInstallResult=::extensions::webstore_install::Result",
-]
diff --git a/chrome/common/extensions/mojom/inline_install_traits.h b/chrome/common/extensions/mojom/inline_install_traits.h
deleted file mode 100644
index 005d17d..0000000
--- a/chrome/common/extensions/mojom/inline_install_traits.h
+++ /dev/null
@@ -1,16 +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.
-
-// Multiply-included file, hence no include guard.
-
-#include "chrome/common/extensions/api/webstore/webstore_api_constants.h"
-#include "chrome/common/extensions/webstore_install_result.h"
-#include "ipc/ipc_message_macros.h"
-
-IPC_ENUM_TRAITS_MAX_VALUE(
-    extensions::api::webstore::InstallStage,
-    extensions::api::webstore::InstallStage::INSTALL_STAGE_INSTALLING)
-IPC_ENUM_TRAITS_MAX_VALUE(extensions::webstore_install::Result,
-                          extensions::webstore_install::RESULT_LAST)
-
diff --git a/chrome/common/extensions/typemaps.gni b/chrome/common/extensions/typemaps.gni
deleted file mode 100644
index 2d63179..0000000
--- a/chrome/common/extensions/typemaps.gni
+++ /dev/null
@@ -1,5 +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.
-
-typemaps = [ "//chrome/common/extensions/mojom/inline_install.typemap" ]
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
index 5f1fed1..7866209 100644
--- a/chrome/renderer/BUILD.gn
+++ b/chrome/renderer/BUILD.gn
@@ -309,8 +309,6 @@
       "extensions/sync_file_system_custom_bindings.h",
       "extensions/tabs_hooks_delegate.cc",
       "extensions/tabs_hooks_delegate.h",
-      "extensions/webstore_bindings.cc",
-      "extensions/webstore_bindings.h",
       "media/cast_ipc_dispatcher.cc",
       "media/cast_ipc_dispatcher.h",
       "media/cast_receiver_audio_valve.cc",
diff --git a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
index bd1695f..a8c8257 100644
--- a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
+++ b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
@@ -25,7 +25,6 @@
 #include "chrome/renderer/extensions/page_capture_custom_bindings.h"
 #include "chrome/renderer/extensions/sync_file_system_custom_bindings.h"
 #include "chrome/renderer/extensions/tabs_hooks_delegate.h"
-#include "chrome/renderer/extensions/webstore_bindings.h"
 #include "components/version_info/version_info.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/renderer/render_thread.h"
@@ -132,9 +131,6 @@
       "page_capture", std::unique_ptr<NativeHandler>(
                           new extensions::PageCaptureCustomBindings(context)));
   module_system->RegisterNativeHandler(
-      "webstore", std::unique_ptr<NativeHandler>(
-                      new extensions::WebstoreBindings(context)));
-  module_system->RegisterNativeHandler(
       "cast_streaming_natives",
       std::make_unique<extensions::CastStreamingNativeHandler>(
           context, bindings_system));
diff --git a/chrome/renderer/extensions/webstore_bindings.cc b/chrome/renderer/extensions/webstore_bindings.cc
deleted file mode 100644
index bbbda3c..0000000
--- a/chrome/renderer/extensions/webstore_bindings.cc
+++ /dev/null
@@ -1,267 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/renderer/extensions/webstore_bindings.h"
-
-#include <stdint.h>
-
-#include "base/macros.h"
-#include "base/strings/string_util.h"
-#include "chrome/common/extensions/api/webstore/webstore_api_constants.h"
-#include "components/crx_file/id_util.h"
-#include "content/public/renderer/render_frame.h"
-#include "extensions/common/extension.h"
-#include "extensions/common/extension_urls.h"
-#include "extensions/renderer/script_context.h"
-#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
-#include "third_party/blink/public/web/web_document.h"
-#include "third_party/blink/public/web/web_element.h"
-#include "third_party/blink/public/web/web_local_frame.h"
-#include "third_party/blink/public/web/web_node.h"
-#include "third_party/blink/public/web/web_user_gesture_indicator.h"
-#include "url/gurl.h"
-#include "v8/include/v8.h"
-
-using blink::WebDocument;
-using blink::WebElement;
-using blink::WebNode;
-using blink::WebUserGestureIndicator;
-
-namespace extensions {
-
-namespace {
-
-const char kWebstoreLinkRelation[] = "chrome-webstore-item";
-
-const char kNotInTopFrameError[] =
-    "Chrome Web Store installations can only be started by the top frame.";
-const char kNotUserGestureError[] =
-    "Chrome Web Store installations can only be initated by a user gesture.";
-const char kNoWebstoreItemLinkFoundError[] =
-    "No Chrome Web Store item link found.";
-const char kInvalidWebstoreItemUrlError[] =
-    "Invalid Chrome Web Store item URL.";
-
-// chrome.webstore.install() calls generate an install ID so that the install's
-// callbacks may be fired when the browser notifies us of install completion
-// (successful or not) via |InlineInstallResponse|.
-int g_next_install_id = 0;
-
-} // anonymous namespace
-
-WebstoreBindings::WebstoreBindings(ScriptContext* context)
-    : ObjectBackedNativeHandler(context) {
-  content::RenderFrame* render_frame = context->GetRenderFrame();
-  if (render_frame)
-    render_frame->GetRemoteAssociatedInterfaces()->GetInterface(
-        &inline_installer_);
-}
-
-WebstoreBindings::~WebstoreBindings() {}
-
-void WebstoreBindings::AddRoutes() {
-  RouteHandlerFunction(
-      "Install", "webstore",
-      base::Bind(&WebstoreBindings::Install, base::Unretained(this)));
-}
-
-void WebstoreBindings::InlineInstallResponse(int install_id,
-                                             bool success,
-                                             const std::string& error,
-                                             webstore_install::Result result) {
-  v8::Isolate* isolate = context()->isolate();
-  v8::HandleScope handle_scope(isolate);
-  v8::Context::Scope context_scope(context()->v8_context());
-  v8::Local<v8::Value> argv[] = {
-      v8::Integer::New(isolate, install_id), v8::Boolean::New(isolate, success),
-      v8::String::NewFromUtf8(isolate, error.c_str(),
-                              v8::NewStringType::kNormal)
-          .ToLocalChecked(),
-      v8::String::NewFromUtf8(
-          isolate, api::webstore::kInstallResultCodes[static_cast<int>(result)],
-          v8::NewStringType::kNormal)
-          .ToLocalChecked()};
-  context()->module_system()->CallModuleMethodSafe(
-      "webstore", "onInstallResponse", arraysize(argv), argv);
-}
-
-void WebstoreBindings::InlineInstallStageChanged(
-    api::webstore::InstallStage stage) {
-  const char* stage_string = nullptr;
-  switch (stage) {
-    case api::webstore::INSTALL_STAGE_DOWNLOADING:
-      stage_string = api::webstore::kInstallStageDownloading;
-      break;
-    case api::webstore::INSTALL_STAGE_INSTALLING:
-      stage_string = api::webstore::kInstallStageInstalling;
-      break;
-  }
-  v8::Isolate* isolate = context()->isolate();
-  v8::HandleScope handle_scope(isolate);
-  v8::Context::Scope context_scope(context()->v8_context());
-  v8::Local<v8::Value> argv[] = {
-      v8::String::NewFromUtf8(isolate, stage_string, v8::NewStringType::kNormal)
-          .ToLocalChecked()};
-  context()->module_system()->CallModuleMethodSafe(
-      "webstore", "onInstallStageChanged", arraysize(argv), argv);
-}
-
-void WebstoreBindings::InlineInstallDownloadProgress(int percent_downloaded) {
-  v8::Isolate* isolate = context()->isolate();
-  v8::HandleScope handle_scope(isolate);
-  v8::Context::Scope context_scope(context()->v8_context());
-  v8::Local<v8::Value> argv[] = {
-      v8::Number::New(isolate, percent_downloaded / 100.0)};
-  context()->module_system()->CallModuleMethodSafe(
-      "webstore", "onDownloadProgress", arraysize(argv), argv);
-}
-
-void WebstoreBindings::Install(
-    const v8::FunctionCallbackInfo<v8::Value>& args) {
-  content::RenderFrame* render_frame = context()->GetRenderFrame();
-  if (!render_frame)
-    return;
-
-  // The first two arguments indicate whether or not there are install stage
-  // or download progress listeners.
-  int listener_mask = 0;
-  CHECK(args[0]->IsBoolean());
-  if (args[0].As<v8::Boolean>()->Value())
-    listener_mask |= api::webstore::INSTALL_STAGE_LISTENER;
-  CHECK(args[1]->IsBoolean());
-  if (args[1].As<v8::Boolean>()->Value())
-    listener_mask |= api::webstore::DOWNLOAD_PROGRESS_LISTENER;
-
-  std::string preferred_store_link_url;
-  if (!args[2]->IsUndefined()) {
-    CHECK(args[2]->IsString());
-    preferred_store_link_url =
-        std::string(*v8::String::Utf8Value(args.GetIsolate(), args[2]));
-  }
-
-  std::string webstore_item_id;
-  std::string error;
-  blink::WebLocalFrame* frame = context()->web_frame();
-
-  if (!GetWebstoreItemIdFromFrame(
-      frame, preferred_store_link_url, &webstore_item_id, &error)) {
-    args.GetIsolate()->ThrowException(
-        v8::String::NewFromUtf8(args.GetIsolate(), error.c_str(),
-                                v8::NewStringType::kNormal)
-            .ToLocalChecked());
-    return;
-  }
-
-  int install_id = g_next_install_id++;
-
-  mojom::InlineInstallProgressListenerPtr install_progress_listener;
-  install_progress_listener_bindings_.AddBinding(
-      this, mojo::MakeRequest(&install_progress_listener));
-
-  inline_installer_->DoInlineInstall(
-      webstore_item_id, listener_mask, std::move(install_progress_listener),
-      base::Bind(&WebstoreBindings::InlineInstallResponse,
-                 base::Unretained(this), install_id));
-  args.GetReturnValue().Set(static_cast<int32_t>(install_id));
-}
-
-// static
-bool WebstoreBindings::GetWebstoreItemIdFromFrame(
-    blink::WebLocalFrame* frame,
-    const std::string& preferred_store_link_url,
-    std::string* webstore_item_id,
-    std::string* error) {
-  if (frame != frame->Top()) {
-    *error = kNotInTopFrameError;
-    return false;
-  }
-
-  if (!WebUserGestureIndicator::IsProcessingUserGesture(frame)) {
-    *error = kNotUserGestureError;
-    return false;
-  }
-
-  WebDocument document = frame->GetDocument();
-  if (document.IsNull()) {
-    *error = kNoWebstoreItemLinkFoundError;
-    return false;
-  }
-
-  WebElement head = document.Head();
-  if (head.IsNull()) {
-    *error = kNoWebstoreItemLinkFoundError;
-    return false;
-  }
-
-  GURL webstore_base_url =
-      GURL(extension_urls::GetWebstoreItemDetailURLPrefix());
-  for (WebNode child = head.FirstChild(); !child.IsNull();
-       child = child.NextSibling()) {
-    if (!child.IsElementNode())
-      continue;
-    WebElement elem = child.To<WebElement>();
-
-    if (!elem.HasHTMLTagName("link") || !elem.HasAttribute("rel") ||
-        !elem.HasAttribute("href"))
-      continue;
-
-    std::string rel = elem.GetAttribute("rel").Utf8();
-    if (!base::LowerCaseEqualsASCII(rel, kWebstoreLinkRelation))
-      continue;
-
-    std::string webstore_url_string(elem.GetAttribute("href").Utf8());
-
-    if (!preferred_store_link_url.empty() &&
-        preferred_store_link_url != webstore_url_string) {
-      continue;
-    }
-
-    GURL webstore_url = GURL(webstore_url_string);
-    if (!webstore_url.is_valid()) {
-      *error = kInvalidWebstoreItemUrlError;
-      return false;
-    }
-
-    if (webstore_url.scheme() != webstore_base_url.scheme() ||
-        webstore_url.host() != webstore_base_url.host() ||
-        !base::StartsWith(webstore_url.path(), webstore_base_url.path(),
-                          base::CompareCase::SENSITIVE)) {
-      *error = kInvalidWebstoreItemUrlError;
-      return false;
-    }
-
-    std::string candidate_webstore_item_id = webstore_url.path().substr(
-        webstore_base_url.path().length());
-    if (!crx_file::id_util::IdIsValid(candidate_webstore_item_id)) {
-      *error = kInvalidWebstoreItemUrlError;
-      return false;
-    }
-
-    std::string reconstructed_webstore_item_url_string =
-        extension_urls::GetWebstoreItemDetailURLPrefix() +
-            candidate_webstore_item_id;
-    if (reconstructed_webstore_item_url_string != webstore_url_string) {
-      *error = kInvalidWebstoreItemUrlError;
-      return false;
-    }
-
-    *webstore_item_id = candidate_webstore_item_id;
-    return true;
-  }
-
-  *error = kNoWebstoreItemLinkFoundError;
-  return false;
-}
-
-void WebstoreBindings::Invalidate() {
-  // We should close all mojo pipes when we invalidate the WebstoreBindings
-  // object and before its associated v8::context is destroyed. This is to
-  // ensure there are no mojo calls that try to access the v8::context after its
-  // destruction.
-  inline_installer_.reset();
-  install_progress_listener_bindings_.CloseAllBindings();
-  ObjectBackedNativeHandler::Invalidate();
-}
-
-}  // namespace extensions
diff --git a/chrome/renderer/extensions/webstore_bindings.h b/chrome/renderer/extensions/webstore_bindings.h
deleted file mode 100644
index 398dd01a..0000000
--- a/chrome/renderer/extensions/webstore_bindings.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_RENDERER_EXTENSIONS_WEBSTORE_BINDINGS_H_
-#define CHROME_RENDERER_EXTENSIONS_WEBSTORE_BINDINGS_H_
-
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "chrome/common/extensions/mojom/inline_install.mojom.h"
-#include "chrome/common/extensions/webstore_install_result.h"
-#include "extensions/renderer/object_backed_native_handler.h"
-#include "mojo/public/cpp/bindings/binding_set.h"
-#include "v8/include/v8.h"
-
-namespace blink {
-class WebLocalFrame;
-}
-
-namespace extensions {
-class ScriptContext;
-
-// A V8 extension that creates an object at window.chrome.webstore. This object
-// allows JavaScript to initiate inline installs of apps that are listed in the
-// Chrome Web Store (CWS).
-class WebstoreBindings : public ObjectBackedNativeHandler,
-                         public mojom::InlineInstallProgressListener {
- public:
-  explicit WebstoreBindings(ScriptContext* context);
-  ~WebstoreBindings() override;
-
-  // ObjectBackedNativeHandler:
-  void AddRoutes() override;
-
-  // mojom::InlineInstallProgressListener:
-  void InlineInstallStageChanged(api::webstore::InstallStage stage) override;
-  void InlineInstallDownloadProgress(int percent_downloaded) override;
-
- private:
-  void InlineInstallResponse(int install_id,
-                             bool success,
-                             const std::string& error,
-                             webstore_install::Result result);
-  void Install(const v8::FunctionCallbackInfo<v8::Value>& args);
-
-  // Extracts a Web Store item ID from a <link rel="chrome-webstore-item"
-  // href="https://chrome.google.com/webstore/detail/id"> node found in the
-  // frame. On success, true will be returned and the |webstore_item_id|
-  // parameter will be populated with the ID. On failure, false will be returned
-  // and |error| will be populated with the error.
-  static bool GetWebstoreItemIdFromFrame(
-      blink::WebLocalFrame* frame,
-      const std::string& preferred_store_link_url,
-      std::string* webstore_item_id,
-      std::string* error);
-
-  // ObjectBackedNativeHandler:
-  void Invalidate() override;
-
-  mojom::InlineInstallerAssociatedPtr inline_installer_;
-
-  mojo::BindingSet<mojom::InlineInstallProgressListener>
-      install_progress_listener_bindings_;
-
-  DISALLOW_COPY_AND_ASSIGN(WebstoreBindings);
-};
-
-}  // namespace extensions
-
-#endif  // CHROME_RENDERER_EXTENSIONS_WEBSTORE_BINDINGS_H_
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 5580282..3d223a2 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1410,12 +1410,10 @@
         "../browser/extensions/wake_event_page_apitest.cc",
         "../browser/extensions/wasm_app_browsertest.cc",
         "../browser/extensions/web_contents_browsertest.cc",
-        "../browser/extensions/webstore_inline_installer_browsertest.cc",
         "../browser/extensions/webstore_installer_browsertest.cc",
         "../browser/extensions/webstore_installer_test.cc",
         "../browser/extensions/webstore_installer_test.h",
         "../browser/extensions/webstore_reinstaller_browsertest.cc",
-        "../browser/extensions/webstore_startup_installer_browsertest.cc",
         "../browser/extensions/window_open_apitest.cc",
         "../browser/extensions/worker_apitest.cc",
         "../browser/notifications/notification_permission_context_apitest.cc",
@@ -3401,7 +3399,6 @@
       "../browser/sync/sync_error_notifier_ash_unittest.cc",
       "../browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc",
       "../browser/ui/ash/accessibility/ax_tree_source_aura_unittest.cc",
-      "../browser/ui/ash/browser_image_registrar_unittest.cc",
       "../browser/ui/ash/chrome_keyboard_ui_unittest.cc",
       "../browser/ui/ash/chrome_keyboard_web_contents_unittest.cc",
       "../browser/ui/ash/ime_controller_client_unittest.cc",
@@ -3603,7 +3600,6 @@
       "../browser/extensions/extension_prefs_unittest.cc",
       "../browser/extensions/extension_prefs_unittest.h",
       "../browser/extensions/extension_protocols_unittest.cc",
-      "../browser/extensions/extension_reenabler_unittest.cc",
       "../browser/extensions/extension_service_sync_unittest.cc",
       "../browser/extensions/extension_service_test_base.cc",
       "../browser/extensions/extension_service_test_base.h",
@@ -3646,7 +3642,6 @@
       "../browser/extensions/updater/extension_updater_unittest.cc",
       "../browser/extensions/user_script_listener_unittest.cc",
       "../browser/extensions/warning_badge_service_unittest.cc",
-      "../browser/extensions/webstore_inline_installer_unittest.cc",
       "../browser/extensions/webstore_installer_unittest.cc",
       "../browser/extensions/zipfile_installer_unittest.cc",
       "../browser/media/cast_transport_host_filter_unittest.cc",
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index 1d914c7..1da5c0c 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -108,8 +108,6 @@
 ]
 
 _VERSION_SPECIFIC_FILTER['69'] = [
-    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1945
-    'ChromeDriverTest.testWindowFullScreen',
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2515
     'HeadlessInvalidCertificateTest.*',
     # Feature not yet supported in this version
@@ -117,8 +115,6 @@
 ]
 
 _VERSION_SPECIFIC_FILTER['68'] = [
-    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1945
-    'ChromeDriverTest.testWindowFullScreen',
     # Feature not yet supported in this version
     'ChromeDriverTest.testGenerateTestReport',
 ]
@@ -141,6 +137,16 @@
     'ChromeDriverTest.testWindowMaximize',
 ]
 
+_OS_VERSION_SPECIFIC_FILTER = {}
+_OS_VERSION_SPECIFIC_FILTER['mac', '68'] = [
+    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1945
+    'ChromeDriverTest.testWindowFullScreen',
+]
+_OS_VERSION_SPECIFIC_FILTER['mac', '69'] = [
+    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1945
+    'ChromeDriverTest.testWindowFullScreen',
+]
+
 _DESKTOP_NEGATIVE_FILTER = [
     # Desktop doesn't support touch (without --touch-events).
     'ChromeDriverTest.testTouchSingleTapElement',
@@ -190,6 +196,8 @@
 def _GetDesktopNegativeFilter(version_name):
   filter = _NEGATIVE_FILTER + _DESKTOP_NEGATIVE_FILTER
   os = util.GetPlatformName()
+  if (os, version_name) in _OS_VERSION_SPECIFIC_FILTER:
+    filter += _OS_VERSION_SPECIFIC_FILTER[os, version_name]
   if os in _OS_SPECIFIC_FILTER:
     filter += _OS_SPECIFIC_FILTER[os]
   if version_name in _VERSION_SPECIFIC_FILTER:
diff --git a/chrome/test/data/webui/extensions/options_dialog_test.js b/chrome/test/data/webui/extensions/options_dialog_test.js
index a61a6bff..81ebdd1 100644
--- a/chrome/test/data/webui/extensions/options_dialog_test.js
+++ b/chrome/test/data/webui/extensions/options_dialog_test.js
@@ -42,19 +42,23 @@
       optionsDialog.show(data);
       return test_util.eventToPromise('cr-dialog-open', optionsDialog)
           .then(function() {
-            assertTrue(isDialogVisible());
+            // The dialog size is set asynchronously (see onpreferredsizechanged
+            // in options_dialog.js) so wait one frame.
+            requestAnimationFrame(function() {
+              assertTrue(isDialogVisible());
 
-            const dialogElement = optionsDialog.$.dialog.getNative();
-            const rect = dialogElement.getBoundingClientRect();
-            assertGE(rect.width, extensions.OptionsDialogMinWidth);
-            assertLE(rect.height, extensions.OptionsDialogMaxHeight);
-            // This is the header height with default font size.
-            assertGE(rect.height, 68);
+              const dialogElement = optionsDialog.$.dialog.getNative();
+              const rect = dialogElement.getBoundingClientRect();
+              assertGE(rect.width, extensions.OptionsDialogMinWidth);
+              assertLE(rect.height, extensions.OptionsDialogMaxHeight);
+              // This is the header height with default font size.
+              assertGE(rect.height, 68);
 
-            assertEquals(
-                data.name,
-                assert(optionsDialog.$$('#icon-and-name-wrapper span'))
-                    .textContent.trim());
+              assertEquals(
+                  data.name,
+                  assert(optionsDialog.$$('#icon-and-name-wrapper span'))
+                      .textContent.trim());
+            });
           });
     });
   });
diff --git a/chrome/test/data/webui/print_preview/destination_dialog_interactive_test.js b/chrome/test/data/webui/print_preview/destination_dialog_interactive_test.js
index acf839b..d0d7e78a 100644
--- a/chrome/test/data/webui/print_preview/destination_dialog_interactive_test.js
+++ b/chrome/test/data/webui/print_preview/destination_dialog_interactive_test.js
@@ -6,6 +6,7 @@
   /** @enum {string} */
   const TestNames = {
     FocusSearchBox: 'focus search box',
+    EscapeSearchBox: 'escape search box',
   };
 
   const suiteName = 'DestinationDialogInteractiveTest';
@@ -59,6 +60,56 @@
       dialog.show();
       return whenFocusDone;
     });
+
+    // Tests that pressing the escape key while the search box is focused
+    // closes the dialog if and only if the query is empty.
+    test(assert(TestNames.EscapeSearchBox), function() {
+      const searchInput = dialog.$.searchBox.getSearchInput();
+      assertTrue(!!searchInput);
+      const whenFocusDone = test_util.eventToPromise('focus', searchInput);
+      destinationStore.startLoadAllDestinations();
+      dialog.show();
+      return whenFocusDone
+          .then(() => {
+            assertTrue(dialog.$.dialog.open);
+
+            // Put something in the search box.
+            const whenSearchChanged =
+                test_util.eventToPromise('search-changed', dialog.$.searchBox);
+            dialog.$.searchBox.setValue('query');
+            return whenSearchChanged;
+          })
+          .then(() => {
+            assertEquals('query', searchInput.value);
+
+            // Simulate escape
+            const whenKeyDown = test_util.eventToPromise('keydown', dialog);
+            MockInteractions.keyDownOn(searchInput, 19, [], 'Escape');
+            return whenKeyDown;
+          })
+          .then(() => {
+            // Dialog should still be open.
+            assertTrue(dialog.$.dialog.open);
+
+            // Clear the search box.
+            const whenSearchChanged =
+                test_util.eventToPromise('search-changed', dialog.$.searchBox);
+            dialog.$.searchBox.setValue('');
+            return whenSearchChanged;
+          })
+          .then(() => {
+            assertEquals('', searchInput.value);
+
+            // Simulate escape
+            const whenKeyDown = test_util.eventToPromise('keydown', dialog);
+            MockInteractions.keyDownOn(searchInput, 19, [], 'Escape');
+            return whenKeyDown;
+          })
+          .then(() => {
+            // Dialog is closed.
+            assertFalse(dialog.$.dialog.open);
+          });
+    });
   });
 
   return {
diff --git a/chrome/test/data/webui/print_preview/number_settings_section_interactive_test.js b/chrome/test/data/webui/print_preview/number_settings_section_interactive_test.js
new file mode 100644
index 0000000..e0212ce
--- /dev/null
+++ b/chrome/test/data/webui/print_preview/number_settings_section_interactive_test.js
@@ -0,0 +1,73 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('number_settings_section_interactive_test', function() {
+  /** @enum {string} */
+  const TestNames = {
+    BlurResetsEmptyInput: 'blur resets empty input',
+  };
+
+  const suiteName = 'NumberSettingsSectionInteractiveTest';
+  suite(suiteName, function() {
+    /** @type {?PrintPreviewNumberSettingsSectionElement} */
+    let numberSettings = null;
+
+    /** @override */
+    setup(function() {
+      PolymerTest.clearBody();
+
+      document.body.innerHTML = `
+          <print-preview-number-settings-section id="numberSettings"
+              min-value="1" max-value="100" default-value="50"
+              current-value="10" hint-message="incorrect value entered"
+              input-valid>
+          </print-preview-number-settings-section>`;
+      numberSettings = document.querySelector('#numberSettings');
+    });
+
+    // Verifies that blurring the input will reset it to the default if it is
+    // empty, but not if it contains an invalid value.
+    test(assert(TestNames.BlurResetsEmptyInput), function() {
+      // Initial value is 10.
+      const crInput = numberSettings.getInput();
+      const input = crInput.inputElement;
+      assertEquals('10', input.value);
+
+      // Set something invalid in the input.
+      const whenFocused = test_util.eventToPromise('focus', input);
+      input.focus();
+      print_preview_test_utils.triggerInputEvent(input, '0');
+      return test_util.eventToPromise('input-change', numberSettings)
+          .then(() => {
+            assertEquals('0', input.value);
+            assertTrue(crInput.invalid);
+
+            // Blurring the input does not clear it or clear the error if there
+            // is an explicit invalid value.
+            input.blur();
+            assertEquals('0', input.value);
+            assertTrue(crInput.invalid);
+
+            // Clear the input.
+            input.focus();
+            print_preview_test_utils.triggerInputEvent(input, '');
+            return test_util.eventToPromise('input-change', numberSettings);
+          })
+          .then(() => {
+            assertEquals('', input.value);
+            assertFalse(crInput.invalid);
+
+            // Blurring the input clears it to the default when it is empty.
+            input.blur();
+            assertEquals('50', input.value);
+            assertFalse(crInput.invalid);
+          });
+    });
+  });
+
+  return {
+    suiteName: suiteName,
+    TestNames: TestNames,
+  };
+});
diff --git a/chrome/test/data/webui/print_preview/number_settings_section_test.js b/chrome/test/data/webui/print_preview/number_settings_section_test.js
index 52a3461..12c2db9 100644
--- a/chrome/test/data/webui/print_preview/number_settings_section_test.js
+++ b/chrome/test/data/webui/print_preview/number_settings_section_test.js
@@ -20,8 +20,8 @@
       document.body.innerHTML = `
         <div id="parentElement">
           <print-preview-number-settings-section id="numberSettings"
-              disabled="false" min-value="1" max-value="100" default-value="50"
-              hint-message="incorrect value entered" input-valid="true">
+              min-value="1" max-value="100" default-value="50"
+              hint-message="incorrect value entered" input-valid>
           </print-preview-number-settings-section>
         </div>`;
       parentElement = document.querySelector('#parentElement');
@@ -44,19 +44,31 @@
         return whenKeyDown;
       };
 
-      return sendKeyDownAndReturnPromise(69, 'e').then(e => {
-        assertTrue(e.defaultPrevented);
-        return sendKeyDownAndReturnPromise(110, '.');
-      }).then(e => {
-        assertTrue(e.defaultPrevented);
-        return sendKeyDownAndReturnPromise(109, '-');
-      }).then(e => {
-        assertTrue(e.defaultPrevented);
-        // Try a valid key.
-        return sendKeyDownAndReturnPromise(49, '1');
-      }).then(e => {
-        assertFalse(e.defaultPrevented);
-      });
+      return sendKeyDownAndReturnPromise(69, 'e')
+          .then(e => {
+            assertTrue(e.defaultPrevented);
+            return sendKeyDownAndReturnPromise(110, '.');
+          })
+          .then(e => {
+            assertTrue(e.defaultPrevented);
+            return sendKeyDownAndReturnPromise(109, '-');
+          })
+          .then(e => {
+            assertTrue(e.defaultPrevented);
+            return sendKeyDownAndReturnPromise(69, 'E');
+          })
+          .then(e => {
+            assertTrue(e.defaultPrevented);
+            return sendKeyDownAndReturnPromise(187, '+');
+          })
+          .then(e => {
+            assertTrue(e.defaultPrevented);
+            // Try a valid key.
+            return sendKeyDownAndReturnPromise(49, '1');
+          })
+          .then(e => {
+            assertFalse(e.defaultPrevented);
+          });
     });
   });
 
diff --git a/chrome/test/data/webui/print_preview/print_preview_interactive_ui_tests.js b/chrome/test/data/webui/print_preview/print_preview_interactive_ui_tests.js
index 7136481..baffb59 100644
--- a/chrome/test/data/webui/print_preview/print_preview_interactive_ui_tests.js
+++ b/chrome/test/data/webui/print_preview/print_preview_interactive_ui_tests.js
@@ -100,6 +100,13 @@
           destination_dialog_interactive_test.TestNames.FocusSearchBox);
     });
 
+TEST_F(
+    'PrintPreviewDestinationDialogInteractiveTest', 'EscapeSearchBox',
+    function() {
+      this.runMochaTest(
+          destination_dialog_interactive_test.TestNames.EscapeSearchBox);
+    });
+
 PrintPreviewPagesSettingsTest = class extends PrintPreviewInteractiveUITest {
   /** @override */
   get browsePreload() {
@@ -140,3 +147,32 @@
 TEST_F('PrintPreviewPagesSettingsTest', 'TabOrder', function() {
   this.runMochaTest(pages_settings_test.TestNames.TabOrder);
 });
+
+PrintPreviewNumberSettingsSectionInteractiveTest =
+    class extends PrintPreviewInteractiveUITest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://print/new/number_settings_section.html';
+  }
+
+  /** @override */
+  get extraLibraries() {
+    return super.extraLibraries.concat([
+      '../settings/test_util.js',
+      'print_preview_test_utils.js',
+      'number_settings_section_interactive_test.js',
+    ]);
+  }
+
+  /** @override */
+  get suiteName() {
+    return number_settings_section_interactive_test.suiteName;
+  }
+};
+
+TEST_F(
+    'PrintPreviewNumberSettingsSectionInteractiveTest', 'BlurResetsEmptyInput',
+    function() {
+      this.runMochaTest(number_settings_section_interactive_test.TestNames
+                            .BlurResetsEmptyInput);
+    });
diff --git a/chrome/test/data/webui/print_preview/print_preview_test_utils.js b/chrome/test/data/webui/print_preview/print_preview_test_utils.js
index 6d8e5a3..84f7bf1 100644
--- a/chrome/test/data/webui/print_preview/print_preview_test_utils.js
+++ b/chrome/test/data/webui/print_preview/print_preview_test_utils.js
@@ -281,6 +281,16 @@
     };
   }
 
+  /**
+   * @param {!HTMLInputElement} element
+   * @param {!string} input The value to set for the input element.
+   */
+  function triggerInputEvent(element, input) {
+    element.value = input;
+    element.dispatchEvent(
+        new CustomEvent('input', {composed: true, bubbles: true}));
+  }
+
   return {
     getDefaultInitialSettings: getDefaultInitialSettings,
     getCddTemplate: getCddTemplate,
@@ -293,5 +303,6 @@
     getMediaSizeCapabilityWithCustomNames:
         getMediaSizeCapabilityWithCustomNames,
     getPdfPrinter: getPdfPrinter,
+    triggerInputEvent: triggerInputEvent,
   };
 });
diff --git a/chrome/test/data/webui/print_preview/settings_section_test.js b/chrome/test/data/webui/print_preview/settings_section_test.js
index dc8332c4..42346ed 100644
--- a/chrome/test/data/webui/print_preview/settings_section_test.js
+++ b/chrome/test/data/webui/print_preview/settings_section_test.js
@@ -106,16 +106,6 @@
       moreSettingsElement.$.label.click();
     }
 
-    /**
-     * @param {!HTMLInputElement} element
-     * @param {!string} input The value to set for the input element.
-     */
-    function triggerInputEvent(element, input) {
-      element.value = input;
-      element.dispatchEvent(
-          new CustomEvent('input', {composed: true, bubbles: true}));
-    }
-
     test(assert(TestNames.Copies), function() {
       const copiesElement = page.$$('print-preview-copies-settings');
       assertFalse(copiesElement.hidden);
@@ -587,7 +577,7 @@
       // platforms.
       pagesElement.set('optionSelected_', pagesElement.pagesValueEnum_.CUSTOM);
 
-      triggerInputEvent(pagesInput, '1-2');
+      print_preview_test_utils.triggerInputEvent(pagesInput, '1-2');
       return test_util.eventToPromise('input-change', pagesElement)
           .then(function() {
             validateInputState(false, '1-2', true);
@@ -598,7 +588,7 @@
             assertTrue(page.settings.pages.valid);
 
             // Select pages 1 and 3
-            triggerInputEvent(pagesInput, '1, 3');
+            print_preview_test_utils.triggerInputEvent(pagesInput, '1, 3');
             return test_util.eventToPromise('input-change', pagesElement);
           })
           .then(function() {
@@ -612,7 +602,7 @@
             assertTrue(page.settings.pages.valid);
 
             // Enter an out of bounds value.
-            triggerInputEvent(pagesInput, '5');
+            print_preview_test_utils.triggerInputEvent(pagesInput, '5');
             return test_util.eventToPromise('input-change', pagesElement);
           })
           .then(function() {
@@ -635,7 +625,7 @@
       assertEquals(1, page.settings.copies.value);
 
       // Change to 2
-      triggerInputEvent(copiesInput, '2');
+      print_preview_test_utils.triggerInputEvent(copiesInput, '2');
       return test_util.eventToPromise('input-change', copiesElement)
           .then(function() {
             assertEquals(2, page.settings.copies.value);
@@ -650,6 +640,37 @@
             assertFalse(collateInput.checked);
             collateInput.dispatchEvent(new CustomEvent('change'));
             assertFalse(page.settings.collate.value);
+
+            // Set an empty value.
+            print_preview_test_utils.triggerInputEvent(copiesInput, '');
+            return test_util.eventToPromise('input-change', copiesElement);
+          })
+          .then(function() {
+            // Collate should be hidden now, but no update to the backing value
+            // occurs.
+            assertTrue(copiesElement.$$('.checkbox').hidden);
+            assertTrue(page.settings.copies.valid);
+            assertEquals(2, page.settings.copies.value);
+
+            // If the field is blurred, it will be reset to the default by the
+            // number-settings-section. Simulate this ocurring.
+            const numberSettingsSection =
+                copiesElement.$$('print-preview-number-settings-section');
+            numberSettingsSection.$.userValue.value = '1';
+            numberSettingsSection.currentValue = '1';
+            assertTrue(page.settings.copies.valid);
+            assertEquals(1, page.settings.copies.value);
+
+            // Enter an invalid value.
+            print_preview_test_utils.triggerInputEvent(copiesInput, '0');
+            return test_util.eventToPromise('input-change', copiesElement);
+          })
+          .then(function() {
+            // Collate should be hidden. Value is not updated to the invalid
+            // number. Setting is marked invalid.
+            assertTrue(copiesElement.$$('.checkbox').hidden);
+            assertFalse(page.settings.copies.valid);
+            assertEquals(1, page.settings.copies.value);
           });
     });
 
@@ -884,7 +905,7 @@
       validateScalingState('100', true, false, false);
 
       // Change to 105
-      triggerInputEvent(scalingInput, '105');
+      print_preview_test_utils.triggerInputEvent(scalingInput, '105');
       return test_util.eventToPromise('input-change', scalingElement)
           .then(function() {
             validateScalingState('105', true, false, false);
@@ -902,7 +923,7 @@
 
             // Set scaling. Should uncheck fit to page and set the settings for
             // scaling and fit to page.
-            triggerInputEvent(scalingInput, '95');
+            print_preview_test_utils.triggerInputEvent(scalingInput, '95');
             return test_util.eventToPromise('input-change', scalingElement);
           })
           .then(function() {
@@ -910,7 +931,7 @@
 
             // Set scaling to something invalid. Should change setting validity
             // but not value.
-            triggerInputEvent(scalingInput, '5');
+            print_preview_test_utils.triggerInputEvent(scalingInput, '5');
             return test_util.eventToPromise('input-change', scalingElement);
           })
           .then(function() {
@@ -951,7 +972,7 @@
             // change the stored value of scaling or fit to page, to avoid an
             // unnecessary preview regeneration, but should display fit to page
             // as unchecked.
-            triggerInputEvent(scalingInput, '9');
+            print_preview_test_utils.triggerInputEvent(scalingInput, '9');
             return test_util.eventToPromise('input-change', scalingElement);
           })
           .then(function() {
@@ -960,7 +981,7 @@
             // Enter a blank value in the scaling field. This should not
             // change the stored value of scaling or fit to page, to avoid an
             // unnecessary preview regeneration.
-            triggerInputEvent(scalingInput, '');
+            print_preview_test_utils.triggerInputEvent(scalingInput, '');
             return test_util.eventToPromise('input-change', scalingElement);
           })
           .then(function() {
@@ -968,7 +989,7 @@
 
             // Entering something valid unsets fit to page and sets scaling
             // valid to true.
-            triggerInputEvent(scalingInput, '90');
+            print_preview_test_utils.triggerInputEvent(scalingInput, '90');
             return test_util.eventToPromise('input-change', scalingElement);
           })
           .then(function() {
diff --git a/chrome/utility/importer/nss_decryptor_win.cc b/chrome/utility/importer/nss_decryptor_win.cc
index 587d3fe9..9b03397 100644
--- a/chrome/utility/importer/nss_decryptor_win.cc
+++ b/chrome/utility/importer/nss_decryptor_win.cc
@@ -15,7 +15,7 @@
 // effects of a previous SetDllDirectory call.
 class SetDllDirectoryCaller {
  public:
-  explicit SetDllDirectoryCaller() : func_(NULL) { }
+  SetDllDirectoryCaller() : func_(NULL) {}
 
   ~SetDllDirectoryCaller() {
     if (func_)
@@ -97,13 +97,18 @@
 }
 
 NSSDecryptor::NSSDecryptor()
-    : NSS_Init(NULL), NSS_Shutdown(NULL), PK11_GetInternalKeySlot(NULL),
-      PK11_CheckUserPassword(NULL), PK11_FreeSlot(NULL),
-      PK11_Authenticate(NULL), PK11SDR_Decrypt(NULL), SECITEM_FreeItem(NULL),
-      PL_ArenaFinish(NULL), PR_Cleanup(NULL),
-      nss3_dll_(NULL), softokn3_dll_(NULL),
-      is_nss_initialized_(false) {
-}
+    : NSS_Init(NULL),
+      NSS_Shutdown(NULL),
+      PK11_GetInternalKeySlot(NULL),
+      PK11_FreeSlot(NULL),
+      PK11_Authenticate(NULL),
+      PK11SDR_Decrypt(NULL),
+      SECITEM_FreeItem(NULL),
+      PL_ArenaFinish(NULL),
+      PR_Cleanup(NULL),
+      nss3_dll_(NULL),
+      softokn3_dll_(NULL),
+      is_nss_initialized_(false) {}
 
 NSSDecryptor::~NSSDecryptor() {
   Free();
diff --git a/chrome/utility/importer/nss_decryptor_win.h b/chrome/utility/importer/nss_decryptor_win.h
index edcf46c2..79bf16d3 100644
--- a/chrome/utility/importer/nss_decryptor_win.h
+++ b/chrome/utility/importer/nss_decryptor_win.h
@@ -166,7 +166,6 @@
   NSSInitFunc NSS_Init;
   NSSShutdownFunc NSS_Shutdown;
   PK11GetInternalKeySlotFunc PK11_GetInternalKeySlot;
-  PK11CheckUserPasswordFunc PK11_CheckUserPassword;
   PK11FreeSlotFunc PK11_FreeSlot;
   PK11AuthenticateFunc PK11_Authenticate;
   PK11SDRDecryptFunc PK11SDR_Decrypt;
diff --git a/chromecast/browser/cast_media_blocker_unittest.cc b/chromecast/browser/cast_media_blocker_unittest.cc
index dc4eb73d..4bb1a53 100644
--- a/chromecast/browser/cast_media_blocker_unittest.cc
+++ b/chromecast/browser/cast_media_blocker_unittest.cc
@@ -39,7 +39,11 @@
   MOCK_METHOD1(SetDuckingVolumeMultiplier, void(double));
   MOCK_METHOD1(DidReceiveAction, void(blink::mojom::MediaSessionAction));
   MOCK_METHOD1(AddObserver, void(content::MediaSessionObserver*));
+  MOCK_METHOD1(AddObserver,
+               void(media_session::mojom::MediaSessionObserverPtr));
   MOCK_METHOD1(RemoveObserver, void(content::MediaSessionObserver*));
+  MOCK_METHOD1(GetMediaSessionInfo, void(GetMediaSessionInfoCallback));
+  MOCK_METHOD1(GetDebugInfo, void(GetDebugInfoCallback));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockMediaSession);
diff --git a/chromecast/media/DEPS b/chromecast/media/DEPS
index 20744a9..88b5631 100644
--- a/chromecast/media/DEPS
+++ b/chromecast/media/DEPS
@@ -9,6 +9,7 @@
   "+media/audio",
   "+media/base",
   "+media/cdm",
+  "+media/filters",
   "+mojo/core/embedder/embedder.h",
   "+mojo/public/cpp/bindings/binding.h",
   "+ui/gfx/geometry",
diff --git a/chromecast/media/service/cast_mojo_media_client.cc b/chromecast/media/service/cast_mojo_media_client.cc
index 90bdd55..04cc924 100644
--- a/chromecast/media/service/cast_mojo_media_client.cc
+++ b/chromecast/media/service/cast_mojo_media_client.cc
@@ -4,13 +4,19 @@
 
 #include "chromecast/media/service/cast_mojo_media_client.h"
 
+#include "build/build_config.h"
 #include "chromecast/media/cma/backend/cma_backend_factory.h"
 #include "chromecast/media/service/cast_renderer.h"
 #include "chromecast/public/media/media_pipeline_backend.h"
+#include "media/base/audio_decoder.h"
 #include "media/base/cdm_factory.h"
 #include "media/base/media_log.h"
 #include "media/base/overlay_info.h"
 
+#if defined(OS_ANDROID)
+#include "media/filters/android/media_codec_audio_decoder.h"
+#endif  // defined(OS_ANDROID)
+
 namespace chromecast {
 namespace media {
 
@@ -53,5 +59,13 @@
   return create_cdm_factory_cb_.Run();
 }
 
+std::unique_ptr<::media::AudioDecoder> CastMojoMediaClient::CreateAudioDecoder(
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+#if defined(OS_ANDROID)
+  return std::make_unique<::media::MediaCodecAudioDecoder>(task_runner);
+#endif  // defined(OS_ANDROID)
+  return nullptr;
+}
+
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromecast/media/service/cast_mojo_media_client.h b/chromecast/media/service/cast_mojo_media_client.h
index 8b9065e0..dbc41d8 100644
--- a/chromecast/media/service/cast_mojo_media_client.h
+++ b/chromecast/media/service/cast_mojo_media_client.h
@@ -39,6 +39,8 @@
       const std::string& audio_device_id) override;
   std::unique_ptr<::media::CdmFactory> CreateCdmFactory(
       service_manager::mojom::InterfaceProvider* host_interfaces) override;
+  std::unique_ptr<::media::AudioDecoder> CreateAudioDecoder(
+      scoped_refptr<base::SingleThreadTaskRunner> task_runner) override;
 
  private:
   service_manager::Connector* connector_;
diff --git a/chromeos/services/media_perception/public/mojom/BUILD.gn b/chromeos/services/media_perception/public/mojom/BUILD.gn
index 23b62e6..a51e3a7 100644
--- a/chromeos/services/media_perception/public/mojom/BUILD.gn
+++ b/chromeos/services/media_perception/public/mojom/BUILD.gn
@@ -6,7 +6,8 @@
 
 mojom("mojom") {
   sources = [
-    "connector.mojom",
+    "media_perception.mojom",
+    "media_perception_service.mojom",
   ]
 
   public_deps = [
diff --git a/chromeos/services/media_perception/public/mojom/connector.mojom b/chromeos/services/media_perception/public/mojom/connector.mojom
deleted file mode 100644
index 929709c0..0000000
--- a/chromeos/services/media_perception/public/mojom/connector.mojom
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module chromeos.media_perception.mojom;
-
-import "services/video_capture/public/mojom/device_factory.mojom";
-
-interface Connector {
-  // Interface for a process running outside of Chrome to connect to the
-  // video capture service directly via a Mojo pipe set up through a
-  // DeviceFactory request.
-  ConnectToVideoCaptureService(video_capture.mojom.DeviceFactory& request);
-};
diff --git a/chromeos/services/media_perception/public/mojom/media_perception.mojom b/chromeos/services/media_perception/public/mojom/media_perception.mojom
new file mode 100644
index 0000000..8747054
--- /dev/null
+++ b/chromeos/services/media_perception/public/mojom/media_perception.mojom
@@ -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.
+
+module chromeos.media_perception.mojom;
+
+interface MediaPerception {
+};
diff --git a/chromeos/services/media_perception/public/mojom/media_perception_service.mojom b/chromeos/services/media_perception/public/mojom/media_perception_service.mojom
new file mode 100644
index 0000000..12151a5
--- /dev/null
+++ b/chromeos/services/media_perception/public/mojom/media_perception_service.mojom
@@ -0,0 +1,29 @@
+// 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.
+//
+// Next MinVersion: 1
+
+module chromeos.media_perception.mojom;
+
+import "chromeos/services/media_perception/public/mojom/media_perception.mojom";
+import "services/video_capture/public/mojom/device_factory.mojom";
+
+// Used to establish two-way communication between a client and the media
+// perception service.
+interface MediaPerceptionService {
+  GetController@0(MediaPerceptionController& request,
+                  MediaPerceptionControllerClient client);
+};
+
+interface MediaPerceptionController {
+  // Used by the client to establish a MediaPerception pipe.
+  ActivateMediaPerception@0(MediaPerception& request);
+};
+
+interface MediaPerceptionControllerClient {
+  // Interface for the service to connect to the Video Capture Service
+  // directly via a Mojo pipe set up through a DeviceFactory request.
+  ConnectToVideoCaptureService@0(video_capture.mojom.DeviceFactory& request);
+};
+
diff --git a/components/autofill/ios/browser/autofill_agent.mm b/components/autofill/ios/browser/autofill_agent.mm
index 13b03d91..fcf1a395 100644
--- a/components/autofill/ios/browser/autofill_agent.mm
+++ b/components/autofill/ios/browser/autofill_agent.mm
@@ -711,11 +711,6 @@
                     hasUserGesture:(BOOL)hasUserGesture
                    formInMainFrame:(BOOL)formInMainFrame
                            inFrame:(web::WebFrame*)frame {
-  if (!formInMainFrame) {
-    // Saving from iframes is not implemented.
-    return;
-  }
-
   if (![self isAutofillEnabled])
     return;
 
diff --git a/components/cronet/BUILD.gn b/components/cronet/BUILD.gn
index 2804fc69..87a8025 100644
--- a/components/cronet/BUILD.gn
+++ b/components/cronet/BUILD.gn
@@ -3,13 +3,17 @@
 # found in the LICENSE file.
 
 import("//build/buildflag_header.gni")
+import("//build/toolchain/toolchain.gni")
+import("//build/util/lastchange.gni")
 import("//build/util/process_version.gni")
 import("//build/util/version.gni")
+import("//components/cronet/native/include/headers.gni")
+import("//components/grpc_support/include/headers.gni")
 import("//testing/test.gni")
 
 declare_args() {
   # If set to true, this will remove histogram manager to reduce binary size.
-  disable_histogram_support = false
+  disable_histogram_support = is_mac || is_win
 }
 
 # Disable histogram support is not allowed on Android.
@@ -170,4 +174,72 @@
       "run_all_unittests.cc",
     ]
   }
+
+  _package_dir = "$root_out_dir/cronet"
+
+  # Generate LICENSE file by recursively joining all dependent licenses.
+  action("generate_license") {
+    _license_path = "$_package_dir/LICENSE"
+
+    script = "//tools/licenses.py"
+    inputs = [
+      lastchange_file,
+    ]
+    outputs = [
+      _license_path,
+    ]
+    args = [
+      "license_file",
+      rebase_path(_license_path, root_build_dir),
+      "--gn-target",
+      "//components/cronet:cronet",
+      "--gn-out-dir",
+      ".",
+    ]
+  }
+
+  # Copy boiler-plate files into the package.
+  copy("cronet_package_copy") {
+    sources = [
+      "//AUTHORS",
+      "//chrome/VERSION",
+    ]
+
+    outputs = [
+      "$_package_dir/{{source_file_part}}",
+    ]
+  }
+
+  # Copy shared library adding the version to the file name.
+  copy("cronet_package_shlib") {
+    sources = [
+      "$root_out_dir/${shlib_prefix}cronet${shlib_extension}",
+    ]
+
+    outputs = [
+      "$_package_dir/${shlib_prefix}cronet.${chrome_version_full}${shlib_extension}",
+    ]
+
+    deps = [
+      ":cronet",
+    ]
+  }
+
+  # Copy headers.
+  copy("cronet_package_headers") {
+    sources = cronet_native_public_headers + grpc_public_headers
+
+    outputs = [
+      "$_package_dir/include/{{source_file_part}}",
+    ]
+  }
+
+  group("cronet_package") {
+    deps = [
+      ":cronet_package_copy",
+      ":cronet_package_headers",
+      ":cronet_package_shlib",
+      ":generate_license",
+    ]
+  }
 }
diff --git a/components/cronet/cronet_url_request_context.cc b/components/cronet/cronet_url_request_context.cc
index adce07a..a3aa286 100644
--- a/components/cronet/cronet_url_request_context.cc
+++ b/components/cronet/cronet_url_request_context.cc
@@ -34,7 +34,6 @@
 #include "build/build_config.h"
 #include "components/cronet/cronet_global_state.h"
 #include "components/cronet/cronet_prefs_manager.h"
-#include "components/cronet/histogram_manager.h"
 #include "components/cronet/host_cache_persistence_manager.h"
 #include "components/cronet/url_request_context_config.h"
 #include "net/base/ip_address.h"
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc
index b898de0e..7e5dc6c 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc
@@ -71,17 +71,6 @@
                          base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN));
 #endif
 
-// Values of the UMA DataReductionProxy.Protocol.NotAcceptingTransform histogram
-// defined in metrics/histograms/histograms.xml. This enum must remain
-// synchronized with DataReductionProxyProtocolNotAcceptingTransformReason in
-// tools/metrics/histograms/enums.xml.
-enum NotAcceptingTransformReason {
-  NOT_ACCEPTING_TRANSFORM_DISABLED = 0,
-  NOT_ACCEPTING_TRANSFORM_BLACKLISTED = 1,
-  NOT_ACCEPTING_TRANSFORM_CELLULAR_ONLY = 2,
-  NOT_ACCEPTING_TRANSFORM_REASON_BOUNDARY
-};
-
 // Values of the UMA DataReductionProxy.NetworkChangeEvents histograms.
 // This enum must remain synchronized with the enum of the same
 // name in metrics/histograms/histograms.xml.
@@ -209,7 +198,6 @@
       configurator_(configurator),
       event_creator_(event_creator),
       connection_type_(network::mojom::ConnectionType::CONNECTION_UNKNOWN),
-      ignore_long_term_black_list_rules_(false),
       network_properties_manager_(nullptr),
       weak_factory_(this) {
   DCHECK(io_task_runner_);
@@ -792,46 +780,6 @@
   return enabled_by_user_ && !unreachable_;
 }
 
-bool DataReductionProxyConfig::IsBlackListedOrDisabled(
-    const net::URLRequest& request,
-    const previews::PreviewsDecider& previews_decider,
-    previews::PreviewsType previews_type) const {
-  // Make sure request is not locally blacklisted.
-  // Pass in net::EFFECTIVE_CONNECTION_TYPE_4G as the threshold since we
-  // just want to check blacklisting here.
-  // TODO(crbug.com/720102): Consider new method to just check blacklist.
-  return !previews_decider.ShouldAllowPreviewAtECT(
-      request, previews_type, net::EFFECTIVE_CONNECTION_TYPE_4G,
-      std::vector<std::string>(), ignore_long_term_black_list_rules_);
-}
-
-bool DataReductionProxyConfig::ShouldAcceptServerPreview(
-    const net::URLRequest& request,
-    const previews::PreviewsDecider& previews_decider) const {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  DCHECK((request.load_flags() & net::LOAD_MAIN_FRAME_DEPRECATED) != 0);
-  DCHECK(request.url().SchemeIsHTTPOrHTTPS());
-
-  if (!previews::params::ArePreviewsAllowed() ||
-      !base::FeatureList::IsEnabled(
-          features::kDataReductionProxyDecidesTransform)) {
-    return false;
-  }
-
-  if (IsBlackListedOrDisabled(request, previews_decider,
-                              previews::PreviewsType::LITE_PAGE) ||
-      IsBlackListedOrDisabled(request, previews_decider,
-                              previews::PreviewsType::LOFI)) {
-    UMA_HISTOGRAM_ENUMERATION(
-        "DataReductionProxy.Protocol.NotAcceptingTransform",
-        NOT_ACCEPTING_TRANSFORM_BLACKLISTED,
-        NOT_ACCEPTING_TRANSFORM_REASON_BOUNDARY);
-    return false;
-  }
-
-  return true;
-}
-
 base::TimeTicks DataReductionProxyConfig::GetTicksNow() const {
   DCHECK(thread_checker_.CalledOnValidThread());
   return base::TimeTicks::Now();
@@ -888,15 +836,4 @@
 }
 #endif  // defined(OS_CHROMEOS)
 
-void DataReductionProxyConfig::SetIgnoreLongTermBlackListRules(
-    bool ignore_long_term_black_list_rules) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  ignore_long_term_black_list_rules_ = ignore_long_term_black_list_rules;
-}
-
-bool DataReductionProxyConfig::IgnoreBlackListLongTermRulesForTesting() const {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  return ignore_long_term_black_list_rules_;
-}
-
 }  // namespace data_reduction_proxy
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h
index 4d68166..33665e8 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h
@@ -43,10 +43,6 @@
 class URLRequestStatus;
 }  // namespace net
 
-namespace previews {
-class PreviewsDecider;
-}
-
 namespace data_reduction_proxy {
 
 class DataReductionProxyConfigValues;
@@ -159,15 +155,6 @@
   virtual bool ContainsDataReductionProxy(
       const net::ProxyConfig::ProxyRules& proxy_rules) const;
 
-  // Returns whether the client should report to the data reduction proxy that
-  // it is willing to accept server previews for |request|.
-  // |previews_decider| is used to check if |request| is locally blacklisted.
-  // Should only be used if the kDataReductionProxyDecidesTransform feature is
-  // enabled.
-  bool ShouldAcceptServerPreview(
-      const net::URLRequest& request,
-      const previews::PreviewsDecider& previews_decider) const;
-
   // Returns true if the data saver has been enabled by the user, and the data
   // saver proxy is reachable.
   bool enabled_by_user_and_reachable() const;
@@ -202,19 +189,12 @@
   // TODO(https://crbug.com/821607): Remove after the bug is resolved.
   void EnableGetNetworkIdAsynchronously();
 #endif  // defined(OS_CHROMEOS)
-
-  // When triggering previews, prevent long term black list rules.
-  void SetIgnoreLongTermBlackListRules(bool ignore_long_term_black_list_rules);
-
   // Called when there is a change in the HTTP RTT estimate.
   void OnRTTOrThroughputEstimatesComputed(base::TimeDelta http_rtt);
 
   // Returns the current HTTP RTT estimate.
   base::Optional<base::TimeDelta> GetHttpRttEstimate() const;
 
-  // Returns the value set in SetIgnoreLongTermBlackListRules.
-  bool IgnoreBlackListLongTermRulesForTesting() const;
-
  protected:
   virtual base::TimeTicks GetTicksNow() const;
 
@@ -312,11 +292,6 @@
                           bool is_https,
                           base::TimeDelta* min_retry_delay) const;
 
-  // Returns whether the request is blacklisted (or if Lo-Fi is disabled).
-  bool IsBlackListedOrDisabled(
-      const net::URLRequest& request,
-      const previews::PreviewsDecider& previews_decider,
-      previews::PreviewsType previews_type) const;
 
   // Checks if the current network has captive portal, and handles the result.
   // If the captive portal probe was blocked on the current network, disables
@@ -378,9 +353,6 @@
   bool warmup_url_fetch_in_flight_secure_proxy_;
   bool warmup_url_fetch_in_flight_core_proxy_;
 
-  // When triggerring previews, prevent long term black list rules.
-  bool ignore_long_term_black_list_rules_;
-
   // Should be accessed only on the IO thread. Guaranteed to be non-null during
   // the lifetime of |this| if accessed on the IO thread.
   NetworkPropertiesManager* network_properties_manager_;
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.cc
index 3c5c6458..0c401ed 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client.cc
@@ -606,7 +606,7 @@
   if (!config.has_proxy_config())
     return false;
 
-  config_->SetIgnoreLongTermBlackListRules(
+  io_data_->SetIgnoreLongTermBlackListRules(
       config.ignore_long_term_black_list_rules());
 
   // An empty proxy config is OK, and allows the server to effectively turn off
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client_unittest.cc
index 90ad9110..e70debbe 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_service_client_unittest.cc
@@ -348,6 +348,10 @@
     return test_context_->io_data()->pingback_reporting_fraction();
   }
 
+  bool ignore_blacklist() const {
+    return test_context_->io_data()->ignore_blacklist();
+  }
+
   void RunUntilIdle() {
     test_context_->RunUntilIdle();
   }
@@ -1358,7 +1362,7 @@
   config_client()->ApplySerializedConfig(
       half_reporting_fraction_encoded_config());
   EXPECT_EQ(0.5f, pingback_reporting_fraction());
-  EXPECT_FALSE(config()->IgnoreBlackListLongTermRulesForTesting());
+  EXPECT_FALSE(ignore_blacklist());
 }
 
 TEST_F(DataReductionProxyConfigServiceClientTest,
@@ -1366,7 +1370,7 @@
   Init(true);
 
   config_client()->ApplySerializedConfig(ignore_black_list_encoded_config());
-  EXPECT_TRUE(config()->IgnoreBlackListLongTermRulesForTesting());
+  EXPECT_TRUE(ignore_blacklist());
 }
 
 TEST_F(DataReductionProxyConfigServiceClientTest, EmptyConfigDisablesDRP) {
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc
index e35d1b5..0b08eb0 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc
@@ -874,84 +874,6 @@
   }
 }
 
-TEST_F(DataReductionProxyConfigTest,
-       ShouldAcceptServerPreviewAllPreviewsDisabled) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitFromCommandLine(
-      "DataReductionProxyDecidesTransform" /* enable_features */,
-      "Previews" /* disable_features */);
-
-  net::TestURLRequestContext context;
-  net::TestDelegate delegate;
-  std::unique_ptr<net::URLRequest> request =
-      context.CreateRequest(GURL("http://origin.net:80"
-                                 ""),
-                            net::IDLE, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS);
-  request->SetLoadFlags(request->load_flags() |
-                        net::LOAD_MAIN_FRAME_DEPRECATED);
-  std::unique_ptr<previews::TestPreviewsDecider> previews_decider =
-      std::make_unique<previews::TestPreviewsDecider>(true);
-  EXPECT_FALSE(
-      test_config()->ShouldAcceptServerPreview(*request, *previews_decider));
-}
-
-TEST_F(DataReductionProxyConfigTest,
-       ShouldAcceptServerPreviewServerPreviewsDisabled) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitFromCommandLine(
-      "Previews" /* enable_features */,
-      "DataReductionProxyDecidesTransform" /* disable_features */);
-
-  net::TestURLRequestContext context;
-  net::TestDelegate delegate;
-  std::unique_ptr<net::URLRequest> request =
-      context.CreateRequest(GURL("http://origin.net:80"
-                                 ""),
-                            net::IDLE, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS);
-  request->SetLoadFlags(request->load_flags() |
-                        net::LOAD_MAIN_FRAME_DEPRECATED);
-  std::unique_ptr<previews::TestPreviewsDecider> previews_decider =
-      std::make_unique<previews::TestPreviewsDecider>(true);
-  EXPECT_FALSE(
-      test_config()->ShouldAcceptServerPreview(*request, *previews_decider));
-}
-
-TEST_F(DataReductionProxyConfigTest, ShouldAcceptServerPreview) {
-  // Turn on proxy-decides-transform feature to satisfy DCHECK.
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitFromCommandLine(
-      "Previews,DataReductionProxyDecidesTransform" /* enable_features */,
-      std::string() /* disable_features */);
-  base::FieldTrialList field_trial_list(nullptr);
-  base::FieldTrialList::CreateFieldTrial(
-      "DataReductionProxyPreviewsBlackListTransition", "Enabled");
-
-  base::HistogramTester histogram_tester;
-  net::TestURLRequestContext context_;
-  net::TestDelegate delegate_;
-  std::unique_ptr<net::URLRequest> request =
-      context_.CreateRequest(GURL("http://origin.net:80"), net::IDLE,
-                             &delegate_, TRAFFIC_ANNOTATION_FOR_TESTS);
-  request->SetLoadFlags(request->load_flags() |
-                        net::LOAD_MAIN_FRAME_DEPRECATED);
-  std::unique_ptr<previews::TestPreviewsDecider> previews_decider =
-      std::make_unique<previews::TestPreviewsDecider>(true);
-
-  // Verify true for no flags.
-  EXPECT_TRUE(
-      test_config()->ShouldAcceptServerPreview(*request, *previews_decider));
-
-  // Verify PreviewsDecider check.
-  base::CommandLine::ForCurrentProcess()->InitFromArgv(0, nullptr);
-  previews_decider = std::make_unique<previews::TestPreviewsDecider>(false);
-  EXPECT_FALSE(
-      test_config()->ShouldAcceptServerPreview(*request, *previews_decider));
-  histogram_tester.ExpectBucketCount(
-      "DataReductionProxy.Protocol.NotAcceptingTransform",
-      1 /* NOT_ACCEPTING_TRANSFORM_BLACKLISTED */, 1);
-  previews_decider = std::make_unique<previews::TestPreviewsDecider>(true);
-}
-
 TEST_F(DataReductionProxyConfigTest, HandleWarmupFetcherResponse) {
   base::HistogramTester histogram_tester;
   const net::URLRequestStatus kSuccess(net::URLRequestStatus::SUCCESS, net::OK);
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.cc
index 8ee53dc8..d0987c1 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.cc
@@ -30,7 +30,6 @@
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_pref_names.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h"
-#include "components/previews/core/previews_decider.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_request_headers.h"
 #include "net/url_request/http_user_agent_settings.h"
@@ -340,15 +339,13 @@
     config_client_->ApplySerializedConfig(serialized_config);
 }
 
-bool DataReductionProxyIOData::ShouldAcceptServerPreview(
-    const net::URLRequest& request,
-    previews::PreviewsDecider* previews_decider) {
-  DCHECK(previews_decider);
-  DCHECK((request.load_flags() & net::LOAD_MAIN_FRAME_DEPRECATED) != 0);
-  if (!config_ || !request.url().SchemeIsHTTPOrHTTPS()) {
-    return false;
-  }
-  return config_->ShouldAcceptServerPreview(request, *previews_decider);
+void DataReductionProxyIOData::SetIgnoreLongTermBlackListRules(
+    bool ignore_long_term_black_list_rules) {
+  ui_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &DataReductionProxyService::SetIgnoreLongTermBlackListRules, service_,
+          ignore_long_term_black_list_rules));
 }
 
 void DataReductionProxyIOData::UpdateDataUseForHost(int64_t network_bytes,
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.h
index 171a49b4..79b7c7dc 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.h
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.h
@@ -41,10 +41,6 @@
 class NetworkConnectionTracker;
 }
 
-namespace previews {
-class PreviewsDecider;
-}
-
 namespace data_reduction_proxy {
 
 class DataReductionProxyBypassStats;
@@ -107,12 +103,10 @@
   // Applies a serialized Data Reduction Proxy configuration.
   void SetDataReductionProxyConfiguration(const std::string& serialized_config);
 
-  // Returns true when server previews should be activated. When server previews
-  // are active, URL requests are modified to request low fidelity versions of
-  // the resources.|previews_decider| is a non-null object that determines
-  // eligibility of showing the preview based on past opt outs.
-  bool ShouldAcceptServerPreview(const net::URLRequest& request,
-                                 previews::PreviewsDecider* previews_decider);
+  // When triggering previews, prevent long term black list rules. Overridden in
+  // testing.
+  virtual void SetIgnoreLongTermBlackListRules(
+      bool ignore_long_term_black_list_rules);
 
   // Bridge methods to safely call to the UI thread objects.
   void UpdateDataUseForHost(int64_t network_bytes,
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc
index 2fb9cb23..2c1c7cdc 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc
@@ -913,11 +913,6 @@
 
 TEST_F(DataReductionProxyNetworkDelegateTest, LoFiTransitions) {
   Init(USE_INSECURE_PROXY, false);
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures(
-      {previews::features::kPreviews,
-       features::kDataReductionProxyDecidesTransform},
-      {});
 
   // Enable Lo-Fi.
   bool is_data_reduction_proxy_enabled[] = {false, true};
@@ -945,16 +940,13 @@
       std::unique_ptr<net::URLRequest> fake_request = context()->CreateRequest(
           GURL(kTestURL), net::IDLE, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS);
       fake_request->SetLoadFlags(net::LOAD_MAIN_FRAME_DEPRECATED);
-      lofi_decider()->SetIsUsingLoFi(config()->ShouldAcceptServerPreview(
-          *fake_request, test_previews_decider));
+      lofi_decider()->SetIsUsingLoFi(true);
       NotifyNetworkDelegate(fake_request.get(), data_reduction_proxy_info,
                             proxy_retry_info, &headers);
 
       VerifyHeaders(is_data_reduction_proxy_enabled[i], true, headers);
       VerifyDataReductionProxyData(*fake_request,
-                                   is_data_reduction_proxy_enabled[i],
-                                   config()->ShouldAcceptServerPreview(
-                                       *fake_request, test_previews_decider));
+                                   is_data_reduction_proxy_enabled[i], true);
     }
 
     {
@@ -1028,14 +1020,11 @@
       std::unique_ptr<net::URLRequest> fake_request = context()->CreateRequest(
           GURL(kTestURL), net::IDLE, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS);
       fake_request->SetLoadFlags(net::LOAD_MAIN_FRAME_DEPRECATED);
-      lofi_decider()->SetIsUsingLoFi(config()->ShouldAcceptServerPreview(
-          *fake_request, test_previews_decider));
+      lofi_decider()->SetIsUsingLoFi(true);
       NotifyNetworkDelegate(fake_request.get(), data_reduction_proxy_info,
                             proxy_retry_info, &headers);
       VerifyDataReductionProxyData(*fake_request,
-                                   is_data_reduction_proxy_enabled[i],
-                                   config()->ShouldAcceptServerPreview(
-                                       *fake_request, test_previews_decider));
+                                   is_data_reduction_proxy_enabled[i], true);
     }
   }
 }
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.cc
index f4fa66ea..3f2e4d5 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.cc
@@ -275,6 +275,12 @@
   settings_->SetProxyRequestHeaders(headers);
 }
 
+void DataReductionProxyService::SetIgnoreLongTermBlackListRules(
+    bool ignore_long_term_black_list_rules) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  settings_->SetIgnoreLongTermBlackListRules(ignore_long_term_black_list_rules);
+}
+
 void DataReductionProxyService::LoadHistoricalDataUsage(
     const HistoricalDataUsageCallback& load_data_usage_callback) {
   std::unique_ptr<std::vector<DataUsageBucket>> data_usage(
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h
index 5f7e563..320e0097 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h
@@ -143,6 +143,9 @@
   // cleared.
   void OnCacheCleared(const base::Time start, const base::Time end);
 
+  // When triggering previews, prevent long term black list rules.
+  void SetIgnoreLongTermBlackListRules(bool ignore_long_term_black_list_rules);
+
   // Returns the current network quality estimates.
   net::EffectiveConnectionType GetEffectiveConnectionType() const;
   base::Optional<base::TimeDelta> GetHttpRttEstimate() const;
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h
index feafcc4..a0289618 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h
@@ -143,6 +143,10 @@
   // some of them should have.
   bool IsDataReductionProxyUnreachable();
 
+  // When triggering previews, prevent long term black list rules.
+  virtual void SetIgnoreLongTermBlackListRules(
+      bool ignore_long_term_black_list_rules) {}
+
   ContentLengthList GetDailyContentLengths(const char* pref_name);
 
   // Configures data reduction proxy. |at_startup| is true when this method is
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.cc
index 79f146d8..f206866 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.cc
@@ -294,6 +294,11 @@
   pingback_reporting_fraction_ = pingback_reporting_fraction;
 }
 
+void TestDataReductionProxyIOData::SetIgnoreLongTermBlackListRules(
+    bool ignore_long_term_black_list_rules) {
+  ignore_blacklist_ = ignore_long_term_black_list_rules;
+}
+
 void TestDataReductionProxyIOData::SetDataReductionProxyService(
     base::WeakPtr<DataReductionProxyService> data_reduction_proxy_service) {
   if (!service_set_)
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.h b/components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.h
index 4537187..157997d 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.h
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.h
@@ -263,10 +263,16 @@
   // Records the reporting fraction that was set by parsing a config.
   void SetPingbackReportingFraction(float pingback_reporting_fraction) override;
 
+  // Records |ignore_long_term_black_list_rules| as |ignore_blacklist_|.
+  void SetIgnoreLongTermBlackListRules(
+      bool ignore_long_term_black_list_rules) override;
+
   float pingback_reporting_fraction() const {
     return pingback_reporting_fraction_;
   }
 
+  bool ignore_blacklist() const { return ignore_blacklist_; }
+
  private:
   // Allowed SetDataReductionProxyService to be re-entrant.
   bool service_set_;
@@ -274,6 +280,9 @@
   // Reporting fraction last set via SetPingbackReportingFraction.
   float pingback_reporting_fraction_;
 
+  // Whether the long term blacklist rules should be ignored.
+  bool ignore_blacklist_ = false;
+
   TestDataReductionProxyRequestOptions* test_request_options_;
 };
 
diff --git a/components/feature_engagement/public/event_constants.cc b/components/feature_engagement/public/event_constants.cc
index 3700e12..59e6061 100644
--- a/components/feature_engagement/public/event_constants.cc
+++ b/components/feature_engagement/public/event_constants.cc
@@ -22,6 +22,7 @@
 const char kIncognitoWindowSessionTimeMet[] =
     "incognito_window_session_time_met";
 
+const char kReopenTabConditionsMet[] = "reopen_tab_conditions_met";
 #endif  // BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
 
 #if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_IOS)
diff --git a/components/feature_engagement/public/event_constants.h b/components/feature_engagement/public/event_constants.h
index d45d76a..45c30820 100644
--- a/components/feature_engagement/public/event_constants.h
+++ b/components/feature_engagement/public/event_constants.h
@@ -40,6 +40,11 @@
 // IncognitoWindowPromo by accumulating 2 hours of active session time (one-off
 // event).
 extern const char kIncognitoWindowSessionTimeMet[];
+
+// All conditions for reopen closed tab IPH were met. Since this IPH needs to
+// track user events (opening/closing tabs, focusing the omnibox, etc) on the
+// second level, it must be done manually.
+extern const char kReopenTabConditionsMet[];
 #endif  // BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
 
 #if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_IOS)
diff --git a/components/feature_engagement/public/feature_constants.cc b/components/feature_engagement/public/feature_constants.cc
index 5f7aad7e..5c33fea 100644
--- a/components/feature_engagement/public/feature_constants.cc
+++ b/components/feature_engagement/public/feature_constants.cc
@@ -66,6 +66,8 @@
     "IPH_IncognitoWindow", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kIPHNewTabFeature{"IPH_NewTab",
                                       base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kIPHReopenTabFeature{"IPH_ReopenTab",
+                                         base::FEATURE_DISABLED_BY_DEFAULT};
 #endif  // BUILDFLAG(ENABLE_DESKTOP_IPH)
 
 #if defined(OS_IOS)
diff --git a/components/feature_engagement/public/feature_constants.h b/components/feature_engagement/public/feature_constants.h
index 93af7eb..bce43b7 100644
--- a/components/feature_engagement/public/feature_constants.h
+++ b/components/feature_engagement/public/feature_constants.h
@@ -48,6 +48,7 @@
 extern const base::Feature kIPHBookmarkFeature;
 extern const base::Feature kIPHIncognitoWindowFeature;
 extern const base::Feature kIPHNewTabFeature;
+extern const base::Feature kIPHReopenTabFeature;
 #endif  // BUILDFLAG(ENABLE_DESKTOP_IPH)
 
 #if defined(OS_IOS)
diff --git a/components/feature_engagement/public/feature_list.cc b/components/feature_engagement/public/feature_list.cc
index 6dd4be7..932a6d9 100644
--- a/components/feature_engagement/public/feature_list.cc
+++ b/components/feature_engagement/public/feature_list.cc
@@ -42,6 +42,7 @@
     &kIPHBookmarkFeature,
     &kIPHIncognitoWindowFeature,
     &kIPHNewTabFeature,
+    &kIPHReopenTabFeature,
 #endif  // BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
 #if defined(OS_IOS)
     &kIPHBottomToolbarTipFeature,
diff --git a/components/feature_engagement/public/feature_list.h b/components/feature_engagement/public/feature_list.h
index 6f834901..334238c5 100644
--- a/components/feature_engagement/public/feature_list.h
+++ b/components/feature_engagement/public/feature_list.h
@@ -81,6 +81,7 @@
 DEFINE_VARIATION_PARAM(kIPHBookmarkFeature, "IPH_Bookmark");
 DEFINE_VARIATION_PARAM(kIPHIncognitoWindowFeature, "IPH_IncognitoWindow");
 DEFINE_VARIATION_PARAM(kIPHNewTabFeature, "IPH_NewTab");
+DEFINE_VARIATION_PARAM(kIPHReopenTabFeature, "IPH_ReopenTab");
 #endif  // BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
 #if defined(OS_IOS)
 DEFINE_VARIATION_PARAM(kIPHBottomToolbarTipFeature, "IPH_BottomToolbarTip");
@@ -124,6 +125,7 @@
         VARIATION_ENTRY(kIPHBookmarkFeature),
         VARIATION_ENTRY(kIPHIncognitoWindowFeature),
         VARIATION_ENTRY(kIPHNewTabFeature),
+        VARIATION_ENTRY(kIPHReopenTabFeature),
 #elif defined(OS_IOS)
         VARIATION_ENTRY(kIPHBottomToolbarTipFeature),
         VARIATION_ENTRY(kIPHLongPressToolbarTipFeature),
diff --git a/components/previews/content/previews_decider_impl.cc b/components/previews/content/previews_decider_impl.cc
index a62125b..996ccac 100644
--- a/components/previews/content/previews_decider_impl.cc
+++ b/components/previews/content/previews_decider_impl.cc
@@ -112,6 +112,7 @@
     std::unique_ptr<PreviewsOptimizationGuide> previews_opt_guide,
     const PreviewsIsEnabledCallback& is_enabled_callback,
     blacklist::BlacklistData::AllowedTypesAndVersions allowed_previews) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(ui_task_runner_->BelongsToCurrentThread());
   is_enabled_callback_ = is_enabled_callback;
   previews_ui_service_ = previews_ui_service;
@@ -127,6 +128,7 @@
 
 void PreviewsDeciderImpl::OnNewBlacklistedHost(const std::string& host,
                                                base::Time time) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(io_task_runner_->BelongsToCurrentThread());
   ui_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&PreviewsUIService::OnNewBlacklistedHost,
@@ -134,6 +136,7 @@
 }
 
 void PreviewsDeciderImpl::OnUserBlacklistedStatusChange(bool blacklisted) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(io_task_runner_->BelongsToCurrentThread());
   ui_task_runner_->PostTask(
       FROM_HERE,
@@ -142,6 +145,7 @@
 }
 
 void PreviewsDeciderImpl::OnBlacklistCleared(base::Time time) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(io_task_runner_->BelongsToCurrentThread());
   ui_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&PreviewsUIService::OnBlacklistCleared,
@@ -151,6 +155,7 @@
 void PreviewsDeciderImpl::InitializeOnIOThread(
     std::unique_ptr<blacklist::OptOutStore> previews_opt_out_store,
     blacklist::BlacklistData::AllowedTypesAndVersions allowed_previews) {
+  DETACH_FROM_SEQUENCE(sequence_checker_);
   DCHECK(io_task_runner_->BelongsToCurrentThread());
   previews_black_list_.reset(
       new PreviewsBlackList(std::move(previews_opt_out_store), clock_, this,
@@ -164,6 +169,7 @@
 void PreviewsDeciderImpl::OnResourceLoadingHints(
     const GURL& document_gurl,
     const std::vector<std::string>& patterns_to_block) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(io_task_runner_->BelongsToCurrentThread());
   ui_task_runner_->PostTask(
       FROM_HERE,
@@ -182,6 +188,7 @@
                                                PreviewsType type,
                                                base::Time time,
                                                uint64_t page_id) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   ui_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&PreviewsUIService::LogPreviewNavigation,
@@ -195,6 +202,7 @@
     PreviewsType type,
     std::vector<PreviewsEligibilityReason>&& passed_reasons,
     uint64_t page_id) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   LogPreviewsEligibilityReason(reason, type);
   ui_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&PreviewsUIService::LogPreviewDecisionMade,
@@ -206,6 +214,7 @@
                                                bool opt_out,
                                                PreviewsType type,
                                                uint64_t page_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(io_task_runner_->BelongsToCurrentThread());
   base::Time time =
       previews_black_list_->AddPreviewNavigation(url, opt_out, type);
@@ -217,11 +226,13 @@
 
 void PreviewsDeciderImpl::ClearBlackList(base::Time begin_time,
                                          base::Time end_time) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(io_task_runner_->BelongsToCurrentThread());
   previews_black_list_->ClearBlackList(begin_time, end_time);
 }
 
 void PreviewsDeciderImpl::SetIgnorePreviewsBlacklistDecision(bool ignored) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(io_task_runner_->BelongsToCurrentThread());
   blacklist_ignored_ = ignored;
   ui_task_runner_->PostTask(
@@ -232,6 +243,7 @@
 
 bool PreviewsDeciderImpl::ShouldAllowPreview(const net::URLRequest& request,
                                              PreviewsType type) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(type == PreviewsType::OFFLINE ||
          type == PreviewsType::LITE_PAGE_REDIRECT ||
          type == PreviewsType::NOSCRIPT ||
@@ -248,7 +260,8 @@
     PreviewsType type,
     net::EffectiveConnectionType effective_connection_type_threshold,
     const std::vector<std::string>& host_blacklist_from_finch,
-    bool ignore_long_term_black_list_rules) const {
+    bool is_server_preview) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!previews::params::ArePreviewsAllowed()) {
     return false;
   }
@@ -289,7 +302,8 @@
     // The blacklist will disallow certain hosts for periods of time based on
     // user's opting out of the preview.
     PreviewsEligibilityReason status = previews_black_list_->IsLoadedAndAllowed(
-        request.url(), type, ignore_long_term_black_list_rules,
+        request.url(), type,
+        is_server_preview && ignore_long_term_blacklist_for_server_previews_,
         &passed_reasons);
 
     if (status != PreviewsEligibilityReason::ALLOWED) {
@@ -408,6 +422,7 @@
 
 bool PreviewsDeciderImpl::IsURLAllowedForPreview(const net::URLRequest& request,
                                                  PreviewsType type) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(PreviewsType::NOSCRIPT == type ||
          PreviewsType::RESOURCE_LOADING_HINTS == type);
   if (previews_black_list_ && !blacklist_ignored_) {
@@ -451,6 +466,7 @@
     const net::URLRequest& request,
     PreviewsType type,
     std::vector<PreviewsEligibilityReason>* passed_reasons) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(type == PreviewsType::LITE_PAGE_REDIRECT ||
          type == PreviewsType::NOSCRIPT ||
          type == PreviewsType::RESOURCE_LOADING_HINTS);
@@ -493,6 +509,7 @@
     const net::URLRequest& request,
     PreviewsType type,
     std::vector<PreviewsEligibilityReason>* passed_reasons) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(type == PreviewsType::LITE_PAGE_REDIRECT ||
          type == PreviewsType::NOSCRIPT ||
          type == PreviewsType::RESOURCE_LOADING_HINTS);
@@ -516,4 +533,11 @@
   return ++page_id_;
 }
 
+void PreviewsDeciderImpl::SetIgnoreLongTermBlackListForServerPreviews(
+    bool ignore_long_term_blacklist_for_server_previews) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  ignore_long_term_blacklist_for_server_previews_ =
+      ignore_long_term_blacklist_for_server_previews;
+}
+
 }  // namespace previews
diff --git a/components/previews/content/previews_decider_impl.h b/components/previews/content/previews_decider_impl.h
index 4691a42..df2e37b 100644
--- a/components/previews/content/previews_decider_impl.h
+++ b/components/previews/content/previews_decider_impl.h
@@ -15,6 +15,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
 #include "base/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "components/blacklist/opt_out_blacklist/opt_out_blacklist_data.h"
@@ -117,10 +118,16 @@
       PreviewsType type,
       net::EffectiveConnectionType effective_connection_type_threshold,
       const std::vector<std::string>& host_blacklist_from_finch,
-      bool ignore_long_term_black_list_rules) const override;
+      bool is_server_preview) const override;
   bool IsURLAllowedForPreview(const net::URLRequest& request,
                               PreviewsType type) const override;
 
+  // Set whether ignoring the long term blacklist rules is allowed for calls to
+  // ShouldAllowPreviewAtECT that have |can_ignore_long_term_black_list_rules|
+  // set to true.
+  void SetIgnoreLongTermBlackListForServerPreviews(
+      bool ignore_long_term_blacklist_for_server_previews);
+
   void LoadResourceHints(const net::URLRequest& request) override;
 
   // Generates a page ID that is guaranteed to be unique from any other page ID
@@ -176,8 +183,15 @@
   // Whether the decisions made by PreviewsBlackList should be ignored or not.
   // This can be changed by chrome://interventions-internals to test/debug the
   // behavior of Previews decisions.
+  // This is related to a test flag and should only be true when the user has
+  // set it in flags. See previews::IsPreviewsBlacklistIgnoredViaFlag.
   bool blacklist_ignored_;
 
+  // Whether ignoring the blacklist is allowed for calls to
+  // ShouldAllowPreviewAtECT that have
+  // |is_server_preview| true.
+  bool ignore_long_term_blacklist_for_server_previews_ = false;
+
   base::Clock* clock_;
 
   // The UI and IO thread task runners. |ui_task_runner_| is used to post
@@ -192,6 +206,8 @@
 
   uint64_t page_id_;
 
+  SEQUENCE_CHECKER(sequence_checker_);
+
   base::WeakPtrFactory<PreviewsDeciderImpl> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(PreviewsDeciderImpl);
diff --git a/components/previews/content/previews_decider_impl_unittest.cc b/components/previews/content/previews_decider_impl_unittest.cc
index fd5755e..73a16c873 100644
--- a/components/previews/content/previews_decider_impl_unittest.cc
+++ b/components/previews/content/previews_decider_impl_unittest.cc
@@ -9,6 +9,7 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
@@ -105,13 +106,14 @@
       PreviewsType type,
       bool ignore_long_term_black_list_rules,
       std::vector<PreviewsEligibilityReason>* passed_reasons) const override {
-    PreviewsEligibilityReason ordered_reasons[] = {
+    std::vector<PreviewsEligibilityReason> ordered_reasons = {
         PreviewsEligibilityReason::BLACKLIST_DATA_NOT_LOADED,
-        PreviewsEligibilityReason::USER_RECENTLY_OPTED_OUT,
-        PreviewsEligibilityReason::USER_BLACKLISTED,
-        PreviewsEligibilityReason::HOST_BLACKLISTED,
-        PreviewsEligibilityReason::ALLOWED,
-    };
+        PreviewsEligibilityReason::USER_RECENTLY_OPTED_OUT};
+
+    if (!ignore_long_term_black_list_rules) {
+      ordered_reasons.push_back(PreviewsEligibilityReason::USER_BLACKLISTED);
+      ordered_reasons.push_back(PreviewsEligibilityReason::HOST_BLACKLISTED);
+    }
 
     for (auto reason : ordered_reasons) {
       if (status_ == reason) {
@@ -119,8 +121,8 @@
       }
       passed_reasons->push_back(reason);
     }
-    NOTREACHED();
-    return status_;
+
+    return PreviewsEligibilityReason::ALLOWED;
   }
 
  private:
@@ -1785,6 +1787,33 @@
   EXPECT_EQ(page_id_set.end(), page_id_set.find(0u));
 }
 
+TEST_F(PreviewsDeciderImplTest, TestIgnoreLongTermRule) {
+  // Verify that when long term rules can be ignored, and the caller is fine
+  // with ignoring long term rules, they are not checked.
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(features::kPreviews);
+  InitializeUIService();
+
+  previews_decider_impl()->SetIgnoreLongTermBlackListForServerPreviews(true);
+
+  std::unique_ptr<TestPreviewsBlackList> blacklist =
+      std::make_unique<TestPreviewsBlackList>(
+          PreviewsEligibilityReason::HOST_BLACKLISTED, previews_decider_impl());
+  previews_decider_impl()->InjectTestBlacklist(std::move(blacklist));
+
+  // LoFi and LitePage check NQE on their own.
+  network_quality_estimator()->set_effective_connection_type(
+      net::EFFECTIVE_CONNECTION_TYPE_3G);
+
+  base::HistogramTester histogram_tester;
+  EXPECT_FALSE(previews_decider_impl()->ShouldAllowPreviewAtECT(
+      *CreateRequest(), PreviewsType::LITE_PAGE,
+      net::EFFECTIVE_CONNECTION_TYPE_4G, std::vector<std::string>(), false));
+  EXPECT_TRUE(previews_decider_impl()->ShouldAllowPreviewAtECT(
+      *CreateRequest(), PreviewsType::LITE_PAGE,
+      net::EFFECTIVE_CONNECTION_TYPE_4G, std::vector<std::string>(), true));
+}
+
 }  // namespace
 
 }  // namespace previews
diff --git a/components/previews/content/previews_ui_service.cc b/components/previews/content/previews_ui_service.cc
index 0ff68ee..f661fe0 100644
--- a/components/previews/content/previews_ui_service.cc
+++ b/components/previews/content/previews_ui_service.cc
@@ -124,6 +124,17 @@
   return logger_.get();
 }
 
+// When triggering previews, prevent long term black list rules.
+void PreviewsUIService::SetIgnoreLongTermBlackListForServerPreviews(
+    bool ignore_long_term_black_list_rules_allowed) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  io_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &PreviewsDeciderImpl::SetIgnoreLongTermBlackListForServerPreviews,
+          previews_decider_impl_, ignore_long_term_black_list_rules_allowed));
+}
+
 void PreviewsUIService::ClearBlackList(base::Time begin_time,
                                        base::Time end_time) {
   DCHECK(thread_checker_.CalledOnValidThread());
diff --git a/components/previews/content/previews_ui_service.h b/components/previews/content/previews_ui_service.h
index 0c4ffdb2..981fe04 100644
--- a/components/previews/content/previews_ui_service.h
+++ b/components/previews/content/previews_ui_service.h
@@ -128,6 +128,10 @@
   // return null.
   PreviewsLogger* previews_logger() const;
 
+  // When triggering previews, prevent long term black list rules.
+  void SetIgnoreLongTermBlackListForServerPreviews(
+      bool ignore_long_term_black_list_rules_allowed);
+
  private:
   // The IO thread portion of the inter-thread communication for previews/.
   base::WeakPtr<previews::PreviewsDeciderImpl> previews_decider_impl_;
diff --git a/components/previews/core/previews_decider.h b/components/previews/core/previews_decider.h
index b2fa370..41dbb10 100644
--- a/components/previews/core/previews_decider.h
+++ b/components/previews/core/previews_decider.h
@@ -26,12 +26,15 @@
   // preview will be disallowed; preview types that check network quality before
   // calling ShouldAllowPreviewAtECT should pass in
   // EFFECTIVE_CONNECTION_TYPE_4G.
+  // |is_server_preview| means that the blacklist does
+  // not need to be checked for long term rules when Previews has been
+  // configured to allow skipping the blacklist.
   virtual bool ShouldAllowPreviewAtECT(
       const net::URLRequest& request,
       PreviewsType type,
       net::EffectiveConnectionType effective_connection_type_threshold,
       const std::vector<std::string>& host_blacklist_from_finch,
-      bool ignore_long_term_black_list_rules) const = 0;
+      bool is_server_preview) const = 0;
 
   // Same as ShouldAllowPreviewAtECT, but uses the previews default
   // EffectiveConnectionType and no blacklisted hosts from the server.
diff --git a/components/previews/core/test_previews_decider.cc b/components/previews/core/test_previews_decider.cc
index a90d491..247e815 100644
--- a/components/previews/core/test_previews_decider.cc
+++ b/components/previews/core/test_previews_decider.cc
@@ -16,7 +16,7 @@
     previews::PreviewsType type,
     net::EffectiveConnectionType effective_connection_type_threshold,
     const std::vector<std::string>& host_blacklist_from_server,
-    bool ignore_long_term_black_list_rules) const {
+    bool is_server_preview) const {
   return allow_previews_;
 }
 
diff --git a/components/previews/core/test_previews_decider.h b/components/previews/core/test_previews_decider.h
index 70d00cb0c..1b76db7 100644
--- a/components/previews/core/test_previews_decider.h
+++ b/components/previews/core/test_previews_decider.h
@@ -21,7 +21,7 @@
       previews::PreviewsType type,
       net::EffectiveConnectionType effective_connection_type_threshold,
       const std::vector<std::string>& host_blacklist_from_server,
-      bool ignore_long_term_black_list_rules) const override;
+      bool is_server_preview) const override;
   bool ShouldAllowPreview(const net::URLRequest& request,
                           previews::PreviewsType type) const override;
   bool IsURLAllowedForPreview(const net::URLRequest& request,
diff --git a/components/signin/ios/DEPS b/components/signin/ios/DEPS
index 4dd6307..4acb57d 100644
--- a/components/signin/ios/DEPS
+++ b/components/signin/ios/DEPS
@@ -1,4 +1,6 @@
 include_rules = [
   "+ios/web/public",
+  "+services/network/public",
+  "+services/network/test",
   "+third_party/ocmock",
 ]
diff --git a/components/signin/ios/browser/wait_for_network_callback_helper.cc b/components/signin/ios/browser/wait_for_network_callback_helper.cc
index fcec9a5f..cb7ef81 100644
--- a/components/signin/ios/browser/wait_for_network_callback_helper.cc
+++ b/components/signin/ios/browser/wait_for_network_callback_helper.cc
@@ -4,17 +4,19 @@
 
 #include "components/signin/ios/browser/wait_for_network_callback_helper.h"
 
-WaitForNetworkCallbackHelper::WaitForNetworkCallbackHelper() {
-  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
+WaitForNetworkCallbackHelper::WaitForNetworkCallbackHelper(
+    network::NetworkConnectionTracker* network_connection_tracker)
+    : network_connection_tracker_(network_connection_tracker) {
+  network_connection_tracker_->AddNetworkConnectionObserver(this);
 }
 
 WaitForNetworkCallbackHelper::~WaitForNetworkCallbackHelper() {
-  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
+  network_connection_tracker_->RemoveNetworkConnectionObserver(this);
 }
 
-void WaitForNetworkCallbackHelper::OnNetworkChanged(
-    net::NetworkChangeNotifier::ConnectionType type) {
-  if (net::NetworkChangeNotifier::IsOffline())
+void WaitForNetworkCallbackHelper::OnConnectionChanged(
+    network::mojom::ConnectionType type) {
+  if (network_connection_tracker_->IsOffline())
     return;
 
   for (const base::Closure& callback : delayed_callbacks_)
@@ -25,7 +27,7 @@
 
 void WaitForNetworkCallbackHelper::HandleCallback(
     const base::Closure& callback) {
-  if (net::NetworkChangeNotifier::IsOffline()) {
+  if (network_connection_tracker_->IsOffline()) {
     delayed_callbacks_.push_back(callback);
   } else {
     callback.Run();
diff --git a/components/signin/ios/browser/wait_for_network_callback_helper.h b/components/signin/ios/browser/wait_for_network_callback_helper.h
index 6e77513..37c4ede 100644
--- a/components/signin/ios/browser/wait_for_network_callback_helper.h
+++ b/components/signin/ios/browser/wait_for_network_callback_helper.h
@@ -9,25 +9,26 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
-#include "net/base/network_change_notifier.h"
+#include "services/network/public/cpp/network_connection_tracker.h"
 
 // Class used for delaying callbacks when the network connection is offline and
 // invoking them when the network connection becomes online.
 class WaitForNetworkCallbackHelper
-    : public net::NetworkChangeNotifier::NetworkChangeObserver {
+    : public network::NetworkConnectionTracker::NetworkConnectionObserver {
  public:
-  WaitForNetworkCallbackHelper();
+  WaitForNetworkCallbackHelper(
+      network::NetworkConnectionTracker* network_connection_tracker);
   ~WaitForNetworkCallbackHelper() override;
 
-  // net::NetworkChangeController::NetworkChangeObserver implementation.
-  void OnNetworkChanged(
-      net::NetworkChangeNotifier::ConnectionType type) override;
+  // network::NetworkConnectionTracker::NetworkConnectionObserver implementation
+  void OnConnectionChanged(network::mojom::ConnectionType type) override;
 
   // If offline, saves the |callback| to be called later when online. Otherwise,
   // invokes immediately.
   void HandleCallback(const base::Closure& callback);
 
  private:
+  network::NetworkConnectionTracker* network_connection_tracker_;
   std::list<base::Closure> delayed_callbacks_;
 
   DISALLOW_COPY_AND_ASSIGN(WaitForNetworkCallbackHelper);
diff --git a/components/signin/ios/browser/wait_for_network_callback_helper_unittest.cc b/components/signin/ios/browser/wait_for_network_callback_helper_unittest.cc
index 78fe1ca..80ae7b2 100644
--- a/components/signin/ios/browser/wait_for_network_callback_helper_unittest.cc
+++ b/components/signin/ios/browser/wait_for_network_callback_helper_unittest.cc
@@ -7,7 +7,7 @@
 #include "base/bind.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
-#include "net/base/mock_network_change_notifier.h"
+#include "services/network/test/test_network_connection_tracker.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 // A test fixture to test WaitForNetworkCallbackHelper.
@@ -16,17 +16,19 @@
   void CallbackFunction() { num_callbacks_invoked_++; }
 
  protected:
-  WaitForNetworkCallbackHelperTest() : num_callbacks_invoked_(0) {}
+  WaitForNetworkCallbackHelperTest()
+      : num_callbacks_invoked_(0),
+        callback_helper_(network::TestNetworkConnectionTracker::GetInstance()) {
+  }
 
   int num_callbacks_invoked_;
   base::test::ScopedTaskEnvironment scoped_task_environment_;
-  net::test::MockNetworkChangeNotifier network_change_notifier_;
   WaitForNetworkCallbackHelper callback_helper_;
 };
 
 TEST_F(WaitForNetworkCallbackHelperTest, CallbackInvokedImmediately) {
-  network_change_notifier_.SetConnectionType(
-      net::NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI);
+  network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
+      network::mojom::ConnectionType::CONNECTION_WIFI);
   callback_helper_.HandleCallback(
       base::Bind(&WaitForNetworkCallbackHelperTest::CallbackFunction,
                  base::Unretained(this)));
@@ -34,8 +36,8 @@
 }
 
 TEST_F(WaitForNetworkCallbackHelperTest, CallbackInvokedLater) {
-  network_change_notifier_.SetConnectionType(
-      net::NetworkChangeNotifier::ConnectionType::CONNECTION_NONE);
+  network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
+      network::mojom::ConnectionType::CONNECTION_NONE);
   callback_helper_.HandleCallback(
       base::Bind(&WaitForNetworkCallbackHelperTest::CallbackFunction,
                  base::Unretained(this)));
@@ -44,10 +46,8 @@
                  base::Unretained(this)));
   EXPECT_EQ(0, num_callbacks_invoked_);
 
-  network_change_notifier_.SetConnectionType(
-      net::NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI);
-  network_change_notifier_.NotifyObserversOfConnectionTypeChangeForTests(
-      net::NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI);
+  network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
+      network::mojom::ConnectionType::CONNECTION_WIFI);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(2, num_callbacks_invoked_);
 }
diff --git a/components/sync/model_impl/model_type_store_backend.cc b/components/sync/model_impl/model_type_store_backend.cc
index 43315bb..9bf4379 100644
--- a/components/sync/model_impl/model_type_store_backend.cc
+++ b/components/sync/model_impl/model_type_store_backend.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "components/sync/protocol/model_type_store_schema_descriptor.pb.h"
@@ -29,6 +30,11 @@
 const char ModelTypeStoreBackend::kStoreInitResultHistogramName[] =
     "Sync.ModelTypeStoreInitResult";
 
+const base::Feature kModelTypeStoreAvoidReadCache{
+    "kModelTypeStoreAvoidReadCache", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kModelTypeStoreSmallWriteBufferSize{
+    "kModelTypeStoreSmallWriteBufferSize", base::FEATURE_DISABLED_BY_DEFAULT};
+
 namespace {
 
 StoreInitResultForHistogram LevelDbStatusToStoreInitResult(
@@ -133,6 +139,10 @@
   leveldb_env::Options options;
   options.create_if_missing = true;
   options.paranoid_checks = true;
+
+  if (base::FeatureList::IsEnabled(kModelTypeStoreSmallWriteBufferSize))
+    options.write_buffer_size = 512 * 1024;
+
   if (env)
     options.env = env;
 
@@ -157,6 +167,8 @@
   record_list->reserve(id_list.size());
   leveldb::ReadOptions read_options;
   read_options.verify_checksums = true;
+  if (base::FeatureList::IsEnabled(kModelTypeStoreAvoidReadCache))
+    read_options.fill_cache = false;
   std::string key;
   std::string value;
   for (const std::string& id : id_list) {
@@ -180,6 +192,8 @@
   DCHECK(db_);
   leveldb::ReadOptions read_options;
   read_options.verify_checksums = true;
+  if (base::FeatureList::IsEnabled(kModelTypeStoreAvoidReadCache))
+    read_options.fill_cache = false;
   std::unique_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options));
   const leveldb::Slice prefix_slice(prefix);
   for (iter->Seek(prefix_slice); iter->Valid(); iter->Next()) {
@@ -211,8 +225,10 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(db_);
   leveldb::WriteBatch write_batch;
-  std::unique_ptr<leveldb::Iterator> iter(
-      db_->NewIterator(leveldb::ReadOptions()));
+  leveldb::ReadOptions read_options;
+  if (base::FeatureList::IsEnabled(kModelTypeStoreAvoidReadCache))
+    read_options.fill_cache = false;
+  std::unique_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options));
   const leveldb::Slice prefix_slice(prefix);
   for (iter->Seek(prefix_slice); iter->Valid(); iter->Next()) {
     leveldb::Slice key = iter->key();
@@ -240,6 +256,8 @@
   DCHECK(db_);
   leveldb::ReadOptions read_options;
   read_options.verify_checksums = true;
+  if (base::FeatureList::IsEnabled(kModelTypeStoreAvoidReadCache))
+    read_options.fill_cache = false;
   std::string value;
   ModelTypeStoreSchemaDescriptor schema_descriptor;
   leveldb::Status status =
diff --git a/components/variations/client_filterable_state.h b/components/variations/client_filterable_state.h
index a244f79..bd2f86d 100644
--- a/components/variations/client_filterable_state.h
+++ b/components/variations/client_filterable_state.h
@@ -47,6 +47,11 @@
   // Based on base::SysInfo::IsLowEndDevice().
   bool is_low_end_device = false;
 
+  // Whether this platform supports experiments which retain their group
+  // assignments across runs.
+  // TODO(paulmiller): Remove this once https://crbug.com/866722 is resolved.
+  bool supports_permanent_consistency = true;
+
   // The country code to use for studies configured with session consistency.
   std::string session_consistency_country;
 
diff --git a/components/variations/service/BUILD.gn b/components/variations/service/BUILD.gn
index e3c0c1e..c77020f 100644
--- a/components/variations/service/BUILD.gn
+++ b/components/variations/service/BUILD.gn
@@ -12,6 +12,7 @@
     "variations_field_trial_creator.h",
     "variations_service.cc",
     "variations_service.h",
+    "variations_service_client.cc",
     "variations_service_client.h",
   ]
 
diff --git a/components/variations/service/variations_field_trial_creator.cc b/components/variations/service/variations_field_trial_creator.cc
index 2dadabf..0797532 100644
--- a/components/variations/service/variations_field_trial_creator.cc
+++ b/components/variations/service/variations_field_trial_creator.cc
@@ -250,6 +250,8 @@
   // evaluated, that field trial would not be able to apply for this case.
   state->is_low_end_device = base::SysInfo::IsLowEndDevice();
 #endif
+  state->supports_permanent_consistency =
+      client_->GetSupportsPermanentConsistency();
   state->session_consistency_country = GetLatestCountry();
   state->permanent_consistency_country = LoadPermanentConsistencyCountry(
       version, state->session_consistency_country);
diff --git a/components/variations/service/variations_service_client.cc b/components/variations/service/variations_service_client.cc
new file mode 100644
index 0000000..c2baa85c
--- /dev/null
+++ b/components/variations/service/variations_service_client.cc
@@ -0,0 +1,13 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/service/variations_service_client.h"
+
+namespace variations {
+
+bool VariationsServiceClient::GetSupportsPermanentConsistency() {
+  return true;
+}
+
+}  // namespace variations
diff --git a/components/variations/service/variations_service_client.h b/components/variations/service/variations_service_client.h
index 2b466bb..4e2c644 100644
--- a/components/variations/service/variations_service_client.h
+++ b/components/variations/service/variations_service_client.h
@@ -45,6 +45,11 @@
   // Gets the channel of the embedder.
   virtual version_info::Channel GetChannel() = 0;
 
+  // Gets whether this platform supports experiments which retain their group
+  // assignments across runs.
+  // TODO(paulmiller): Remove this once https://crbug.com/866722 is resolved.
+  virtual bool GetSupportsPermanentConsistency();
+
   // Returns whether the embedder overrides the value of the restrict parameter.
   // |parameter| is an out-param that will contain the value of the restrict
   // parameter if true is returned.
diff --git a/components/variations/study_filtering.cc b/components/variations/study_filtering.cc
index eaca58a..6f1e2177 100644
--- a/components/variations/study_filtering.cc
+++ b/components/variations/study_filtering.cc
@@ -12,6 +12,7 @@
 #include "base/stl_util.h"
 #include "base/strings/string_util.h"
 #include "components/variations/client_filterable_state.h"
+#include "components/variations/proto/study.pb.h"
 
 namespace variations {
 namespace {
@@ -266,6 +267,14 @@
     }
   }
 
+  // TODO(paulmiller): Remove this once https://crbug.com/866722 is resolved.
+  if (study.consistency() == Study_Consistency_PERMANENT &&
+      !client_state.supports_permanent_consistency) {
+    DVLOG(1) << "Filtered out study " << study.name()
+             << " due to supports_permanent_consistency.";
+    return false;
+  }
+
   DVLOG(1) << "Kept study " << study.name() << ".";
   return true;
 }
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index cf8c28f..cb7f3f1 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -493,14 +493,9 @@
     current_paint_.setFilterQuality(kLow_SkFilterQuality);
   }
 
-  if (quad->ShouldDrawWithBlending() ||
-      quad->shared_quad_state->blend_mode != SkBlendMode::kSrcOver) {
-    current_paint_.setAlpha(quad->shared_quad_state->opacity * 255);
-    current_paint_.setBlendMode(
-        static_cast<SkBlendMode>(quad->shared_quad_state->blend_mode));
-  } else {
-    current_paint_.setBlendMode(SkBlendMode::kSrc);
-  }
+  current_paint_.setAlpha(quad->shared_quad_state->opacity * 255);
+  current_paint_.setBlendMode(
+      static_cast<SkBlendMode>(quad->shared_quad_state->blend_mode));
 
   if (draw_region) {
     gfx::QuadF local_draw_region(*draw_region);
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
index 1d6edc8..75fdc1f6 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -392,6 +392,14 @@
     // to determine the freshness of a surface at aggregation time.
     const LocalSurfaceId& last_created_local_surface_id =
         last_created_surface_id_.local_surface_id();
+    bool last_surface_has_dependent_frame =
+        prev_surface && prev_surface->HasDependentFrame();
+
+    bool child_initiated_synchronization_event =
+        last_created_local_surface_id.is_valid() &&
+        local_surface_id.child_sequence_number() >
+            last_created_local_surface_id.child_sequence_number();
+
     // Neither sequence numbers of the LocalSurfaceId can decrease and at least
     // one must increase.
     bool monotonically_increasing_id =
@@ -401,8 +409,7 @@
              last_created_local_surface_id.child_sequence_number()) &&
         (local_surface_id.parent_sequence_number() >
              last_created_local_surface_id.parent_sequence_number() ||
-         local_surface_id.child_sequence_number() >
-             last_created_local_surface_id.child_sequence_number());
+         child_initiated_synchronization_event);
 
     if (!surface_info.is_valid() || !monotonically_increasing_id) {
       TRACE_EVENT_INSTANT0("viz", "Surface Invariants Violation",
@@ -410,7 +417,15 @@
       return SubmitResult::SURFACE_INVARIANTS_VIOLATION;
     }
 
-    current_surface = CreateSurface(surface_info);
+    // If the last Surface doesn't have a dependent frame, and this frame
+    // corresponds to a child-initiated synchronization event then defer this
+    // Surface until a dependent frame arrives. This throttles child submission
+    // of CompositorFrames to the parent's embedding rate.
+    const bool block_activation_on_parent =
+        child_initiated_synchronization_event &&
+        !last_surface_has_dependent_frame;
+
+    current_surface = CreateSurface(surface_info, block_activation_on_parent);
     last_created_surface_id_ = SurfaceId(frame_sink_id_, local_surface_id);
     surface_manager_->SurfaceDamageExpected(current_surface->surface_id(),
                                             last_begin_frame_args_);
@@ -564,10 +579,12 @@
 }
 
 Surface* CompositorFrameSinkSupport::CreateSurface(
-    const SurfaceInfo& surface_info) {
+    const SurfaceInfo& surface_info,
+    bool block_activation_on_parent) {
   return surface_manager_->CreateSurface(
       weak_factory_.GetWeakPtr(), surface_info,
-      frame_sink_manager_->GetPrimaryBeginFrameSource(), needs_sync_tokens_);
+      frame_sink_manager_->GetPrimaryBeginFrameSource(), needs_sync_tokens_,
+      block_activation_on_parent);
 }
 
 SubmitResult CompositorFrameSinkSupport::MaybeSubmitCompositorFrame(
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.h b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
index 7a77470..cd7f6ea 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -197,7 +197,8 @@
   bool WantsAnimateOnlyBeginFrames() const override;
 
   void UpdateNeedsBeginFramesInternal();
-  Surface* CreateSurface(const SurfaceInfo& surface_info);
+  Surface* CreateSurface(const SurfaceInfo& surface_info,
+                         bool block_activation_on_parent);
 
   // For the sync API calls, if we are blocking a client callback, runs it once
   // BeginFrame and FrameAck are done.
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
index a0d62a0..360e6a2e 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
@@ -554,26 +554,64 @@
   LocalSurfaceId local_surface_id4(5, 3, kArbitraryToken);
   LocalSurfaceId local_surface_id5(8, 1, kArbitraryToken);
   LocalSurfaceId local_surface_id6(9, 3, kArbitraryToken);
+
+  // LocalSurfaceId1(6, 1)
   auto result = support->MaybeSubmitCompositorFrame(
       local_surface_id1, MakeDefaultCompositorFrame(), base::nullopt, 0,
       mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
   EXPECT_EQ(SubmitResult::ACCEPTED, result);
+
+  // LocalSurfaceId(6, 2): Child-initiated synchronization.
   result = support->MaybeSubmitCompositorFrame(
       local_surface_id2, MakeDefaultCompositorFrame(), base::nullopt, 0,
       mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
   EXPECT_EQ(SubmitResult::ACCEPTED, result);
+
+  // Since the Surface corresponding to |local_surface_id1| was not a dependency
+  // anywhere then the Surface corresponding to |local_surface_id2| will not
+  // activate until it becomes a dependency.
+  Surface* last_created_surface = support->GetLastCreatedSurfaceForTesting();
+  EXPECT_EQ(local_surface_id2,
+            last_created_surface->surface_id().local_surface_id());
+  EXPECT_FALSE(last_created_surface->HasActiveFrame());
+
+  SurfaceId surface_id2(kAnotherArbitraryFrameSinkId, local_surface_id2);
+  auto frame =
+      CompositorFrameBuilder()
+          .AddDefaultRenderPass()
+          .SetActivationDependencies({surface_id2})
+          .SetReferencedSurfaces({SurfaceRange(base::nullopt, surface_id2)})
+          .Build();
+  result = support_->MaybeSubmitCompositorFrame(
+      local_surface_id_, std::move(frame), base::nullopt, 0,
+      mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
+  EXPECT_EQ(SubmitResult::ACCEPTED, result);
+
+  // Submitting a CompositorFrame to the parent FrameSink with a dependency on
+  // |local_surface_id2| causes that Surface's CompositorFrame to activate.
+  EXPECT_TRUE(last_created_surface->HasActiveFrame());
+
+  // LocalSurfaceId(7, 2): Parent-initiated synchronization.
   result = support->MaybeSubmitCompositorFrame(
       local_surface_id3, MakeDefaultCompositorFrame(), base::nullopt, 0,
       mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
   EXPECT_EQ(SubmitResult::ACCEPTED, result);
+
+  // LocalSurfaceId(5, 3): Surface Invariants Violation. Not monotonically
+  // increasing.
   result = support->MaybeSubmitCompositorFrame(
       local_surface_id4, MakeDefaultCompositorFrame(), base::nullopt, 0,
       mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
   EXPECT_EQ(SubmitResult::SURFACE_INVARIANTS_VIOLATION, result);
+
+  // LocalSurfaceId(8, 1): Surface Invariants Violation. Not monotonically
+  // increasing.
   result = support->MaybeSubmitCompositorFrame(
       local_surface_id5, MakeDefaultCompositorFrame(), base::nullopt, 0,
       mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
   EXPECT_EQ(SubmitResult::SURFACE_INVARIANTS_VIOLATION, result);
+
+  // LocalSurfaceId(9, 3): Parent AND child-initiated synchronization.
   result = support->MaybeSubmitCompositorFrame(
       local_surface_id6, MakeDefaultCompositorFrame(), base::nullopt, 0,
       mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
diff --git a/components/viz/service/frame_sinks/surface_synchronization_unittest.cc b/components/viz/service/frame_sinks/surface_synchronization_unittest.cc
index 8174fbf..b02f9b2 100644
--- a/components/viz/service/frame_sinks/surface_synchronization_unittest.cc
+++ b/components/viz/service/frame_sinks/surface_synchronization_unittest.cc
@@ -2166,9 +2166,19 @@
   // Verify that there is a temporary reference for child_id3.
   EXPECT_TRUE(HasTemporaryReference(child_id3));
 
+  // The surface corresponding to |child_id3| will not be activated until
+  // a parent embeds it because it's a child initiated synchronization.
+  EXPECT_EQ(GetSurfaceForId(child_id2),
+            GetLatestInFlightSurface(SurfaceRange(child_id1, child_id4)));
+
+  parent_support().SubmitCompositorFrame(
+      parent_id.local_surface_id(),
+      MakeCompositorFrame({child_id3}, {SurfaceRange(base::nullopt, child_id3)},
+                          std::vector<TransferableResource>()));
+
   EXPECT_EQ(GetSurfaceForId(child_id3),
             GetLatestInFlightSurface(SurfaceRange(child_id1, child_id4)));
-  EXPECT_THAT(GetChildReferences(parent_id), UnorderedElementsAre(child_id1));
+  EXPECT_THAT(GetChildReferences(parent_id), UnorderedElementsAre(child_id3));
 
   // If the primary surface is active, we return it.
   EXPECT_EQ(GetSurfaceForId(child_id3),
@@ -2780,5 +2790,204 @@
   EXPECT_EQ(parent_id, parent_support().last_activated_surface_id());
 }
 
+// If a parent CompositorFrame embeds a child Surface newer than the throttled
+// child then the throttled child surface is immediately unthrottled. This
+// unblocks the child to make progress to catch up with the parent.
+TEST_F(SurfaceSynchronizationTest,
+       ParentEmbeddingFutureChildUnblocksCurrentChildSurface) {
+  SetFrameSinkHierarchy(kParentFrameSink, kChildFrameSink1);
+  const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+  const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1, 1);
+  const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink1, 1, 2);
+  const SurfaceId child_id3 = MakeSurfaceId(kChildFrameSink1, 1, 3);
+
+  // |child_id1| Surface should immediately activate.
+  child_support1().SubmitCompositorFrame(child_id1.local_surface_id(),
+                                         MakeDefaultCompositorFrame());
+  Surface* child_surface1 = GetSurfaceForId(child_id1);
+  ASSERT_NE(nullptr, child_surface1);
+  EXPECT_FALSE(child_surface1->HasPendingFrame());
+  EXPECT_TRUE(child_surface1->HasActiveFrame());
+
+  // |child_id2| Surface should not activate because |child_id1| was never
+  // added as a dependency by a parent.
+  child_support1().SubmitCompositorFrame(child_id2.local_surface_id(),
+                                         MakeDefaultCompositorFrame());
+  Surface* child_surface2 = GetSurfaceForId(child_id2);
+  ASSERT_NE(nullptr, child_surface2);
+  EXPECT_TRUE(child_surface2->HasPendingFrame());
+  EXPECT_FALSE(child_surface2->HasActiveFrame());
+
+  // The parent finally embeds a child surface that hasn't arrived which
+  // activates |child_id2|'s Surface in order for the child to make forward
+  // progress.
+  parent_support().SubmitCompositorFrame(
+      parent_id.local_surface_id(),
+      MakeCompositorFrame({child_id3}, {SurfaceRange(base::nullopt, child_id3)},
+                          std::vector<TransferableResource>(),
+                          MakeDefaultDeadline()));
+
+  EXPECT_FALSE(child_surface2->HasPendingFrame());
+  EXPECT_TRUE(child_surface2->HasActiveFrame());
+}
+
+// A child surface can be blocked on its own activation dependencies and on a
+// parent embedding it as an activation dependency. Even if a child's activation
+// dependencies arrive, it may not activate until it is embedded.
+TEST_F(SurfaceSynchronizationTest, ChildBlockedOnDependenciesAndParent) {
+  const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+  const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1, 1);
+  const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink1, 1, 2);
+  const SurfaceId child_id3 = MakeSurfaceId(kChildFrameSink2, 1);
+
+  // |child_id1| Surface should immediately activate.
+  child_support1().SubmitCompositorFrame(child_id1.local_surface_id(),
+                                         MakeDefaultCompositorFrame());
+  Surface* child_surface1 = GetSurfaceForId(child_id1);
+  ASSERT_NE(nullptr, child_surface1);
+  EXPECT_FALSE(child_surface1->HasPendingFrame());
+  EXPECT_TRUE(child_surface1->HasActiveFrame());
+
+  // |child_id2| Surface should not activate because |child_id1| was never
+  // added as a dependency by a parent AND it depends on |child_id3| which has
+  // not yet arrived.
+  child_support1().SubmitCompositorFrame(
+      child_id2.local_surface_id(),
+      MakeCompositorFrame({child_id3}, {SurfaceRange(base::nullopt, child_id3)},
+                          std::vector<TransferableResource>(),
+                          MakeDefaultDeadline()));
+  Surface* child_surface2 = GetSurfaceForId(child_id2);
+  ASSERT_NE(nullptr, child_surface2);
+  EXPECT_TRUE(child_surface2->HasPendingFrame());
+  EXPECT_FALSE(child_surface2->HasActiveFrame());
+  EXPECT_THAT(child_surface2->activation_dependencies(),
+              UnorderedElementsAre(child_id3));
+
+  // |child_id2|'s dependency has arrived but |child_id2| Surface has not
+  // activated because it is still throttled by its parent. It will not
+  // activate until its parent arrives.
+  child_support2().SubmitCompositorFrame(child_id3.local_surface_id(),
+                                         MakeDefaultCompositorFrame());
+  EXPECT_THAT(child_surface2->activation_dependencies(), IsEmpty());
+  EXPECT_TRUE(child_surface2->HasPendingFrame());
+  EXPECT_FALSE(child_surface2->HasActiveFrame());
+
+  // The parent finally embeds a |child_id2| which activates |child_id2|'s
+  // Surface.
+  parent_support().SubmitCompositorFrame(
+      parent_id.local_surface_id(),
+      MakeCompositorFrame({child_id2}, {SurfaceRange(base::nullopt, child_id2)},
+                          std::vector<TransferableResource>(),
+                          MakeDefaultDeadline()));
+
+  EXPECT_FALSE(child_surface2->HasPendingFrame());
+  EXPECT_TRUE(child_surface2->HasActiveFrame());
+}
+
+// Similar to the previous test, a child surface can be blocked on its own
+// activation dependencies and on a parent embedding it as an activation
+// dependency. In this case, the parent CompositorFrame arrives first, and then
+// the activation dependency.
+TEST_F(SurfaceSynchronizationTest, ChildBlockedOnDependenciesAndParent2) {
+  const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
+  const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1, 1);
+  const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink1, 1, 2);
+  const SurfaceId child_id3 = MakeSurfaceId(kChildFrameSink2, 1);
+
+  // |child_id1| Surface should immediately activate.
+  child_support1().SubmitCompositorFrame(child_id1.local_surface_id(),
+                                         MakeDefaultCompositorFrame());
+  Surface* child_surface1 = GetSurfaceForId(child_id1);
+  ASSERT_NE(nullptr, child_surface1);
+  EXPECT_FALSE(child_surface1->HasPendingFrame());
+  EXPECT_TRUE(child_surface1->HasActiveFrame());
+
+  // |child_id2| Surface should not activate because |child_id1| was never
+  // added as a dependency by a parent AND it depends on |child_id3| which has
+  // not yet arrived.
+  child_support1().SubmitCompositorFrame(
+      child_id2.local_surface_id(),
+      MakeCompositorFrame({child_id3}, {SurfaceRange(base::nullopt, child_id3)},
+                          std::vector<TransferableResource>(),
+                          MakeDefaultDeadline()));
+  Surface* child_surface2 = GetSurfaceForId(child_id2);
+  ASSERT_NE(nullptr, child_surface2);
+  EXPECT_TRUE(child_surface2->HasPendingFrame());
+  EXPECT_FALSE(child_surface2->HasActiveFrame());
+  EXPECT_THAT(child_surface2->activation_dependencies(),
+              UnorderedElementsAre(child_id3));
+  EXPECT_FALSE(child_surface2->HasDependentFrame());
+
+  // The parent embeds |child_id2| but |child_id2|'s Surface cannot activate
+  // until its dependencies arrive.
+  parent_support().SubmitCompositorFrame(
+      parent_id.local_surface_id(),
+      MakeCompositorFrame({child_id2}, {SurfaceRange(base::nullopt, child_id2)},
+                          std::vector<TransferableResource>(),
+                          MakeDefaultDeadline()));
+  EXPECT_TRUE(child_surface2->HasPendingFrame());
+  EXPECT_FALSE(child_surface2->HasActiveFrame());
+  EXPECT_THAT(child_surface2->activation_dependencies(),
+              UnorderedElementsAre(child_id3));
+  EXPECT_TRUE(child_surface2->HasDependentFrame());
+
+  // |child_id2|'s dependency has arrived and |child_id2|'s Surface finally
+  // activates.
+  child_support2().SubmitCompositorFrame(child_id3.local_surface_id(),
+                                         MakeDefaultCompositorFrame());
+  EXPECT_THAT(child_surface2->activation_dependencies(), IsEmpty());
+  EXPECT_FALSE(child_surface2->HasPendingFrame());
+  EXPECT_TRUE(child_surface2->HasActiveFrame());
+}
+
+// This test verifies that if a child-initiated synchronization is blocked
+// on a parent, then the frame will still activate after a deadline passes.
+TEST_F(SurfaceSynchronizationTest, ChildBlockedOnParentActivatesAfterDeadline) {
+  const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1, 1);
+  const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink1, 1, 2);
+
+  // |child_id1| Surface should immediately activate.
+  child_support1().SubmitCompositorFrame(child_id1.local_surface_id(),
+                                         MakeDefaultCompositorFrame());
+  Surface* child_surface1 = GetSurfaceForId(child_id1);
+  ASSERT_NE(nullptr, child_surface1);
+  EXPECT_FALSE(child_surface1->HasPendingFrame());
+  EXPECT_TRUE(child_surface1->HasActiveFrame());
+  EXPECT_FALSE(child_surface1->HasDependentFrame());
+  EXPECT_FALSE(child_surface1->has_deadline());
+
+  // |child_id2| Surface should not activate because |child_id1| was never
+  // added as a dependency by a parent.
+  child_support1().SubmitCompositorFrame(
+      child_id2.local_surface_id(),
+      MakeCompositorFrame(empty_surface_ids(), empty_surface_ranges(),
+                          std::vector<TransferableResource>(),
+                          MakeDefaultDeadline()));
+  Surface* child_surface2 = GetSurfaceForId(child_id2);
+  ASSERT_NE(nullptr, child_surface2);
+  EXPECT_TRUE(child_surface2->HasPendingFrame());
+  EXPECT_FALSE(child_surface2->HasActiveFrame());
+  EXPECT_FALSE(child_surface2->HasDependentFrame());
+  EXPECT_TRUE(child_surface2->has_deadline());
+
+  for (int i = 0; i < 3; ++i) {
+    SendNextBeginFrame();
+    // There is still a looming deadline! Eeek!
+    EXPECT_TRUE(child_surface2->HasPendingFrame());
+    EXPECT_FALSE(child_surface2->HasActiveFrame());
+    EXPECT_FALSE(child_surface2->HasDependentFrame());
+    EXPECT_TRUE(child_surface2->has_deadline());
+  }
+
+  SendNextBeginFrame();
+
+  EXPECT_FALSE(child_surface2->HasPendingFrame());
+  EXPECT_TRUE(child_surface2->HasActiveFrame());
+  // We pretend this is true so that subsequent CompositorFrames to the same
+  // surface do not block on the parent again.
+  EXPECT_TRUE(child_surface2->HasDependentFrame());
+  EXPECT_FALSE(child_surface2->has_deadline());
+}
+
 }  // namespace test
 }  // namespace viz
diff --git a/components/viz/service/surfaces/surface.cc b/components/viz/service/surfaces/surface.cc
index f279157..5870483 100644
--- a/components/viz/service/surfaces/surface.cc
+++ b/components/viz/service/surfaces/surface.cc
@@ -26,11 +26,13 @@
 Surface::Surface(const SurfaceInfo& surface_info,
                  SurfaceManager* surface_manager,
                  base::WeakPtr<SurfaceClient> surface_client,
-                 bool needs_sync_tokens)
+                 bool needs_sync_tokens,
+                 bool block_activation_on_parent)
     : surface_info_(surface_info),
       surface_manager_(surface_manager),
       surface_client_(std::move(surface_client)),
-      needs_sync_tokens_(needs_sync_tokens) {
+      needs_sync_tokens_(needs_sync_tokens),
+      block_activation_on_parent_(block_activation_on_parent) {
   TRACE_EVENT_ASYNC_BEGIN1(TRACE_DISABLED_BY_DEFAULT("viz.surface_lifetime"),
                            "Surface", this, "surface_info",
                            surface_info.ToString());
@@ -186,6 +188,20 @@
   }
 }
 
+void Surface::OnSurfaceDependencyAdded() {
+  if (seen_first_surface_dependency_)
+    return;
+
+  seen_first_surface_dependency_ = true;
+  if (!activation_dependencies_.empty() || !pending_frame_data_)
+    return;
+
+  DCHECK(frame_sink_id_dependencies_.empty());
+
+  // All blockers have been cleared. The surface can be activated now.
+  ActivatePendingFrame(base::nullopt);
+}
+
 void Surface::Close() {
   closed_ = true;
 }
@@ -222,7 +238,16 @@
   // regardless of whether it's pending or active.
   surface_client_->ReceiveFromChild(frame.resource_list);
 
-  if (activation_dependencies_.empty()) {
+  if (!seen_first_surface_dependency_) {
+    seen_first_surface_dependency_ =
+        surface_manager_->dependency_tracker()->HasSurfaceBlockedOn(
+            surface_id());
+  }
+
+  bool block_activation =
+      block_activation_on_parent_ && !seen_first_surface_dependency_;
+
+  if (!block_activation && activation_dependencies_.empty()) {
     // If there are no blockers, then immediately activate the frame.
     ActivateFrame(
         FrameData(std::move(frame), frame_index, std::move(presented_callback)),
@@ -298,7 +323,12 @@
                dependency.local_surface_id() <= surface_id.local_surface_id();
       });
 
-  if (!activation_dependencies_.empty())
+  // We cannot activate this CompositorFrame if there are still missing
+  // activation dependencies or this surface is blocked on its parent arriving
+  // and the parent has not arrived yet.
+  bool block_activation =
+      block_activation_on_parent_ && !seen_first_surface_dependency_;
+  if (block_activation || !activation_dependencies_.empty())
     return;
 
   DCHECK(frame_sink_id_dependencies_.empty());
@@ -307,6 +337,17 @@
   ActivatePendingFrame(base::nullopt);
 }
 
+bool Surface::IsBlockedOn(const SurfaceId& surface_id) const {
+  for (const SurfaceId& dependency : activation_dependencies_) {
+    if (dependency.frame_sink_id() != surface_id.frame_sink_id())
+      continue;
+
+    if (dependency.local_surface_id() <= surface_id.local_surface_id())
+      return true;
+  }
+  return false;
+}
+
 void Surface::ActivatePendingFrameForDeadline(
     base::Optional<base::TimeDelta> duration) {
   if (!pending_frame_data_)
@@ -483,7 +524,12 @@
 
   for (const SurfaceId& surface_id :
        current_frame.metadata.activation_dependencies) {
+    // Inform the Surface |dependency| that it's been added as a dependency in
+    // another Surface's CompositorFrame.
+    surface_manager_->SurfaceDependencyAdded(surface_id);
+
     Surface* dependency = surface_manager_->GetSurfaceForId(surface_id);
+
     // If a activation dependency does not have a corresponding active frame in
     // the display compositor, then it blocks this frame.
     if (!dependency || !dependency->HasActiveFrame()) {
diff --git a/components/viz/service/surfaces/surface.h b/components/viz/service/surfaces/surface.h
index 9f53af1..ba53c4dc 100644
--- a/components/viz/service/surfaces/surface.h
+++ b/components/viz/service/surfaces/surface.h
@@ -81,7 +81,8 @@
   Surface(const SurfaceInfo& surface_info,
           SurfaceManager* surface_manager,
           base::WeakPtr<SurfaceClient> surface_client,
-          bool needs_sync_tokens);
+          bool needs_sync_tokens,
+          bool block_activation_on_parent);
   ~Surface();
 
   void SetDependencyDeadline(
@@ -121,6 +122,10 @@
 
   bool needs_sync_tokens() const { return needs_sync_tokens_; }
 
+  bool block_activation_on_parent() const {
+    return block_activation_on_parent_;
+  }
+
   // Returns false if |frame| is invalid.
   // |frame_rejected_callback| will be called once if the frame will not be
   // displayed.
@@ -136,6 +141,10 @@
   // frame.
   void NotifySurfaceIdAvailable(const SurfaceId& surface_id);
 
+  // Returns whether the Surface is blocked on the provided |surface_id| or a
+  // predecessor.
+  bool IsBlockedOn(const SurfaceId& surface_id) const;
+
   // Called if a deadline has been hit and this surface is not yet active but
   // it's marked as respecting deadlines.
   void ActivatePendingFrameForDeadline(
@@ -200,6 +209,10 @@
     return HasActiveFrame() && !active_frame_data_->frame_processed;
   }
 
+  // Returns true if at any point, another Surface's CompositorFrame has
+  // depended on this Surface.
+  bool HasDependentFrame() const { return seen_first_surface_dependency_; }
+
   // SurfaceDeadlineClient implementation:
   void OnDeadline(base::TimeDelta duration) override;
 
@@ -210,6 +223,9 @@
   // referenced SurfaceRange.
   void OnChildActivated(const SurfaceId& surface_id);
 
+  // Called when this surface is embedded by another Surface's CompositorFrame.
+  void OnSurfaceDependencyAdded();
+
  private:
   struct SequenceNumbers {
     uint32_t parent_sequence_number = 0u;
@@ -296,7 +312,9 @@
   bool closed_ = false;
   bool seen_first_frame_activation_ = false;
   bool seen_first_surface_embedding_ = false;
+  bool seen_first_surface_dependency_ = false;
   const bool needs_sync_tokens_;
+  const bool block_activation_on_parent_;
 
   base::flat_set<SurfaceId> activation_dependencies_;
   base::flat_set<SurfaceId> late_activation_dependencies_;
diff --git a/components/viz/service/surfaces/surface_dependency_tracker.cc b/components/viz/service/surfaces/surface_dependency_tracker.cc
index 12a6310..ed24b25 100644
--- a/components/viz/service/surfaces/surface_dependency_tracker.cc
+++ b/components/viz/service/surfaces/surface_dependency_tracker.cc
@@ -34,15 +34,72 @@
     }
   }
 
+  // If |surface| is blocking on the arrival of a parent and the parent frame
+  // has not yet arrived then track this |surface|'s SurfaceId by FrameSinkId so
+  // that if a parent refers to it or a more recent surface, then
+  // SurfaceDependencyTracker reports back that a dependency has been added.
+  if (surface->block_activation_on_parent() && !surface->HasDependentFrame()) {
+    surfaces_blocked_on_parent_by_frame_sink_id_[surface->surface_id()
+                                                     .frame_sink_id()]
+        .insert(surface->surface_id());
+  }
+
   UpdateSurfaceDeadline(surface);
 }
 
+bool SurfaceDependencyTracker::HasSurfaceBlockedOn(
+    const SurfaceId& surface_id) const {
+  auto it = blocked_surfaces_from_dependency_.find(surface_id.frame_sink_id());
+  if (it == blocked_surfaces_from_dependency_.end())
+    return false;
+
+  for (const SurfaceId& blocked_surface_by_id : it->second) {
+    Surface* blocked_surface =
+        surface_manager_->GetSurfaceForId(blocked_surface_by_id);
+    if (blocked_surface && blocked_surface->IsBlockedOn(surface_id))
+      return true;
+  }
+  return false;
+}
+
 void SurfaceDependencyTracker::OnSurfaceActivated(Surface* surface) {
   if (!surface->late_activation_dependencies().empty())
     surfaces_with_missing_dependencies_.insert(surface->surface_id());
   else
     surfaces_with_missing_dependencies_.erase(surface->surface_id());
   NotifySurfaceIdAvailable(surface->surface_id());
+  // We treat an activation (by deadline) as being the equivalent of a parent
+  // embedding the surface.
+  OnSurfaceDependencyAdded(surface->surface_id());
+}
+
+void SurfaceDependencyTracker::OnSurfaceDependencyAdded(
+    const SurfaceId& surface_id) {
+  auto it = surfaces_blocked_on_parent_by_frame_sink_id_.find(
+      surface_id.frame_sink_id());
+  if (it == surfaces_blocked_on_parent_by_frame_sink_id_.end())
+    return;
+
+  std::vector<SurfaceId> dependencies_to_notify;
+
+  base::flat_set<SurfaceId>& blocked_surfaces = it->second;
+  for (auto iter = blocked_surfaces.begin(); iter != blocked_surfaces.end();) {
+    if (iter->local_surface_id() <= surface_id.local_surface_id()) {
+      dependencies_to_notify.push_back(*iter);
+      iter = blocked_surfaces.erase(iter);
+    } else {
+      ++iter;
+    }
+  }
+
+  if (blocked_surfaces.empty())
+    surfaces_blocked_on_parent_by_frame_sink_id_.erase(it);
+
+  for (const SurfaceId& dependency : dependencies_to_notify) {
+    Surface* surface = surface_manager_->GetSurfaceForId(dependency);
+    if (surface)
+      surface->OnSurfaceDependencyAdded();
+  }
 }
 
 void SurfaceDependencyTracker::OnSurfaceDependenciesChanged(
@@ -78,6 +135,7 @@
   // Pretend that the discarded surface's SurfaceId is now available to
   // unblock dependencies because we now know the surface will never activate.
   NotifySurfaceIdAvailable(surface->surface_id());
+  OnSurfaceDependencyAdded(surface->surface_id());
 }
 
 void SurfaceDependencyTracker::OnFrameSinkInvalidated(
@@ -85,6 +143,7 @@
   // We now know the frame sink will never generated any more frames,
   // thus unblock all dependencies to any future surfaces.
   NotifySurfaceIdAvailable(SurfaceId::MaxSequenceId(frame_sink_id));
+  OnSurfaceDependencyAdded(SurfaceId::MaxSequenceId(frame_sink_id));
 }
 
 void SurfaceDependencyTracker::ActivateLateSurfaceSubtree(Surface* surface) {
diff --git a/components/viz/service/surfaces/surface_dependency_tracker.h b/components/viz/service/surfaces/surface_dependency_tracker.h
index 064487b..8a217d2 100644
--- a/components/viz/service/surfaces/surface_dependency_tracker.h
+++ b/components/viz/service/surfaces/surface_dependency_tracker.h
@@ -34,7 +34,12 @@
   // informed when that surface's dependencies are resolved.
   void RequestSurfaceResolution(Surface* surface);
 
+  // Returns whether the dependency tracker has a surface blocked on the
+  // provided |surface_id|.
+  bool HasSurfaceBlockedOn(const SurfaceId& surface_id) const;
+
   void OnSurfaceActivated(Surface* surface);
+  void OnSurfaceDependencyAdded(const SurfaceId& surface_id);
   void OnSurfaceDependenciesChanged(
       Surface* surface,
       const base::flat_set<FrameSinkId>& added_dependencies,
@@ -68,6 +73,11 @@
   std::unordered_map<FrameSinkId, base::flat_set<SurfaceId>, FrameSinkIdHash>
       blocked_surfaces_from_dependency_;
 
+  // A map from a FrameSinkid to a set of surfaces with that FrameSinkId that
+  // are blocked on a parent arriving to embed them.
+  std::unordered_map<FrameSinkId, base::flat_set<SurfaceId>, FrameSinkIdHash>
+      surfaces_blocked_on_parent_by_frame_sink_id_;
+
   // The set of SurfaceIds corresponding to Surfaces that have active
   // CompositorFrames with missing dependencies.
   base::flat_set<SurfaceId> surfaces_with_missing_dependencies_;
diff --git a/components/viz/service/surfaces/surface_manager.cc b/components/viz/service/surfaces/surface_manager.cc
index 89c5fd2d..f9fc14b 100644
--- a/components/viz/service/surfaces/surface_manager.cc
+++ b/components/viz/service/surfaces/surface_manager.cc
@@ -106,7 +106,8 @@
     base::WeakPtr<SurfaceClient> surface_client,
     const SurfaceInfo& surface_info,
     BeginFrameSource* begin_frame_source,
-    bool needs_sync_tokens) {
+    bool needs_sync_tokens,
+    bool block_activation_on_parent) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(surface_info.is_valid());
   DCHECK(surface_client);
@@ -116,7 +117,8 @@
   auto it = surface_map_.find(surface_info.id());
   if (it == surface_map_.end()) {
     std::unique_ptr<Surface> surface = std::make_unique<Surface>(
-        surface_info, this, surface_client, needs_sync_tokens);
+        surface_info, this, surface_client, needs_sync_tokens,
+        block_activation_on_parent);
     surface->SetDependencyDeadline(std::make_unique<SurfaceDependencyDeadline>(
         surface.get(), begin_frame_source, tick_clock_));
     surface_map_[surface_info.id()] = std::move(surface);
@@ -606,6 +608,10 @@
   dependency_tracker_.OnSurfaceActivated(surface);
 }
 
+void SurfaceManager::SurfaceDependencyAdded(const SurfaceId& surface_id) {
+  dependency_tracker_.OnSurfaceDependencyAdded(surface_id);
+}
+
 void SurfaceManager::SurfaceDependenciesChanged(
     Surface* surface,
     const base::flat_set<FrameSinkId>& added_dependencies,
diff --git a/components/viz/service/surfaces/surface_manager.h b/components/viz/service/surfaces/surface_manager.h
index c976f6b..b562cc1 100644
--- a/components/viz/service/surfaces/surface_manager.h
+++ b/components/viz/service/surfaces/surface_manager.h
@@ -85,11 +85,13 @@
   Surface* CreateSurface(base::WeakPtr<SurfaceClient> surface_client,
                          const SurfaceInfo& surface_info,
                          BeginFrameSource* begin_frame_source,
-                         bool needs_sync_tokens);
+                         bool needs_sync_tokens,
+                         bool block_activation_on_parent);
 
   // Destroy the Surface once a set of sequence numbers has been satisfied.
   void DestroySurface(const SurfaceId& surface_id);
 
+  // Returns a Surface corresponding to the provided |surface_id|.
   Surface* GetSurfaceForId(const SurfaceId& surface_id);
 
   void AddObserver(SurfaceObserver* obs) { observer_list_.AddObserver(obs); }
@@ -123,6 +125,10 @@
   void SurfaceActivated(Surface* surface,
                         base::Optional<base::TimeDelta> duration);
 
+  // Called when this |surface_id| is referenced as an activation dependency
+  // from a parent CompositorFrame.
+  void SurfaceDependencyAdded(const SurfaceId& surface_id);
+
   // Called when the dependencies of a pending CompositorFrame within |surface|
   // has changed.
   void SurfaceDependenciesChanged(
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 0184681a..5bdaa0f1 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -148,6 +148,7 @@
 #include "media/mojo/services/media_metrics_provider.h"
 #include "media/mojo/services/video_decode_perf_history.h"
 #include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
 #include "mojo/public/cpp/bindings/message.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "mojo/public/cpp/system/data_pipe.h"
@@ -886,6 +887,31 @@
       last_committed_origin_, std::move(default_factory_request));
 }
 
+void RenderFrameHostImpl::MarkInitiatorsAsRequiringSeparateURLLoaderFactory(
+    std::vector<url::Origin> request_initiators,
+    bool push_to_renderer_now) {
+  size_t old_size = initiators_requiring_separate_url_loader_factory_.size();
+  initiators_requiring_separate_url_loader_factory_.insert(
+      request_initiators.begin(), request_initiators.end());
+  size_t new_size = initiators_requiring_separate_url_loader_factory_.size();
+  bool insertion_took_place = (old_size != new_size);
+  if (push_to_renderer_now && insertion_took_place)
+    UpdateSubresourceLoaderFactories();
+}
+
+URLLoaderFactoryBundleInfo::OriginMap
+RenderFrameHostImpl::CreateInitiatorSpecificURLLoaderFactories() {
+  URLLoaderFactoryBundleInfo::OriginMap result;
+  for (const url::Origin& initiator :
+       initiators_requiring_separate_url_loader_factory_) {
+    network::mojom::URLLoaderFactoryPtrInfo factory_info;
+    CreateNetworkServiceDefaultFactoryInternal(
+        initiator, mojo::MakeRequest(&factory_info));
+    result[initiator] = std::move(factory_info);
+  }
+  return result;
+}
+
 gfx::NativeView RenderFrameHostImpl::GetNativeView() {
   RenderWidgetHostView* view = render_view_host_->GetWidget()->GetView();
   if (!view)
@@ -4123,6 +4149,9 @@
       subresource_loader_factories->scheme_specific_factory_infos().emplace(
           factory.first, std::move(factory_proxy_info));
     }
+
+    subresource_loader_factories->initiator_specific_factory_infos() =
+        CreateInitiatorSpecificURLLoaderFactories();
   }
 
   // It is imperative that cross-document navigations always provide a set of
@@ -4255,7 +4284,8 @@
         origin, mojo::MakeRequest(&default_factory_info));
     subresource_loader_factories = std::make_unique<URLLoaderFactoryBundleInfo>(
         std::move(default_factory_info),
-        URLLoaderFactoryBundleInfo::SchemeMap(), bypass_redirect_checks);
+        URLLoaderFactoryBundleInfo::SchemeMap(),
+        URLLoaderFactoryBundleInfo::OriginMap(), bypass_redirect_checks);
   }
   SaveSubresourceFactories(std::move(subresource_loader_factories));
 
@@ -4798,7 +4828,8 @@
   std::unique_ptr<URLLoaderFactoryBundleInfo> subresource_loader_factories =
       std::make_unique<URLLoaderFactoryBundleInfo>(
           std::move(default_factory_info),
-          URLLoaderFactoryBundleInfo::SchemeMap(), bypass_redirect_checks);
+          URLLoaderFactoryBundleInfo::SchemeMap(),
+          CreateInitiatorSpecificURLLoaderFactories(), bypass_redirect_checks);
   SaveSubresourceFactories(std::move(subresource_loader_factories));
   GetNavigationControl()->UpdateSubresourceLoaderFactories(
       CloneSubresourceFactories());
@@ -4843,14 +4874,6 @@
 bool RenderFrameHostImpl::CreateNetworkServiceDefaultFactoryInternal(
     const url::Origin& origin,
     network::mojom::URLLoaderFactoryRequest default_factory_request) {
-  network::mojom::URLLoaderFactoryParamsPtr params =
-      network::mojom::URLLoaderFactoryParams::New();
-  params->process_id = GetProcess()->GetID();
-  params->disable_web_security =
-      base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kDisableWebSecurity);
-  SiteIsolationPolicy::PopulateURLLoaderFactoryParamsPtrForCORB(params.get());
-
   auto* context = GetSiteInstance()->GetBrowserContext();
   bool bypass_redirect_checks = false;
 
@@ -4860,23 +4883,24 @@
         &default_factory_request, &bypass_redirect_checks);
   }
 
-  // Keep DevTools proxy lasy, i.e. closest to the network.
+  // Keep DevTools proxy last, i.e. closest to the network.
   RenderFrameDevToolsAgentHost::WillCreateURLLoaderFactory(
       this, false /* is_navigation */, false /* is_download */,
       &default_factory_request);
-  StoragePartitionImpl* storage_partition = static_cast<StoragePartitionImpl*>(
-      BrowserContext::GetStoragePartition(context, GetSiteInstance()));
+
+  // Create the URLLoaderFactory - either via ContentBrowserClient or ourselves.
   if (g_create_network_factory_callback_for_test.Get().is_null()) {
-    storage_partition->GetNetworkContext()->CreateURLLoaderFactory(
-        std::move(default_factory_request), std::move(params));
+    GetProcess()->CreateURLLoaderFactory(origin,
+                                         std::move(default_factory_request));
   } else {
     network::mojom::URLLoaderFactoryPtr original_factory;
-    storage_partition->GetNetworkContext()->CreateURLLoaderFactory(
-        mojo::MakeRequest(&original_factory), std::move(params));
+    GetProcess()->CreateURLLoaderFactory(origin,
+                                         mojo::MakeRequest(&original_factory));
     g_create_network_factory_callback_for_test.Get().Run(
         std::move(default_factory_request), GetProcess()->GetID(),
         original_factory.PassInterface());
   }
+
   return bypass_redirect_checks;
 }
 
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index 2af671e..fe23b03 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -18,6 +18,7 @@
 
 #include "base/callback.h"
 #include "base/compiler_specific.h"
+#include "base/containers/flat_set.h"
 #include "base/containers/id_map.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
@@ -252,6 +253,9 @@
       const blink::WebMediaPlayerAction& action) override;
   bool CreateNetworkServiceDefaultFactory(
       network::mojom::URLLoaderFactoryRequest default_factory_request) override;
+  void MarkInitiatorsAsRequiringSeparateURLLoaderFactory(
+      std::vector<url::Origin> request_initiators,
+      bool push_to_renderer_now) override;
 
   // IPC::Sender
   bool Send(IPC::Message* msg) override;
@@ -1291,6 +1295,11 @@
   std::unique_ptr<base::trace_event::TracedValue> CommitAsTracedValue(
       FrameHostMsg_DidCommitProvisionalLoad_Params* validated_params) const;
 
+  // Creates initiator-specific URLLoaderFactory objects for intiator origins
+  // registered via MarkInitiatorAsRequiringSeparateURLLoaderFactory method.
+  URLLoaderFactoryBundleInfo::OriginMap
+  CreateInitiatorSpecificURLLoaderFactories();
+
   // For now, RenderFrameHosts indirectly keep RenderViewHosts alive via a
   // refcount that calls Shutdown when it reaches zero.  This allows each
   // RenderFrameHostManager to just care about RenderFrameHosts, while ensuring
@@ -1701,6 +1710,11 @@
   network::mojom::URLLoaderFactoryPtr
       network_service_connection_error_handler_holder_;
 
+  // Set of request-initiator-origins that require a separate URLLoaderFactory
+  // (e.g. for handling requests initiated by extension content scripts that
+  // require relaxed CORS/CORB rules).
+  base::flat_set<url::Origin> initiators_requiring_separate_url_loader_factory_;
+
   // Holds the renderer generated ID and global request ID for the main frame
   // request.
   std::pair<int, GlobalRequestID> main_frame_request_ids_;
diff --git a/content/browser/media/encrypted_media_browsertest.cc b/content/browser/media/encrypted_media_browsertest.cc
index 4718aa6..4a157e6 100644
--- a/content/browser/media/encrypted_media_browsertest.cc
+++ b/content/browser/media/encrypted_media_browsertest.cc
@@ -182,9 +182,7 @@
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitchASCII(
-        switches::kAutoplayPolicy,
-        switches::autoplay::kNoUserGestureRequiredPolicy);
+    MediaBrowserTest::SetUpCommandLine(command_line);
 #if defined(SUPPORTS_EXTERNAL_CLEAR_KEY_IN_CONTENT_SHELL)
     scoped_feature_list_.InitWithFeatures({media::kExternalClearKeyForTesting},
                                           {});
diff --git a/content/browser/media/media_browsertest.cc b/content/browser/media/media_browsertest.cc
index 93bea4f..5aabc37 100644
--- a/content/browser/media/media_browsertest.cc
+++ b/content/browser/media/media_browsertest.cc
@@ -30,6 +30,9 @@
   command_line->AppendSwitchASCII(
       switches::kAutoplayPolicy,
       switches::autoplay::kNoUserGestureRequiredPolicy);
+  // Disable fallback after decode error to avoid unexpected test pass on the
+  // fallback path.
+  scoped_feature_list_.InitAndDisableFeature(media::kFallbackAfterDecodeError);
 }
 
 void MediaBrowserTest::RunMediaTestPage(const std::string& html_page,
diff --git a/content/browser/media/media_browsertest.h b/content/browser/media/media_browsertest.h
index b8e0b906..bc9af5cb 100644
--- a/content/browser/media/media_browsertest.h
+++ b/content/browser/media/media_browsertest.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/strings/string_split.h"
+#include "base/test/scoped_feature_list.h"
 #include "content/public/test/content_browser_test.h"
 
 namespace content {
@@ -44,6 +45,9 @@
 
   // Adds titles that RunTest() should wait for.
   virtual void AddTitlesToAwait(content::TitleWatcher* title_watcher);
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 }  // namespace content
diff --git a/content/browser/media/media_internals.cc b/content/browser/media/media_internals.cc
index 81d0ffa..6dd5d98 100644
--- a/content/browser/media/media_internals.cc
+++ b/content/browser/media/media_internals.cc
@@ -118,6 +118,10 @@
 const char kAudioLogStatusKey[] = "status";
 const char kAudioLogUpdateFunction[] = "media.updateAudioComponent";
 
+const char kAudioFocusFunction[] = "media.onReceiveAudioFocusState";
+const char kAudioFocusIdKey[] = "id";
+const char kAudioFocusSessionsKey[] = "sessions";
+
 }  // namespace
 
 namespace content {
@@ -716,30 +720,31 @@
 
 void MediaInternals::SendAudioFocusState() {
 #if !defined(OS_ANDROID)
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!CanUpdate())
     return;
 
-  base::DictionaryValue audio_focus_data;
-  const std::list<AudioFocusManager::StackRow>& stack =
-      AudioFocusManager::GetInstance()->audio_focus_stack_;
+  audio_focus_data_.Clear();
+
+  auto& stack = AudioFocusManager::GetInstance()->audio_focus_stack_;
 
   // We should go backwards through the stack so the top of the stack is always
   // shown first in the list.
   base::ListValue stack_data;
   for (auto iter = stack.rbegin(); iter != stack.rend(); ++iter) {
-    MediaSessionImpl::DebugInfo debug_info =
-        (*iter).media_session->GetDebugInfo();
     base::DictionaryValue media_session_data;
-    media_session_data.SetKey("name", base::Value(debug_info.name));
-    media_session_data.SetKey("owner", base::Value(debug_info.owner));
-    media_session_data.SetKey("state", base::Value(debug_info.state));
+    media_session_data.SetKey(kAudioFocusIdKey, base::Value((*iter)->id()));
     stack_data.GetList().push_back(std::move(media_session_data));
+
+    (*iter)->session()->GetDebugInfo(
+        base::BindOnce(&MediaInternals::DidGetAudioFocusDebugInfo,
+                       base::Unretained(this), (*iter)->id()));
   }
 
-  audio_focus_data.SetKey("sessions", std::move(stack_data));
+  audio_focus_data_.SetKey(kAudioFocusSessionsKey, std::move(stack_data));
 
-  SendUpdate(
-      SerializeUpdate("media.onReceiveAudioFocusState", &audio_focus_data));
+  if (stack.empty())
+    SendUpdate(SerializeUpdate(kAudioFocusFunction, &audio_focus_data_));
 #endif  // !defined(OS_ANDROID)
 }
 
@@ -824,7 +829,7 @@
 }
 
 void MediaInternals::OnFocusGained(
-    media_session::mojom::MediaSessionPtr media_session,
+    media_session::mojom::MediaSessionInfoPtr media_session,
     media_session::mojom::AudioFocusType type) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
@@ -834,7 +839,7 @@
 }
 
 void MediaInternals::OnFocusLost(
-    media_session::mojom::MediaSessionPtr media_session) {
+    media_session::mojom::MediaSessionInfoPtr media_session) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
@@ -842,6 +847,35 @@
                                           base::Unretained(this)));
 }
 
+void MediaInternals::DidGetAudioFocusDebugInfo(
+    int id,
+    media_session::mojom::MediaSessionDebugInfoPtr info) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  if (!CanUpdate())
+    return;
+
+  base::Value* sessions_list =
+      audio_focus_data_.FindKey(kAudioFocusSessionsKey);
+  DCHECK(sessions_list);
+
+  bool updated = false;
+  for (auto& session : sessions_list->GetList()) {
+    if (session.FindKey(kAudioFocusIdKey)->GetInt() != id)
+      continue;
+
+    session.SetKey("name", base::Value(info->name));
+    session.SetKey("owner", base::Value(info->owner));
+    session.SetKey("state", base::Value(info->state));
+    updated = true;
+  }
+
+  if (!updated)
+    return;
+
+  SendUpdate(SerializeUpdate(kAudioFocusFunction, &audio_focus_data_));
+}
+
 void MediaInternals::SendUpdate(const base::string16& update) {
   // SendUpdate() may be called from any thread, but must run on the UI thread.
   if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
diff --git a/content/browser/media/media_internals.h b/content/browser/media/media_internals.h
index 7aa7ddc..deeda062 100644
--- a/content/browser/media/media_internals.h
+++ b/content/browser/media/media_internals.h
@@ -121,10 +121,15 @@
   MediaInternals();
 
   // AudioFocusObserver implementation.
-  void OnFocusGained(media_session::mojom::MediaSessionPtr media_session,
+  void OnFocusGained(media_session::mojom::MediaSessionInfoPtr media_session,
                      media_session::mojom::AudioFocusType type) override;
   void OnFocusLost(
-      media_session::mojom::MediaSessionPtr media_session) override;
+      media_session::mojom::MediaSessionInfoPtr media_session) override;
+
+  // Called when we receive audio focus debug info to display.
+  void DidGetAudioFocusDebugInfo(
+      int id,
+      media_session::mojom::MediaSessionDebugInfoPtr info);
 
   // Sends |update| to each registered UpdateCallback.  Safe to call from any
   // thread, but will forward to the IO thread.
@@ -163,6 +168,9 @@
 
   NotificationRegistrar registrar_;
 
+  // Must only be accessed on the UI thread.
+  base::DictionaryValue audio_focus_data_;
+
   // All variables below must be accessed under |lock_|.
   base::Lock lock_;
   bool can_update_;
diff --git a/content/browser/media/media_internals_proxy.cc b/content/browser/media/media_internals_proxy.cc
index 0699f6e0..2b572f8b 100644
--- a/content/browser/media/media_internals_proxy.cc
+++ b/content/browser/media/media_internals_proxy.cc
@@ -40,6 +40,10 @@
 
   MediaInternals::GetInstance()->SendHistoricalMediaEvents();
 
+#if !defined(OS_ANDROID)
+  MediaInternals::GetInstance()->SendAudioFocusState();
+#endif
+
   // Ask MediaInternals for its data on IO thread.
   base::PostTaskWithTraits(
       FROM_HERE, {BrowserThread::IO},
@@ -51,10 +55,6 @@
   // TODO(xhwang): Investigate whether we can update on UI thread directly.
   MediaInternals::GetInstance()->SendAudioStreamData();
   MediaInternals::GetInstance()->SendVideoCaptureDeviceCapabilities();
-
-#if !defined(OS_ANDROID)
-  MediaInternals::GetInstance()->SendAudioFocusState();
-#endif
 }
 
 void MediaInternalsProxy::UpdateUIOnUIThread(const base::string16& update) {
diff --git a/content/browser/media/media_internals_unittest.cc b/content/browser/media/media_internals_unittest.cc
index 033385ac..8374c25 100644
--- a/content/browser/media/media_internals_unittest.cc
+++ b/content/browser/media/media_internals_unittest.cc
@@ -46,7 +46,7 @@
 
  protected:
   // Extracts and deserializes the JSON update data; merges into |update_data_|.
-  void UpdateCallbackImpl(const base::string16& update) {
+  virtual void UpdateCallbackImpl(const base::string16& update) {
     // Each update string looks like "<JavaScript Function Name>({<JSON>});"
     // or for video capabilities: "<JavaScript Function Name>([{<JSON>}]);".
     // In the second case we will be able to extract the dictionary if it is the
@@ -312,6 +312,8 @@
     scoped_command_line_.GetProcessCommandLine()->AppendSwitch(
         media_session::switches::kEnableAudioFocus);
 
+    run_loop_ = std::make_unique<base::RunLoop>();
+
     content::MediaInternals::GetInstance()->AddUpdateCallback(update_cb_);
     browser_context_.reset(new TestBrowserContext());
   }
@@ -322,10 +324,21 @@
   }
 
  protected:
-  void ExpectValue(base::ListValue expected_list) {
+  void UpdateCallbackImpl(const base::string16& update) override {
+    MediaInternalsTestBase::UpdateCallbackImpl(update);
+    run_loop_->Quit();
+  }
+
+  void ExpectValueAndReset(base::ListValue expected_list) {
     base::DictionaryValue expected_data;
     expected_data.SetKey("sessions", std::move(expected_list));
     EXPECT_EQ(expected_data, update_data_);
+    Reset();
+  }
+
+  void Reset() {
+    update_data_.Clear();
+    run_loop_ = std::make_unique<base::RunLoop>();
   }
 
   std::unique_ptr<TestWebContents> CreateWebContents() {
@@ -344,13 +357,16 @@
   }
 
   void WaitForCallback() {
-    AudioFocusManager::GetInstance()->FlushForTesting();
-    base::RunLoop().RunUntilIdle();
+    if (!update_data_.empty())
+      return;
+
+    run_loop_->Run();
   }
 
   MediaInternals::UpdateCallback update_cb_;
 
  private:
+  std::unique_ptr<base::RunLoop> run_loop_;
   base::test::ScopedCommandLine scoped_command_line_;
   std::unique_ptr<TestBrowserContext> browser_context_;
 };
@@ -366,13 +382,14 @@
   // Check JSON is what we expect.
   {
     base::DictionaryValue expected_session;
+    expected_session.SetKey("id", base::Value(0));
     expected_session.SetKey("name", GetAddressAsValue(media_session1));
     expected_session.SetKey("owner", base::Value(kTestTitle1));
     expected_session.SetKey("state", base::Value("Active"));
 
     base::ListValue expected_list;
     expected_list.GetList().push_back(std::move(expected_session));
-    ExpectValue(std::move(expected_list));
+    ExpectValueAndReset(std::move(expected_list));
   }
 
   // Create another media session.
@@ -382,15 +399,19 @@
   media_session2->RequestSystemAudioFocus(
       AudioFocusType::kGainTransientMayDuck);
   WaitForCallback();
+  Reset();
+  WaitForCallback();
 
   // Check JSON is what we expect.
   {
     base::DictionaryValue expected_session1;
+    expected_session1.SetKey("id", base::Value(1));
     expected_session1.SetKey("name", GetAddressAsValue(media_session2));
     expected_session1.SetKey("owner", base::Value(kTestTitle2));
     expected_session1.SetKey("state", base::Value("Active"));
 
     base::DictionaryValue expected_session2;
+    expected_session2.SetKey("id", base::Value(0));
     expected_session2.SetKey("name", GetAddressAsValue(media_session1));
     expected_session2.SetKey("owner", base::Value(kTestTitle1));
     expected_session2.SetKey("state", base::Value("Active Ducked"));
@@ -398,7 +419,7 @@
     base::ListValue expected_list;
     expected_list.GetList().push_back(std::move(expected_session1));
     expected_list.GetList().push_back(std::move(expected_session2));
-    ExpectValue(std::move(expected_list));
+    ExpectValueAndReset(std::move(expected_list));
   }
 
   // Abandon audio focus.
@@ -408,13 +429,14 @@
   // Check JSON is what we expect.
   {
     base::DictionaryValue expected_session;
+    expected_session.SetKey("id", base::Value(0));
     expected_session.SetKey("name", GetAddressAsValue(media_session1));
     expected_session.SetKey("owner", base::Value(kTestTitle1));
     expected_session.SetKey("state", base::Value("Active"));
 
     base::ListValue expected_list;
     expected_list.GetList().push_back(std::move(expected_session));
-    ExpectValue(std::move(expected_list));
+    ExpectValueAndReset(std::move(expected_list));
   }
 
   // Abandon audio focus.
@@ -424,7 +446,7 @@
   // Check JSON is what we expect.
   {
     base::ListValue expected_list;
-    ExpectValue(std::move(expected_list));
+    ExpectValueAndReset(std::move(expected_list));
   }
 }
 
diff --git a/content/browser/media/media_source_browsertest.cc b/content/browser/media/media_source_browsertest.cc
index c03bf22..593ae9b 100644
--- a/content/browser/media/media_source_browsertest.cc
+++ b/content/browser/media/media_source_browsertest.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include "base/command_line.h"
-#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "content/browser/media/media_browsertest.h"
 #include "media/base/media_switches.h"
@@ -37,7 +36,7 @@
 
 namespace content {
 
-class MediaSourceTest : public content::MediaBrowserTest {
+class MediaSourceTest : public MediaBrowserTest {
  public:
   void TestSimplePlayback(const std::string& media_file,
                           const std::string& media_type,
@@ -48,15 +47,6 @@
     RunMediaTestPage("media_source_player.html", query_params, expectation,
                      false);
   }
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitchASCII(
-        switches::kAutoplayPolicy,
-        switches::autoplay::kNoUserGestureRequiredPolicy);
-  }
-
- protected:
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(MediaSourceTest, Playback_VideoAudio_WebM) {
diff --git a/content/browser/media/media_suspend_browsertest.cc b/content/browser/media/media_suspend_browsertest.cc
index d005969..1cd1a66f 100644
--- a/content/browser/media/media_suspend_browsertest.cc
+++ b/content/browser/media/media_suspend_browsertest.cc
@@ -24,13 +24,13 @@
 // and that players suspended in this way can be resumed. Note: This does not
 // test suspend in various ready states; those tests are handled by layout tests
 // for ease of writing and ready state manipulation.
-class MediaSuspendTest : public content::MediaBrowserTest {
+class MediaSuspendTest : public MediaBrowserTest {
  public:
   void RunSuspendTest(const std::string& load_until) {
     base::StringPairs query_params;
     query_params.emplace_back("event", load_until);
 
-    GURL gurl = content::GetFileUrlWithQuery(
+    GURL gurl = GetFileUrlWithQuery(
         media::GetTestDataFilePath("media_suspend_test.html"),
         media::GetURLQueryString(query_params));
 
@@ -74,7 +74,7 @@
 
  protected:
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    content::MediaBrowserTest::SetUpCommandLine(command_line);
+    MediaBrowserTest::SetUpCommandLine(command_line);
     command_line->AppendSwitch(switches::kExposeInternalsForTesting);
   }
 };
diff --git a/content/browser/media/session/audio_focus_delegate.h b/content/browser/media/session/audio_focus_delegate.h
index ba23f16..1f163c7 100644
--- a/content/browser/media/session/audio_focus_delegate.h
+++ b/content/browser/media/session/audio_focus_delegate.h
@@ -27,12 +27,19 @@
 
   virtual ~AudioFocusDelegate() = default;
 
-  virtual bool RequestAudioFocus(
+  enum class AudioFocusResult {
+    kSuccess,
+    kFailed,
+    kDelayed,
+  };
+
+  virtual AudioFocusResult RequestAudioFocus(
       media_session::mojom::AudioFocusType audio_focus_type) = 0;
   virtual void AbandonAudioFocus() = 0;
 
   // Retrieves the current |AudioFocusType| for the associated |MediaSession|.
-  virtual media_session::mojom::AudioFocusType GetCurrentFocusType() const = 0;
+  virtual base::Optional<media_session::mojom::AudioFocusType>
+  GetCurrentFocusType() const = 0;
 };
 
 }  // namespace content
diff --git a/content/browser/media/session/audio_focus_delegate_android.cc b/content/browser/media/session/audio_focus_delegate_android.cc
index 59353a0..bb4e9cc 100644
--- a/content/browser/media/session/audio_focus_delegate_android.cc
+++ b/content/browser/media/session/audio_focus_delegate_android.cc
@@ -30,14 +30,17 @@
       Java_AudioFocusDelegate_create(env, reinterpret_cast<intptr_t>(this)));
 }
 
-bool AudioFocusDelegateAndroid::RequestAudioFocus(
+AudioFocusDelegate::AudioFocusResult
+AudioFocusDelegateAndroid::RequestAudioFocus(
     media_session::mojom::AudioFocusType audio_focus_type) {
   JNIEnv* env = base::android::AttachCurrentThread();
   DCHECK(env);
-  return Java_AudioFocusDelegate_requestAudioFocus(
+  bool success = Java_AudioFocusDelegate_requestAudioFocus(
       env, j_media_session_delegate_,
       audio_focus_type ==
           media_session::mojom::AudioFocusType::kGainTransientMayDuck);
+  return success ? AudioFocusDelegate::AudioFocusResult::kSuccess
+                 : AudioFocusDelegate::AudioFocusResult::kFailed;
 }
 
 void AudioFocusDelegateAndroid::AbandonAudioFocus() {
@@ -46,7 +49,7 @@
   Java_AudioFocusDelegate_abandonAudioFocus(env, j_media_session_delegate_);
 }
 
-media_session::mojom::AudioFocusType
+base::Optional<media_session::mojom::AudioFocusType>
 AudioFocusDelegateAndroid::GetCurrentFocusType() const {
   JNIEnv* env = base::android::AttachCurrentThread();
   DCHECK(env);
diff --git a/content/browser/media/session/audio_focus_delegate_android.h b/content/browser/media/session/audio_focus_delegate_android.h
index 8c18679..a8cd35c 100644
--- a/content/browser/media/session/audio_focus_delegate_android.h
+++ b/content/browser/media/session/audio_focus_delegate_android.h
@@ -27,10 +27,11 @@
 
   void Initialize();
 
-  bool RequestAudioFocus(
+  AudioFocusResult RequestAudioFocus(
       media_session::mojom::AudioFocusType audio_focus_type) override;
   void AbandonAudioFocus() override;
-  media_session::mojom::AudioFocusType GetCurrentFocusType() const override;
+  base::Optional<media_session::mojom::AudioFocusType> GetCurrentFocusType()
+      const override;
 
   // Called when the Android system requests the MediaSession to be suspended.
   // Called by Java through JNI.
diff --git a/content/browser/media/session/audio_focus_delegate_default.cc b/content/browser/media/session/audio_focus_delegate_default.cc
index eb7ec76aa..65d9ea08 100644
--- a/content/browser/media/session/audio_focus_delegate_default.cc
+++ b/content/browser/media/session/audio_focus_delegate_default.cc
@@ -5,6 +5,7 @@
 #include "content/browser/media/session/audio_focus_delegate.h"
 
 #include "content/browser/media/session/audio_focus_manager.h"
+#include "content/browser/media/session/media_session_impl.h"
 #include "services/media_session/public/cpp/switches.h"
 #include "services/media_session/public/mojom/audio_focus.mojom.h"
 
@@ -22,16 +23,19 @@
   ~AudioFocusDelegateDefault() override;
 
   // AudioFocusDelegate implementation.
-  bool RequestAudioFocus(AudioFocusType audio_focus_type) override;
+  AudioFocusResult RequestAudioFocus(AudioFocusType audio_focus_type) override;
   void AbandonAudioFocus() override;
-  AudioFocusType GetCurrentFocusType() const override;
+  base::Optional<AudioFocusType> GetCurrentFocusType() const override;
 
  private:
+  // Holds the current audio focus request id for |media_session_|.
+  base::Optional<AudioFocusManager::RequestId> request_id_;
+
   // Weak pointer because |this| is owned by |media_session_|.
   MediaSessionImpl* media_session_;
 
   // The last requested AudioFocusType by the associated |media_session_|.
-  AudioFocusType audio_focus_type_if_disabled_;
+  base::Optional<AudioFocusType> audio_focus_type_if_disabled_;
 };
 
 }  // anonymous namespace
@@ -42,29 +46,52 @@
 
 AudioFocusDelegateDefault::~AudioFocusDelegateDefault() = default;
 
-bool AudioFocusDelegateDefault::RequestAudioFocus(
-    AudioFocusType audio_focus_type) {
+AudioFocusDelegate::AudioFocusResult
+AudioFocusDelegateDefault::RequestAudioFocus(AudioFocusType audio_focus_type) {
   audio_focus_type_if_disabled_ = audio_focus_type;
 
   if (!media_session::IsAudioFocusEnabled())
-    return true;
+    return AudioFocusDelegate::AudioFocusResult::kSuccess;
 
-  AudioFocusManager::GetInstance()->RequestAudioFocus(media_session_,
-                                                      audio_focus_type);
-  return true;
+  media_session::mojom::MediaSessionInfoPtr session_info =
+      media_session_->GetMediaSessionInfoSync();
+
+  // Create a mojo interface pointer to our media session. This will allow the
+  // AudioFocusManager to interact with the media session across processes.
+  media_session::mojom::MediaSessionPtr media_session;
+  media_session_->BindToMojoRequest(mojo::MakeRequest(&media_session));
+
+  AudioFocusManager::RequestResponse response =
+      AudioFocusManager::GetInstance()->RequestAudioFocus(
+          std::move(media_session), std::move(session_info), audio_focus_type,
+          request_id_);
+
+  request_id_ = response.first;
+
+  return response.second ? AudioFocusDelegate::AudioFocusResult::kSuccess
+                         : AudioFocusDelegate::AudioFocusResult::kFailed;
 }
 
 void AudioFocusDelegateDefault::AbandonAudioFocus() {
-  AudioFocusManager::GetInstance()->AbandonAudioFocus(media_session_);
+  audio_focus_type_if_disabled_.reset();
+
+  if (!request_id_.has_value())
+    return;
+
+  AudioFocusManager::GetInstance()->AbandonAudioFocus(request_id_.value());
+  request_id_.reset();
 }
 
-AudioFocusType AudioFocusDelegateDefault::GetCurrentFocusType() const {
-  if (media_session::IsAudioFocusEnabled()) {
-    return AudioFocusManager::GetInstance()->GetFocusTypeForSession(
-        media_session_);
-  }
+base::Optional<AudioFocusType> AudioFocusDelegateDefault::GetCurrentFocusType()
+    const {
+  if (!media_session::IsAudioFocusEnabled())
+    return audio_focus_type_if_disabled_;
 
-  return audio_focus_type_if_disabled_;
+  if (!request_id_.has_value())
+    return base::Optional<AudioFocusType>();
+
+  return AudioFocusManager::GetInstance()->GetFocusTypeForSession(
+      request_id_.value());
 }
 
 // static
diff --git a/content/browser/media/session/audio_focus_delegate_default_browsertest.cc b/content/browser/media/session/audio_focus_delegate_default_browsertest.cc
index 686e12a..13416af 100644
--- a/content/browser/media/session/audio_focus_delegate_default_browsertest.cc
+++ b/content/browser/media/session/audio_focus_delegate_default_browsertest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/command_line.h"
+#include "content/browser/media/session/audio_focus_test_util.h"
 #include "content/browser/media/session/media_session_impl.h"
 #include "content/browser/media/session/mock_media_session_player_observer.h"
 #include "content/public/test/content_browser_test.h"
@@ -23,21 +24,33 @@
         player_observer(new MockMediaSessionPlayerObserver);
 
     MediaSessionImpl* media_session = MediaSessionImpl::Get(start_contents);
-    ASSERT_TRUE(media_session);
+    EXPECT_TRUE(media_session);
 
     MediaSessionImpl* other_media_session =
         MediaSessionImpl::Get(interrupt_contents);
-    ASSERT_TRUE(other_media_session);
+    EXPECT_TRUE(other_media_session);
 
     player_observer->StartNewPlayer();
-    media_session->AddPlayer(player_observer.get(), 0,
-                             media::MediaContentType::Persistent);
+
+    {
+      test::TestAudioFocusObserver observer;
+      media_session->AddPlayer(player_observer.get(), 0,
+                               media::MediaContentType::Persistent);
+      observer.WaitForGainedEvent();
+    }
+
     EXPECT_TRUE(media_session->IsActive());
     EXPECT_FALSE(other_media_session->IsActive());
 
     player_observer->StartNewPlayer();
-    other_media_session->AddPlayer(player_observer.get(), 1,
-                                   media::MediaContentType::Persistent);
+
+    {
+      test::TestAudioFocusObserver observer;
+      other_media_session->AddPlayer(player_observer.get(), 1,
+                                     media::MediaContentType::Persistent);
+      observer.WaitForGainedEvent();
+    }
+
     EXPECT_FALSE(media_session->IsActive());
     EXPECT_TRUE(other_media_session->IsActive());
 
diff --git a/content/browser/media/session/audio_focus_manager.cc b/content/browser/media/session/audio_focus_manager.cc
index 805ca47..c1671cb 100644
--- a/content/browser/media/session/audio_focus_manager.cc
+++ b/content/browser/media/session/audio_focus_manager.cc
@@ -4,8 +4,12 @@
 
 #include "content/browser/media/session/audio_focus_manager.h"
 
+#include "base/atomic_sequence_num.h"
+#include "base/memory/ptr_util.h"
+#include "base/task/post_task.h"
 #include "content/browser/media/session/audio_focus_observer.h"
 #include "content/browser/media/session/media_session_impl.h"
+#include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
@@ -14,14 +18,17 @@
 namespace content {
 
 using media_session::mojom::AudioFocusType;
+using media_session::mojom::MediaSessionPtr;
+using media_session::mojom::MediaSessionInfo;
+using media_session::mojom::MediaSessionInfoPtr;
 
 namespace {
 
-media_session::mojom::MediaSessionPtr GetSessionMojoPtr(
-    MediaSessionImpl* session) {
-  media_session::mojom::MediaSessionPtr media_session_ptr;
-  session->BindToMojoRequest(mojo::MakeRequest(&media_session_ptr));
-  return media_session_ptr;
+// Generate a unique audio focus request ID for the audio focus request. The IDs
+// are only handed out by the audio focus manager.
+int GenerateAudioFocusRequestId() {
+  static base::AtomicSequenceNumber request_id;
+  return request_id.GetNext();
 }
 
 }  // namespace
@@ -31,95 +38,104 @@
   return base::Singleton<AudioFocusManager>::get();
 }
 
-void AudioFocusManager::RequestAudioFocus(MediaSessionImpl* media_session,
-                                          AudioFocusType type) {
+AudioFocusManager::RequestResponse AudioFocusManager::RequestAudioFocus(
+    MediaSessionPtr media_session,
+    MediaSessionInfoPtr session_info,
+    AudioFocusType type,
+    base::Optional<RequestId> previous_id) {
+  DCHECK(!session_info.is_null());
+  DCHECK(media_session.is_bound());
+
   if (!audio_focus_stack_.empty() &&
-      audio_focus_stack_.back().media_session == media_session &&
-      audio_focus_stack_.back().audio_focus_type == type &&
-      audio_focus_stack_.back().media_session->IsActive()) {
+      previous_id == audio_focus_stack_.back()->id() &&
+      audio_focus_stack_.back()->audio_focus_type() == type &&
+      audio_focus_stack_.back()->IsActive()) {
     // Early returning if |media_session| is already on top (has focus) and is
     // active.
-    return;
+    return AudioFocusManager::RequestResponse(previous_id.value(), true);
   }
 
-  MaybeRemoveFocusEntry(media_session);
+  // If we have a previous ID then we should remove it from the stack.
+  if (previous_id.has_value())
+    RemoveFocusEntryIfPresent(previous_id.value());
 
-  // TODO(zqzhang): It seems like MediaSessionImpl is exposed to
-  // AudioFocusManager
-  // too much. Maybe it's better to do some abstraction and refactoring to clean
-  // up the relation between AudioFocusManager and MediaSessionImpl.
-  // See https://crbug.com/651069
   if (type == AudioFocusType::kGainTransientMayDuck) {
-    for (auto& old_session : audio_focus_stack_) {
-      old_session.media_session->StartDucking();
-    }
+    for (auto& old_session : audio_focus_stack_)
+      old_session->session()->StartDucking();
   } else {
     for (auto& old_session : audio_focus_stack_) {
-      if (old_session.media_session->IsActive()) {
-        if (old_session.media_session->HasPepper())
-          old_session.media_session->StartDucking();
-        else
-          old_session.media_session->Suspend(
-              MediaSessionImpl::SuspendType::kSystem);
+      // If the session has the force duck bit set then we should duck the
+      // session instead of suspending it.
+      if (old_session->info()->force_duck) {
+        old_session->session()->StartDucking();
+      } else {
+        old_session->session()->Suspend(MediaSession::SuspendType::kSystem);
       }
     }
   }
 
-  // Store the MediaSession and requested focus type.
-  audio_focus_stack_.emplace_back(media_session, type);
-  audio_focus_stack_.back().media_session->StopDucking();
+  audio_focus_stack_.push_back(std::make_unique<AudioFocusManager::StackRow>(
+      std::move(media_session), std::move(session_info), type,
+      previous_id ? *previous_id : GenerateAudioFocusRequestId()));
+
+  AudioFocusManager::StackRow* row = audio_focus_stack_.back().get();
+  row->session()->StopDucking();
 
   // Notify observers that we were gained audio focus.
   observers_.ForAllPtrs(
-      [media_session,
-       type](media_session::mojom::AudioFocusObserver* observer) {
-        observer->OnFocusGained(GetSessionMojoPtr(media_session), type);
+      [&row, type](media_session::mojom::AudioFocusObserver* observer) {
+        observer->OnFocusGained(row->info().Clone(), type);
       });
+
+  // We always grant the audio focus request but this may not always be the case
+  // in the future.
+  return AudioFocusManager::RequestResponse(row->id(), true);
 }
 
-void AudioFocusManager::AbandonAudioFocus(MediaSessionImpl* media_session) {
+void AudioFocusManager::AbandonAudioFocus(RequestId id) {
   if (audio_focus_stack_.empty())
     return;
 
-  if (audio_focus_stack_.back().media_session != media_session) {
-    MaybeRemoveFocusEntry(media_session);
+  if (audio_focus_stack_.back()->id() != id) {
+    RemoveFocusEntryIfPresent(id);
     return;
   }
 
+  auto row = std::move(audio_focus_stack_.back());
   audio_focus_stack_.pop_back();
+
   if (audio_focus_stack_.empty()) {
     // Notify observers that we lost audio focus.
     observers_.ForAllPtrs(
-        [media_session](media_session::mojom::AudioFocusObserver* observer) {
-          observer->OnFocusLost(GetSessionMojoPtr(media_session));
+        [&row](media_session::mojom::AudioFocusObserver* observer) {
+          observer->OnFocusLost(row->info().Clone());
         });
     return;
   }
 
-  // Allow the top-most MediaSessionImpl having Pepper to unduck pepper even if
-  // it's
-  // not active.
+  // Allow the top-most MediaSession having force duck to unduck even if
+  // it is not active.
   for (auto iter = audio_focus_stack_.rbegin();
        iter != audio_focus_stack_.rend(); ++iter) {
-    if (!iter->media_session->HasPepper())
+    if (!(*iter)->info()->force_duck)
       continue;
 
-    MediaSessionImpl* pepper_session = iter->media_session;
-    AudioFocusType focus_type = iter->audio_focus_type;
-    pepper_session->StopDucking();
-    MaybeRemoveFocusEntry(pepper_session);
-    audio_focus_stack_.emplace_back(pepper_session, focus_type);
+    auto duck_row = std::move(*iter);
+    duck_row->session()->StopDucking();
+    audio_focus_stack_.erase(std::next(iter).base());
+    audio_focus_stack_.push_back(std::move(duck_row));
     return;
   }
-  // Only try to unduck the new MediaSessionImpl on top. The session might be
-  // still
-  // inactive but it will not be resumed (so it doesn't surprise the user).
-  audio_focus_stack_.back().media_session->StopDucking();
+
+  // Only try to unduck the new MediaSession on top. The session might be
+  // still inactive but it will not be resumed (so it doesn't surprise the
+  // user).
+  audio_focus_stack_.back()->session()->StopDucking();
 
   // Notify observers that we lost audio focus.
   observers_.ForAllPtrs(
-      [media_session](media_session::mojom::AudioFocusObserver* observer) {
-        observer->OnFocusLost(GetSessionMojoPtr(media_session));
+      [&row](media_session::mojom::AudioFocusObserver* observer) {
+        observer->OnFocusLost(row->info().Clone());
       });
 }
 
@@ -128,11 +144,10 @@
   return observers_.AddPtr(std::move(observer));
 }
 
-AudioFocusType AudioFocusManager::GetFocusTypeForSession(
-    MediaSessionImpl* media_session) const {
-  for (auto row : audio_focus_stack_) {
-    if (row.media_session == media_session)
-      return row.audio_focus_type;
+AudioFocusType AudioFocusManager::GetFocusTypeForSession(RequestId id) {
+  for (auto& row : audio_focus_stack_) {
+    if (row->id() == id)
+      return row->audio_focus_type();
   }
 
   NOTREACHED();
@@ -150,6 +165,9 @@
 
 void AudioFocusManager::FlushForTesting() {
   observers_.FlushForTesting();
+
+  for (auto& session : audio_focus_stack_)
+    session->FlushForTesting();
 }
 
 AudioFocusManager::AudioFocusManager() {
@@ -160,14 +178,69 @@
 
 AudioFocusManager::~AudioFocusManager() = default;
 
-void AudioFocusManager::MaybeRemoveFocusEntry(MediaSessionImpl* media_session) {
+void AudioFocusManager::RemoveFocusEntryIfPresent(
+    AudioFocusManager::RequestId id) {
   for (auto iter = audio_focus_stack_.begin(); iter != audio_focus_stack_.end();
        ++iter) {
-    if (iter->media_session == media_session) {
+    if ((*iter)->id() == id) {
       audio_focus_stack_.erase(iter);
       break;
     }
   }
 }
 
+AudioFocusManager::StackRow::StackRow(MediaSessionPtr session,
+                                      MediaSessionInfoPtr current_info,
+                                      AudioFocusType audio_focus_type,
+                                      AudioFocusManager::RequestId id)
+    : id_(id),
+      session_(std::move(session)),
+      current_info_(std::move(current_info)),
+      audio_focus_type_(audio_focus_type),
+      binding_(this) {
+  media_session::mojom::MediaSessionObserverPtr observer;
+  binding_.Bind(mojo::MakeRequest(&observer));
+
+  // Listen for mojo errors.
+  binding_.set_connection_error_handler(base::BindOnce(
+      &AudioFocusManager::StackRow::OnConnectionError, base::Unretained(this)));
+  session_.set_connection_error_handler(base::BindOnce(
+      &AudioFocusManager::StackRow::OnConnectionError, base::Unretained(this)));
+
+  // Listen to info changes on the MediaSession.
+  session_->AddObserver(std::move(observer));
+};
+
+AudioFocusManager::StackRow::~StackRow() = default;
+
+void AudioFocusManager::StackRow::MediaSessionInfoChanged(
+    MediaSessionInfoPtr info) {
+  current_info_ = std::move(info);
+}
+
+media_session::mojom::MediaSession* AudioFocusManager::StackRow::session() {
+  return session_.get();
+}
+
+const media_session::mojom::MediaSessionInfoPtr&
+AudioFocusManager::StackRow::info() const {
+  return current_info_;
+}
+
+bool AudioFocusManager::StackRow::IsActive() const {
+  return current_info_->state == MediaSessionInfo::SessionState::kActive;
+}
+
+void AudioFocusManager::StackRow::FlushForTesting() {
+  session_.FlushForTesting();
+  binding_.FlushForTesting();
+}
+
+void AudioFocusManager::StackRow::OnConnectionError() {
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI},
+      base::BindOnce(&AudioFocusManager::AbandonAudioFocus,
+                     base::Unretained(AudioFocusManager::GetInstance()), id()));
+}
+
 }  // namespace content
diff --git a/content/browser/media/session/audio_focus_manager.h b/content/browser/media/session/audio_focus_manager.h
index 3bc2cc7..8871635 100644
--- a/content/browser/media/session/audio_focus_manager.h
+++ b/content/browser/media/session/audio_focus_manager.h
@@ -11,25 +11,38 @@
 #include "base/memory/singleton.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
 #include "mojo/public/cpp/bindings/interface_ptr_set.h"
 #include "services/media_session/public/mojom/audio_focus.mojom.h"
 
 namespace content {
 
-class MediaSessionImpl;
-
 class CONTENT_EXPORT AudioFocusManager {
  public:
+  using RequestId = int;
+  using RequestResponse = std::pair<RequestId, bool>;
+
   // Returns Chromium's internal AudioFocusManager.
   static AudioFocusManager* GetInstance();
 
-  void RequestAudioFocus(MediaSessionImpl* media_session,
-                         media_session::mojom::AudioFocusType type);
+  // Requests audio focus with |type| for the |media_session| with
+  // |session_info|. The function will return a |RequestResponse| which contains
+  // a |RequestId| and a boolean as to whether the request was successful. If a
+  // session wishes to request a different focus type it should provide its
+  // previous request id as |previous_id|.
+  RequestResponse RequestAudioFocus(
+      media_session::mojom::MediaSessionPtr media_session,
+      media_session::mojom::MediaSessionInfoPtr session_info,
+      media_session::mojom::AudioFocusType type,
+      base::Optional<RequestId> previous_id);
 
-  void AbandonAudioFocus(MediaSessionImpl* media_session);
+  // Abandons audio focus for a request with |id|. The next request on top of
+  // the stack will be granted audio focus.
+  void AbandonAudioFocus(RequestId id);
 
-  media_session::mojom::AudioFocusType GetFocusTypeForSession(
-      MediaSessionImpl* media_session) const;
+  // Returns the current audio focus type for a request with |id|.
+  media_session::mojom::AudioFocusType GetFocusTypeForSession(RequestId id);
 
   // Adds/removes audio focus observers.
   mojo::InterfacePtrSetElementId AddObserver(
@@ -53,22 +66,57 @@
   AudioFocusManager();
   ~AudioFocusManager();
 
-  void MaybeRemoveFocusEntry(MediaSessionImpl* media_session);
+  void RemoveFocusEntryIfPresent(RequestId id);
 
   // Weak reference of managed observers. Observers are expected to remove
   // themselves before being destroyed.
   mojo::InterfacePtrSet<media_session::mojom::AudioFocusObserver> observers_;
 
-  // Weak reference of managed MediaSessions and their requested focus type.
-  // A MediaSession must abandon audio focus before its destruction.
-  struct StackRow {
-    StackRow(MediaSessionImpl* media_session,
-             media_session::mojom::AudioFocusType audio_focus_type)
-        : media_session(media_session), audio_focus_type(audio_focus_type) {}
-    MediaSessionImpl* media_session;
-    media_session::mojom::AudioFocusType audio_focus_type;
+  // StackRow is a MediaSessionObserver and holds a cached copy of the latest
+  // MediaSessionInfo associated with the MediaSession. By keeping the info
+  // cached and readily available we can make audio focus decisions quickly
+  // without waiting on MediaSessions.
+  class StackRow : public media_session::mojom::MediaSessionObserver {
+   public:
+    StackRow(media_session::mojom::MediaSessionPtr session,
+             media_session::mojom::MediaSessionInfoPtr current_info,
+             media_session::mojom::AudioFocusType audio_focus_type,
+             RequestId id);
+    ~StackRow() override;
+
+    // media_session::mojom::MediaSessionObserver.
+    void MediaSessionInfoChanged(
+        media_session::mojom::MediaSessionInfoPtr info) override;
+
+    media_session::mojom::MediaSession* session();
+    const media_session::mojom::MediaSessionInfoPtr& info() const;
+    media_session::mojom::AudioFocusType audio_focus_type() const {
+      return audio_focus_type_;
+    }
+
+    bool IsActive() const;
+    int id() { return id_; }
+
+    // Flush any pending mojo messages for testing.
+    void FlushForTesting();
+
+   private:
+    void OnConnectionError();
+
+    int id_;
+    media_session::mojom::MediaSessionPtr session_;
+    media_session::mojom::MediaSessionInfoPtr current_info_;
+    media_session::mojom::AudioFocusType audio_focus_type_;
+    mojo::Binding<media_session::mojom::MediaSessionObserver> binding_;
+
+    DISALLOW_COPY_AND_ASSIGN(StackRow);
   };
-  std::list<StackRow> audio_focus_stack_;
+
+  // A stack of Mojo interface pointers and their requested audio focus type.
+  // A MediaSession must abandon audio focus before its destruction.
+  std::list<std::unique_ptr<StackRow>> audio_focus_stack_;
+
+  DISALLOW_COPY_AND_ASSIGN(AudioFocusManager);
 };
 
 }  // namespace content
diff --git a/content/browser/media/session/audio_focus_manager_unittest.cc b/content/browser/media/session/audio_focus_manager_unittest.cc
index fc4a597..da1040e5 100644
--- a/content/browser/media/session/audio_focus_manager_unittest.cc
+++ b/content/browser/media/session/audio_focus_manager_unittest.cc
@@ -7,9 +7,7 @@
 #include <memory>
 
 #include "base/command_line.h"
-#include "base/optional.h"
-#include "base/run_loop.h"
-#include "content/browser/media/session/audio_focus_observer.h"
+#include "content/browser/media/session/audio_focus_test_util.h"
 #include "content/browser/media/session/media_session_impl.h"
 #include "content/browser/media/session/media_session_player_observer.h"
 #include "content/public/test/mock_render_process_host.h"
@@ -17,51 +15,101 @@
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "content/test/test_web_contents.h"
 #include "media/base/media_content_type.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
 #include "services/media_session/public/cpp/switches.h"
 #include "services/media_session/public/mojom/audio_focus.mojom.h"
 
 namespace content {
 
 using media_session::mojom::AudioFocusType;
+using media_session::mojom::MediaSessionInfo;
+using media_session::mojom::MediaSessionInfoPtr;
 using media_session::mojom::MediaSessionPtr;
 
 namespace {
 
-class MockAudioFocusObserver : public AudioFocusObserver {
- public:
-  MockAudioFocusObserver() { RegisterAudioFocusObserver(); }
+static const base::Optional<AudioFocusManager::RequestId> kNoRequestId;
 
-  void OnFocusGained(MediaSessionPtr session, AudioFocusType type) override {
-    EXPECT_TRUE(session.is_bound());
-    focus_gained_call_ = type;
-    focus_lost_call_ = false;
+static const AudioFocusManager::RequestId kNoFocusedSession = -1;
+
+class MockMediaSession : public media_session::mojom::MediaSession {
+ public:
+  MockMediaSession() = default;
+  explicit MockMediaSession(bool force_duck) : force_duck_(force_duck) {}
+
+  ~MockMediaSession() override {}
+
+  void Suspend(SuspendType suspend_type) override {
+    DCHECK_EQ(SuspendType::kSystem, suspend_type);
+    SetState(MediaSessionInfo::SessionState::kSuspended);
   }
 
-  void OnFocusLost(MediaSessionPtr session) override {
-    EXPECT_TRUE(session.is_bound());
-    focus_lost_call_ = true;
-    focus_gained_call_.reset();
+  void StartDucking() override {
+    is_ducking_ = true;
+    NotifyObservers();
   }
 
-  base::Optional<AudioFocusType> focus_gained_call_;
-  bool focus_lost_call_ = false;
-};
+  void StopDucking() override {
+    is_ducking_ = false;
+    NotifyObservers();
+  }
 
-class MockMediaSessionPlayerObserver : public MediaSessionPlayerObserver {
- public:
-  void OnSuspend(int player_id) override {}
-  void OnResume(int player_id) override {}
-  void OnSeekForward(int player_id, base::TimeDelta seek_time) override {}
-  void OnSeekBackward(int player_id, base::TimeDelta seek_time) override {}
-  void OnSetVolumeMultiplier(
-      int player_id, double volume_multiplier) override {}
-  RenderFrameHost* render_frame_host() const override { return nullptr; }
+  void GetMediaSessionInfo(GetMediaSessionInfoCallback callback) override {
+    std::move(callback).Run(GetSessionInfoSync());
+  }
+
+  void AddObserver(
+      media_session::mojom::MediaSessionObserverPtr observer) override {
+    observers_.AddPtr(std::move(observer));
+  }
+
+  void GetDebugInfo(GetDebugInfoCallback callback) override {}
+
+  void BindToMojoRequest(
+      mojo::InterfaceRequest<media_session::mojom::MediaSession> request) {
+    bindings_.AddBinding(this, std::move(request));
+  }
+
+  void SetState(MediaSessionInfo::SessionState state) {
+    state_ = state;
+    NotifyObservers();
+  }
+
+  bool has_observers() const { return !observers_.empty(); }
+
+  void CloseAllObservers() { observers_.CloseAll(); }
+
+ private:
+  MediaSessionInfoPtr GetSessionInfoSync() {
+    MediaSessionInfoPtr info(MediaSessionInfo::New());
+    info->force_duck = force_duck_;
+    info->state = state_;
+    if (is_ducking_)
+      info->state = MediaSessionInfo::SessionState::kDucking;
+    return info;
+  }
+
+  void NotifyObservers() {
+    observers_.ForAllPtrs(
+        [this](media_session::mojom::MediaSessionObserver* observer) {
+          observer->MediaSessionInfoChanged(GetSessionInfoSync());
+        });
+
+    // This will flush all pending async messages on the observers.
+    observers_.FlushForTesting();
+  }
+
+  const bool force_duck_ = false;
+  bool is_ducking_ = false;
+  MediaSessionInfo::SessionState state_ =
+      MediaSessionInfo::SessionState::kInactive;
+
+  mojo::InterfacePtrSet<media_session::mojom::MediaSessionObserver> observers_;
+  mojo::BindingSet<media_session::mojom::MediaSession> bindings_;
 };
 
 }  // anonymous namespace
 
-using SuspendType = MediaSession::SuspendType;
-
 class AudioFocusManagerTest : public testing::Test {
  public:
   AudioFocusManagerTest() = default;
@@ -73,7 +121,6 @@
     RenderProcessHostImpl::set_render_process_host_factory_for_testing(
         rph_factory_.get());
     browser_context_.reset(new TestBrowserContext());
-    pepper_observer_.reset(new MockMediaSessionPlayerObserver());
 
     // AudioFocusManager is a singleton so we should make sure we reset any
     // state in between tests.
@@ -89,16 +136,16 @@
     rph_factory_.reset();
   }
 
-  MediaSessionImpl* GetAudioFocusedSession() const {
+  AudioFocusManager::RequestId GetAudioFocusedSession() const {
     const AudioFocusManager* manager = AudioFocusManager::GetInstance();
     const auto& audio_focus_stack = manager->audio_focus_stack_;
 
     for (auto iter = audio_focus_stack.rbegin();
          iter != audio_focus_stack.rend(); ++iter) {
-      if ((*iter).audio_focus_type == AudioFocusType::kGain)
-        return (*iter).media_session;
+      if ((*iter)->audio_focus_type() == AudioFocusType::kGain)
+        return (*iter)->id();
     }
-    return nullptr;
+    return kNoFocusedSession;
   }
 
   int GetTransientMaybeDuckCount() const {
@@ -108,7 +155,7 @@
 
     for (auto iter = audio_focus_stack.rbegin();
          iter != audio_focus_stack.rend(); ++iter) {
-      if ((*iter).audio_focus_type == AudioFocusType::kGainTransientMayDuck)
+      if ((*iter)->audio_focus_type() == AudioFocusType::kGainTransientMayDuck)
         ++count;
       else
         break;
@@ -117,31 +164,47 @@
     return count;
   }
 
-  double IsSessionDucking(MediaSessionImpl* session) {
-    return session->is_ducking_;  // Quack! Quack!
+  void AbandonAudioFocus(AudioFocusManager::RequestId id) {
+    AudioFocusManager::GetInstance()->AbandonAudioFocus(id);
+    FlushForTesting();
   }
 
-  void RequestAudioFocus(MediaSessionImpl* session,
-                         AudioFocusType audio_focus_type) {
-    session->RequestSystemAudioFocus(audio_focus_type);
+  AudioFocusManager::RequestId RequestAudioFocus(
+      MockMediaSession* session,
+      AudioFocusType audio_focus_type) {
+    return RequestAudioFocus(session, audio_focus_type, kNoRequestId);
   }
 
-  void AbandonAudioFocus(MediaSessionImpl* session) {
-    session->AbandonSystemAudioFocusIfNeeded();
+  AudioFocusManager::RequestId RequestAudioFocus(
+      MockMediaSession* session,
+      AudioFocusType audio_focus_type,
+      base::Optional<AudioFocusManager::RequestId> previous_id) {
+    media_session::mojom::MediaSessionPtr media_session;
+    session->BindToMojoRequest(mojo::MakeRequest(&media_session));
+
+    AudioFocusManager::RequestResponse response =
+        AudioFocusManager::GetInstance()->RequestAudioFocus(
+            std::move(media_session), test::GetMediaSessionInfoSync(session),
+            audio_focus_type, previous_id);
+
+    // If the audio focus was granted then we should set the session state to
+    // active.
+    if (response.second)
+      session->SetState(MediaSessionInfo::SessionState::kActive);
+
+    FlushForTesting();
+    return response.first;
   }
 
+  MediaSessionInfo::SessionState GetState(MockMediaSession* session) {
+    return test::GetMediaSessionInfoSync(session)->state;
+  }
+
+ private:
   void FlushForTesting() {
     AudioFocusManager::GetInstance()->FlushForTesting();
   }
 
-  std::unique_ptr<WebContents> CreateWebContents() {
-    return TestWebContents::Create(browser_context_.get(),
-        SiteInstance::SiteInstance::Create(browser_context_.get()));
-  }
-
-  std::unique_ptr<MediaSessionPlayerObserver> pepper_observer_;
-
- private:
   TestBrowserThreadBundle test_browser_thread_bundle_;
 
   std::unique_ptr<MockRenderProcessHostFactory> rph_factory_;
@@ -150,400 +213,365 @@
 
 TEST_F(AudioFocusManagerTest, InstanceAvailableAndSame) {
   AudioFocusManager* audio_focus_manager = AudioFocusManager::GetInstance();
-  ASSERT_TRUE(!!audio_focus_manager);
-  ASSERT_EQ(audio_focus_manager, AudioFocusManager::GetInstance());
+  EXPECT_TRUE(!!audio_focus_manager);
+  EXPECT_EQ(audio_focus_manager, AudioFocusManager::GetInstance());
+}
+
+TEST_F(AudioFocusManagerTest, AddObserverOnRequest) {
+  MockMediaSession media_session_1;
+  EXPECT_FALSE(media_session_1.has_observers());
+
+  RequestAudioFocus(&media_session_1, AudioFocusType::kGain, kNoRequestId);
+  EXPECT_TRUE(media_session_1.has_observers());
 }
 
 TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_ReplaceFocusedEntry) {
-  std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
-  MediaSessionImpl* media_session_1 =
-      MediaSessionImpl::Get(web_contents_1.get());
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
+  MockMediaSession media_session_3;
 
-  std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
-  MediaSessionImpl* media_session_2 =
-      MediaSessionImpl::Get(web_contents_2.get());
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(MediaSessionInfo::SessionState::kInactive,
+            GetState(&media_session_1));
+  EXPECT_EQ(MediaSessionInfo::SessionState::kInactive,
+            GetState(&media_session_2));
+  EXPECT_EQ(MediaSessionInfo::SessionState::kInactive,
+            GetState(&media_session_3));
 
-  std::unique_ptr<WebContents> web_contents_3(CreateWebContents());
-  MediaSessionImpl* media_session_3 =
-      MediaSessionImpl::Get(web_contents_3.get());
+  AudioFocusManager::RequestId request_id_1 =
+      RequestAudioFocus(&media_session_1, AudioFocusType::kGain);
+  EXPECT_EQ(request_id_1, GetAudioFocusedSession());
+  EXPECT_EQ(MediaSessionInfo::SessionState::kActive,
+            GetState(&media_session_1));
 
-  ASSERT_EQ(nullptr, GetAudioFocusedSession());
+  AudioFocusManager::RequestId request_id_2 =
+      RequestAudioFocus(&media_session_2, AudioFocusType::kGain);
+  EXPECT_EQ(request_id_2, GetAudioFocusedSession());
+  EXPECT_EQ(MediaSessionInfo::SessionState::kSuspended,
+            GetState(&media_session_1));
 
-  RequestAudioFocus(media_session_1, AudioFocusType::kGain);
-  ASSERT_EQ(media_session_1, GetAudioFocusedSession());
-
-  RequestAudioFocus(media_session_2, AudioFocusType::kGain);
-  ASSERT_EQ(media_session_2, GetAudioFocusedSession());
-
-  RequestAudioFocus(media_session_3, AudioFocusType::kGain);
-  ASSERT_EQ(media_session_3, GetAudioFocusedSession());
+  AudioFocusManager::RequestId request_id_3 =
+      RequestAudioFocus(&media_session_3, AudioFocusType::kGain);
+  EXPECT_EQ(request_id_3, GetAudioFocusedSession());
+  EXPECT_EQ(MediaSessionInfo::SessionState::kSuspended,
+            GetState(&media_session_2));
 }
 
 TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_Duplicate) {
-  std::unique_ptr<WebContents> web_contents(CreateWebContents());
-  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+  MockMediaSession media_session;
 
-  ASSERT_EQ(nullptr, GetAudioFocusedSession());
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
 
-  RequestAudioFocus(media_session, AudioFocusType::kGain);
-  ASSERT_EQ(media_session, GetAudioFocusedSession());
+  AudioFocusManager::RequestId request_id =
+      RequestAudioFocus(&media_session, AudioFocusType::kGain);
+  EXPECT_EQ(request_id, GetAudioFocusedSession());
 
-  RequestAudioFocus(media_session, AudioFocusType::kGain);
-  ASSERT_EQ(media_session, GetAudioFocusedSession());
+  RequestAudioFocus(&media_session, AudioFocusType::kGain, request_id);
+  EXPECT_EQ(request_id, GetAudioFocusedSession());
 }
 
 TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_FromTransient) {
-  std::unique_ptr<WebContents> web_contents(CreateWebContents());
-  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+  MockMediaSession media_session;
 
-  RequestAudioFocus(media_session, AudioFocusType::kGainTransientMayDuck);
-  ASSERT_EQ(nullptr, GetAudioFocusedSession());
-  ASSERT_EQ(1, GetTransientMaybeDuckCount());
+  AudioFocusManager::RequestId request_id =
+      RequestAudioFocus(&media_session, AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
 
-  RequestAudioFocus(media_session, AudioFocusType::kGain);
-  ASSERT_EQ(media_session, GetAudioFocusedSession());
-  ASSERT_EQ(0, GetTransientMaybeDuckCount());
+  RequestAudioFocus(&media_session, AudioFocusType::kGain, request_id);
+  EXPECT_EQ(request_id, GetAudioFocusedSession());
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
 }
 
 TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGain) {
-  std::unique_ptr<WebContents> web_contents(CreateWebContents());
-  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+  MockMediaSession media_session;
 
-  RequestAudioFocus(media_session, AudioFocusType::kGain);
-  ASSERT_EQ(media_session, GetAudioFocusedSession());
-  ASSERT_EQ(0, GetTransientMaybeDuckCount());
+  AudioFocusManager::RequestId request_id =
+      RequestAudioFocus(&media_session, AudioFocusType::kGain);
+  EXPECT_EQ(request_id, GetAudioFocusedSession());
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
 
-  RequestAudioFocus(media_session, AudioFocusType::kGainTransientMayDuck);
-  ASSERT_EQ(nullptr, GetAudioFocusedSession());
-  ASSERT_EQ(1, GetTransientMaybeDuckCount());
-  ASSERT_FALSE(IsSessionDucking(media_session));
+  RequestAudioFocus(&media_session, AudioFocusType::kGainTransientMayDuck,
+                    request_id);
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
+  EXPECT_NE(MediaSessionInfo::SessionState::kDucking, GetState(&media_session));
 }
 
 TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGainWhileDucking) {
-  std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
-  MediaSessionImpl* media_session_1 =
-      MediaSessionImpl::Get(web_contents_1.get());
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
 
-  std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
-  MediaSessionImpl* media_session_2 =
-      MediaSessionImpl::Get(web_contents_2.get());
+  AudioFocusManager::RequestId request_id =
+      RequestAudioFocus(&media_session_1, AudioFocusType::kGain);
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
+  EXPECT_EQ(MediaSessionInfo::SessionState::kActive,
+            GetState(&media_session_1));
 
-  RequestAudioFocus(media_session_1, AudioFocusType::kGain);
-  ASSERT_EQ(0, GetTransientMaybeDuckCount());
-  ASSERT_FALSE(IsSessionDucking(media_session_1));
+  RequestAudioFocus(&media_session_2, AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
+  EXPECT_EQ(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 
-  RequestAudioFocus(media_session_2, AudioFocusType::kGainTransientMayDuck);
-  ASSERT_EQ(1, GetTransientMaybeDuckCount());
-  ASSERT_TRUE(IsSessionDucking(media_session_1));
-
-  RequestAudioFocus(media_session_1, AudioFocusType::kGainTransientMayDuck);
-  ASSERT_EQ(2, GetTransientMaybeDuckCount());
-  ASSERT_FALSE(IsSessionDucking(media_session_1));
+  RequestAudioFocus(&media_session_1, AudioFocusType::kGainTransientMayDuck,
+                    request_id);
+  EXPECT_EQ(2, GetTransientMaybeDuckCount());
+  EXPECT_EQ(MediaSessionInfo::SessionState::kActive,
+            GetState(&media_session_1));
 }
 
 TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesFocusedEntry) {
-  std::unique_ptr<WebContents> web_contents(CreateWebContents());
-  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+  MockMediaSession media_session;
 
-  RequestAudioFocus(media_session, AudioFocusType::kGain);
-  ASSERT_EQ(media_session, GetAudioFocusedSession());
+  AudioFocusManager::RequestId request_id =
+      RequestAudioFocus(&media_session, AudioFocusType::kGain);
+  EXPECT_EQ(request_id, GetAudioFocusedSession());
 
-  AbandonAudioFocus(media_session);
-  ASSERT_EQ(nullptr, GetAudioFocusedSession());
+  AbandonAudioFocus(request_id);
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
 }
 
 TEST_F(AudioFocusManagerTest, AbandonAudioFocus_NoAssociatedEntry) {
-  std::unique_ptr<WebContents> web_contents(CreateWebContents());
-  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
-
-  AbandonAudioFocus(media_session);
-  ASSERT_EQ(nullptr, GetAudioFocusedSession());
+  AbandonAudioFocus(kNoFocusedSession);
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
 }
 
 TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesTransientEntry) {
-  std::unique_ptr<WebContents> web_contents(CreateWebContents());
-  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+  MockMediaSession media_session;
 
-  RequestAudioFocus(media_session, AudioFocusType::kGainTransientMayDuck);
-  ASSERT_EQ(1, GetTransientMaybeDuckCount());
+  AudioFocusManager::RequestId request_id =
+      RequestAudioFocus(&media_session, AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
 
   {
-    MockAudioFocusObserver observer;
-    AbandonAudioFocus(media_session);
-    FlushForTesting();
+    test::TestAudioFocusObserver observer;
+    AbandonAudioFocus(request_id);
 
     EXPECT_EQ(0, GetTransientMaybeDuckCount());
-    EXPECT_TRUE(observer.focus_lost_call_);
+    EXPECT_TRUE(observer.focus_lost_session_.Equals(
+        test::GetMediaSessionInfoSync(&media_session)));
   }
 }
 
 TEST_F(AudioFocusManagerTest, AbandonAudioFocus_WhileDuckingThenResume) {
-  std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
-  MediaSessionImpl* media_session_1 =
-      MediaSessionImpl::Get(web_contents_1.get());
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
 
-  std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
-  MediaSessionImpl* media_session_2 =
-      MediaSessionImpl::Get(web_contents_2.get());
+  AudioFocusManager::RequestId request_id_1 =
+      RequestAudioFocus(&media_session_1, AudioFocusType::kGain);
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
+  EXPECT_NE(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 
-  RequestAudioFocus(media_session_1, AudioFocusType::kGain);
-  ASSERT_EQ(0, GetTransientMaybeDuckCount());
-  ASSERT_FALSE(IsSessionDucking(media_session_1));
+  AudioFocusManager::RequestId request_id_2 = RequestAudioFocus(
+      &media_session_2, AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
+  EXPECT_EQ(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 
-  RequestAudioFocus(media_session_2, AudioFocusType::kGainTransientMayDuck);
-  ASSERT_EQ(1, GetTransientMaybeDuckCount());
-  ASSERT_TRUE(IsSessionDucking(media_session_1));
+  AbandonAudioFocus(request_id_1);
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
 
-  AbandonAudioFocus(media_session_1);
-  ASSERT_EQ(1, GetTransientMaybeDuckCount());
+  AbandonAudioFocus(request_id_2);
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
 
-  AbandonAudioFocus(media_session_2);
-  ASSERT_EQ(0, GetTransientMaybeDuckCount());
-
-  RequestAudioFocus(media_session_1, AudioFocusType::kGain);
-  ASSERT_FALSE(IsSessionDucking(media_session_1));
+  RequestAudioFocus(&media_session_1, AudioFocusType::kGain);
+  EXPECT_NE(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 }
 
 TEST_F(AudioFocusManagerTest, AbandonAudioFocus_StopsDucking) {
-  std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
-  MediaSessionImpl* media_session_1 =
-      MediaSessionImpl::Get(web_contents_1.get());
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
 
-  std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
-  MediaSessionImpl* media_session_2 =
-      MediaSessionImpl::Get(web_contents_2.get());
+  RequestAudioFocus(&media_session_1, AudioFocusType::kGain);
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
+  EXPECT_NE(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 
-  RequestAudioFocus(media_session_1, AudioFocusType::kGain);
-  ASSERT_EQ(0, GetTransientMaybeDuckCount());
-  ASSERT_FALSE(IsSessionDucking(media_session_1));
+  AudioFocusManager::RequestId request_id_2 = RequestAudioFocus(
+      &media_session_2, AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
+  EXPECT_EQ(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 
-  RequestAudioFocus(media_session_2, AudioFocusType::kGainTransientMayDuck);
-  ASSERT_EQ(1, GetTransientMaybeDuckCount());
-  ASSERT_TRUE(IsSessionDucking(media_session_1));
-
-  AbandonAudioFocus(media_session_2);
-  ASSERT_EQ(0, GetTransientMaybeDuckCount());
-  ASSERT_FALSE(IsSessionDucking(media_session_1));
+  AbandonAudioFocus(request_id_2);
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
+  EXPECT_NE(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 }
 
 TEST_F(AudioFocusManagerTest, DuckWhilePlaying) {
-  std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
-  MediaSessionImpl* media_session_1 =
-      MediaSessionImpl::Get(web_contents_1.get());
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
 
-  std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
-  MediaSessionImpl* media_session_2 =
-      MediaSessionImpl::Get(web_contents_2.get());
+  RequestAudioFocus(&media_session_1, AudioFocusType::kGain);
+  EXPECT_NE(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 
-  RequestAudioFocus(media_session_1, AudioFocusType::kGain);
-  ASSERT_FALSE(IsSessionDucking(media_session_1));
-
-  RequestAudioFocus(media_session_2, AudioFocusType::kGainTransientMayDuck);
-  ASSERT_TRUE(IsSessionDucking(media_session_1));
+  RequestAudioFocus(&media_session_2, AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 }
 
 TEST_F(AudioFocusManagerTest, GainSuspendsTransient) {
-  std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
-  MediaSessionImpl* media_session_1 =
-      MediaSessionImpl::Get(web_contents_1.get());
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
 
-  std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
-  MediaSessionImpl* media_session_2 =
-      MediaSessionImpl::Get(web_contents_2.get());
+  RequestAudioFocus(&media_session_2, AudioFocusType::kGainTransientMayDuck);
 
-  RequestAudioFocus(media_session_2, AudioFocusType::kGainTransientMayDuck);
-
-  RequestAudioFocus(media_session_1, AudioFocusType::kGain);
-  ASSERT_TRUE(media_session_2->IsSuspended());
+  RequestAudioFocus(&media_session_1, AudioFocusType::kGain);
+  EXPECT_EQ(MediaSessionInfo::SessionState::kSuspended,
+            GetState(&media_session_2));
 }
 
 TEST_F(AudioFocusManagerTest, DuckWithMultipleTransients) {
-  std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
-  MediaSessionImpl* media_session_1 =
-      MediaSessionImpl::Get(web_contents_1.get());
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
+  MockMediaSession media_session_3;
 
-  std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
-  MediaSessionImpl* media_session_2 =
-      MediaSessionImpl::Get(web_contents_2.get());
+  RequestAudioFocus(&media_session_1, AudioFocusType::kGain);
+  EXPECT_NE(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 
-  std::unique_ptr<WebContents> web_contents_3(CreateWebContents());
-  MediaSessionImpl* media_session_3 =
-      MediaSessionImpl::Get(web_contents_3.get());
+  AudioFocusManager::RequestId request_id_2 = RequestAudioFocus(
+      &media_session_2, AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 
-  RequestAudioFocus(media_session_1, AudioFocusType::kGain);
-  ASSERT_FALSE(IsSessionDucking(media_session_1));
+  AudioFocusManager::RequestId request_id_3 = RequestAudioFocus(
+      &media_session_3, AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 
-  RequestAudioFocus(media_session_2, AudioFocusType::kGainTransientMayDuck);
-  ASSERT_TRUE(IsSessionDucking(media_session_1));
+  AbandonAudioFocus(request_id_2);
+  EXPECT_EQ(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 
-  RequestAudioFocus(media_session_3, AudioFocusType::kGainTransientMayDuck);
-  ASSERT_TRUE(IsSessionDucking(media_session_1));
-
-  AbandonAudioFocus(media_session_2);
-  ASSERT_TRUE(IsSessionDucking(media_session_1));
-
-  AbandonAudioFocus(media_session_3);
-  ASSERT_FALSE(IsSessionDucking(media_session_1));
+  AbandonAudioFocus(request_id_3);
+  EXPECT_NE(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 }
 
-TEST_F(AudioFocusManagerTest, WebContentsDestroyed_ReleasesFocus) {
-  std::unique_ptr<WebContents> web_contents(CreateWebContents());
-  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+TEST_F(AudioFocusManagerTest, MediaSessionDestroyed_ReleasesFocus) {
+  {
+    MockMediaSession media_session;
 
-  RequestAudioFocus(media_session, AudioFocusType::kGain);
-  ASSERT_EQ(media_session, GetAudioFocusedSession());
+    AudioFocusManager::RequestId request_id =
+        RequestAudioFocus(&media_session, AudioFocusType::kGain);
+    EXPECT_EQ(request_id, GetAudioFocusedSession());
+  }
 
-  web_contents.reset();
-  ASSERT_EQ(nullptr, GetAudioFocusedSession());
+  // If the media session is destroyed without abandoning audio focus we do not
+  // know until we next interact with the manager.
+  MockMediaSession media_session;
+  RequestAudioFocus(&media_session, AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
 }
 
-TEST_F(AudioFocusManagerTest, WebContentsDestroyed_ReleasesTransients) {
-  std::unique_ptr<WebContents> web_contents(CreateWebContents());
-  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+TEST_F(AudioFocusManagerTest, MediaSessionDestroyed_ReleasesTransients) {
+  {
+    MockMediaSession media_session;
+    RequestAudioFocus(&media_session, AudioFocusType::kGainTransientMayDuck);
+    EXPECT_EQ(1, GetTransientMaybeDuckCount());
+  }
 
-  RequestAudioFocus(media_session, AudioFocusType::kGainTransientMayDuck);
-  ASSERT_EQ(1, GetTransientMaybeDuckCount());
-
-  web_contents.reset();
-  ASSERT_EQ(0, GetTransientMaybeDuckCount());
+  // If the media session is destroyed without abandoning audio focus we do not
+  // know until we next interact with the manager.
+  MockMediaSession media_session;
+  RequestAudioFocus(&media_session, AudioFocusType::kGain);
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
 }
 
-TEST_F(AudioFocusManagerTest, WebContentsDestroyed_StopsDucking) {
-  std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
-  MediaSessionImpl* media_session_1 =
-      MediaSessionImpl::Get(web_contents_1.get());
+TEST_F(AudioFocusManagerTest, GainDucksForceDuck) {
+  MockMediaSession media_session_1(true /* force_duck */);
+  MockMediaSession media_session_2;
 
-  std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
-  MediaSessionImpl* media_session_2 =
-      MediaSessionImpl::Get(web_contents_2.get());
+  RequestAudioFocus(&media_session_1, AudioFocusType::kGain);
 
-  RequestAudioFocus(media_session_1, AudioFocusType::kGain);
-  ASSERT_FALSE(IsSessionDucking(media_session_1));
+  AudioFocusManager::RequestId request_id_2 =
+      RequestAudioFocus(&media_session_2, AudioFocusType::kGain);
 
-  RequestAudioFocus(media_session_2, AudioFocusType::kGainTransientMayDuck);
-  ASSERT_TRUE(IsSessionDucking(media_session_1));
-
-  web_contents_2.reset();
-  ASSERT_FALSE(IsSessionDucking(media_session_1));
+  EXPECT_EQ(request_id_2, GetAudioFocusedSession());
+  EXPECT_EQ(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 }
 
-TEST_F(AudioFocusManagerTest, PepperRequestsGainFocus) {
-  std::unique_ptr<WebContents> web_contents(CreateWebContents());
-  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+TEST_F(AudioFocusManagerTest,
+       AbandoningGainFocusRevokesTopMostForceDuckSession) {
+  MockMediaSession media_session_1(true /* force_duck */);
+  MockMediaSession media_session_2;
+  MockMediaSession media_session_3;
 
-  media_session->AddPlayer(
-      pepper_observer_.get(), 0, media::MediaContentType::Pepper);
-  ASSERT_EQ(media_session, GetAudioFocusedSession());
+  AudioFocusManager::RequestId request_id_1 =
+      RequestAudioFocus(&media_session_1, AudioFocusType::kGain);
+  RequestAudioFocus(&media_session_2, AudioFocusType::kGain);
 
-  media_session->RemovePlayer(pepper_observer_.get(), 0);
-  ASSERT_EQ(nullptr, GetAudioFocusedSession());
-}
+  AudioFocusManager::RequestId request_id_3 =
+      RequestAudioFocus(&media_session_3, AudioFocusType::kGain);
+  EXPECT_EQ(request_id_3, GetAudioFocusedSession());
 
-TEST_F(AudioFocusManagerTest, GainDucksPepper) {
-  std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
-  MediaSessionImpl* media_session_1 =
-      MediaSessionImpl::Get(web_contents_1.get());
+  EXPECT_EQ(MediaSessionInfo::SessionState::kSuspended,
+            GetState(&media_session_2));
+  EXPECT_EQ(MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
 
-  std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
-  MediaSessionImpl* media_session_2 =
-      MediaSessionImpl::Get(web_contents_2.get());
-
-  media_session_1->AddPlayer(
-      pepper_observer_.get(), 0, media::MediaContentType::Pepper);
-
-  RequestAudioFocus(media_session_2, AudioFocusType::kGain);
-
-  ASSERT_EQ(media_session_2, GetAudioFocusedSession());
-  ASSERT_TRUE(media_session_1->IsActive());
-  ASSERT_TRUE(IsSessionDucking(media_session_1));
-}
-
-TEST_F(AudioFocusManagerTest, AbandoningGainFocusRevokesTopMostPepperSession) {
-  std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
-  MediaSessionImpl* media_session_1 =
-      MediaSessionImpl::Get(web_contents_1.get());
-
-  std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
-  MediaSessionImpl* media_session_2 =
-      MediaSessionImpl::Get(web_contents_2.get());
-
-  std::unique_ptr<WebContents> web_contents_3(CreateWebContents());
-  MediaSessionImpl* media_session_3 =
-      MediaSessionImpl::Get(web_contents_3.get());
-
-  media_session_1->AddPlayer(
-      pepper_observer_.get(), 0, media::MediaContentType::Pepper);
-
-  RequestAudioFocus(media_session_2, AudioFocusType::kGain);
-  RequestAudioFocus(media_session_3, AudioFocusType::kGain);
-
-  ASSERT_EQ(media_session_3, GetAudioFocusedSession());
-  ASSERT_TRUE(media_session_2->IsSuspended());
-  ASSERT_TRUE(media_session_1->IsActive());
-  ASSERT_TRUE(IsSessionDucking(media_session_1));
-
-  AbandonAudioFocus(media_session_3);
-  ASSERT_EQ(media_session_1, GetAudioFocusedSession());
+  AbandonAudioFocus(request_id_3);
+  EXPECT_EQ(request_id_1, GetAudioFocusedSession());
 }
 
 TEST_F(AudioFocusManagerTest, AudioFocusObserver_AbandonNoop) {
-  std::unique_ptr<WebContents> web_contents(CreateWebContents());
-  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+  test::TestAudioFocusObserver observer;
+  AbandonAudioFocus(kNoFocusedSession);
 
-  {
-    MockAudioFocusObserver observer;
-    AbandonAudioFocus(media_session);
-    FlushForTesting();
-
-    EXPECT_EQ(nullptr, GetAudioFocusedSession());
-    EXPECT_FALSE(observer.focus_lost_call_);
-  }
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_TRUE(observer.focus_lost_session_.is_null());
 }
 
 TEST_F(AudioFocusManagerTest, AudioFocusObserver_RequestNoop) {
-  std::unique_ptr<WebContents> web_contents(CreateWebContents());
-  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+  MockMediaSession media_session;
+  AudioFocusManager::RequestId request_id;
 
   {
-    MockAudioFocusObserver observer;
-    RequestAudioFocus(media_session, AudioFocusType::kGain);
-    FlushForTesting();
+    test::TestAudioFocusObserver observer;
+    request_id = RequestAudioFocus(&media_session, AudioFocusType::kGain);
 
-    EXPECT_EQ(media_session, GetAudioFocusedSession());
-    EXPECT_EQ(AudioFocusType::kGain, observer.focus_gained_call_.value());
+    EXPECT_EQ(request_id, GetAudioFocusedSession());
+    EXPECT_EQ(AudioFocusType::kGain, observer.focus_gained_type());
+    EXPECT_FALSE(observer.focus_gained_session_.is_null());
   }
 
   {
-    MockAudioFocusObserver observer;
-    RequestAudioFocus(media_session, AudioFocusType::kGain);
-    FlushForTesting();
+    test::TestAudioFocusObserver observer;
+    RequestAudioFocus(&media_session, AudioFocusType::kGain, request_id);
 
-    EXPECT_EQ(media_session, GetAudioFocusedSession());
-    EXPECT_FALSE(observer.focus_gained_call_.has_value());
+    EXPECT_EQ(request_id, GetAudioFocusedSession());
+    EXPECT_TRUE(observer.focus_gained_session_.is_null());
   }
 }
 
 TEST_F(AudioFocusManagerTest, AudioFocusObserver_TransientMayDuck) {
-  std::unique_ptr<WebContents> web_contents(CreateWebContents());
-  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+  MockMediaSession media_session;
+  AudioFocusManager::RequestId request_id;
 
   {
-    MockAudioFocusObserver observer;
-    RequestAudioFocus(media_session, AudioFocusType::kGainTransientMayDuck);
-    FlushForTesting();
+    test::TestAudioFocusObserver observer;
+    request_id = RequestAudioFocus(&media_session,
+                                   AudioFocusType::kGainTransientMayDuck);
 
     EXPECT_EQ(1, GetTransientMaybeDuckCount());
     EXPECT_EQ(AudioFocusType::kGainTransientMayDuck,
-              observer.focus_gained_call_.value());
+              observer.focus_gained_type());
+    EXPECT_FALSE(observer.focus_gained_session_.is_null());
   }
 
   {
-    MockAudioFocusObserver observer;
-    AbandonAudioFocus(media_session);
-    FlushForTesting();
+    test::TestAudioFocusObserver observer;
+    AbandonAudioFocus(request_id);
 
     EXPECT_EQ(0, GetTransientMaybeDuckCount());
-    EXPECT_TRUE(observer.focus_lost_call_);
+    EXPECT_TRUE(observer.focus_lost_session_.Equals(
+        test::GetMediaSessionInfoSync(&media_session)));
   }
 }
 
diff --git a/content/browser/media/session/audio_focus_observer.h b/content/browser/media/session/audio_focus_observer.h
index c4eae2b..97ee55a 100644
--- a/content/browser/media/session/audio_focus_observer.h
+++ b/content/browser/media/session/audio_focus_observer.h
@@ -23,11 +23,11 @@
   ~AudioFocusObserver() override;
 
   // The given media session gained audio focus with the specified type.
-  void OnFocusGained(::media_session::mojom::MediaSessionPtr,
+  void OnFocusGained(::media_session::mojom::MediaSessionInfoPtr,
                      media_session::mojom::AudioFocusType) override {}
 
   // The given media session lost audio focus.
-  void OnFocusLost(::media_session::mojom::MediaSessionPtr) override {}
+  void OnFocusLost(::media_session::mojom::MediaSessionInfoPtr) override {}
 
  protected:
   // Called by subclasses to (un-)register the observer with AudioFocusManager.
diff --git a/content/browser/media/session/audio_focus_test_util.cc b/content/browser/media/session/audio_focus_test_util.cc
new file mode 100644
index 0000000..b25d0ee5
--- /dev/null
+++ b/content/browser/media/session/audio_focus_test_util.cc
@@ -0,0 +1,73 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/media/session/audio_focus_test_util.h"
+
+namespace content {
+namespace test {
+
+namespace {
+
+void ReceivedSessionInfo(media_session::mojom::MediaSessionInfoPtr* info_out,
+                         base::RepeatingClosure callback,
+                         media_session::mojom::MediaSessionInfoPtr result) {
+  *info_out = std::move(result);
+  std::move(callback).Run();
+}
+
+}  // namespace
+
+TestAudioFocusObserver::TestAudioFocusObserver() {
+  RegisterAudioFocusObserver();
+}
+
+TestAudioFocusObserver::~TestAudioFocusObserver() = default;
+
+void TestAudioFocusObserver::OnFocusGained(
+    media_session::mojom::MediaSessionInfoPtr session,
+    media_session::mojom::AudioFocusType type) {
+  focus_gained_type_ = type;
+  focus_gained_session_ = std::move(session);
+
+  if (wait_for_gained_)
+    run_loop_.Quit();
+}
+
+void TestAudioFocusObserver::OnFocusLost(
+    media_session::mojom::MediaSessionInfoPtr session) {
+  focus_lost_session_ = std::move(session);
+
+  if (wait_for_lost_)
+    run_loop_.Quit();
+}
+
+void TestAudioFocusObserver::WaitForGainedEvent() {
+  if (!focus_gained_session_.is_null())
+    return;
+
+  wait_for_gained_ = true;
+  run_loop_.Run();
+}
+
+void TestAudioFocusObserver::WaitForLostEvent() {
+  if (!focus_lost_session_.is_null())
+    return;
+
+  wait_for_lost_ = true;
+  run_loop_.Run();
+}
+
+media_session::mojom::MediaSessionInfoPtr GetMediaSessionInfoSync(
+    media_session::mojom::MediaSession* session) {
+  media_session::mojom::MediaSessionInfoPtr session_info;
+  base::RunLoop run_loop;
+
+  session->GetMediaSessionInfo(base::BindOnce(
+      &ReceivedSessionInfo, &session_info, run_loop.QuitClosure()));
+
+  return session_info;
+}
+
+}  // namespace test
+}  // namespace content
diff --git a/content/browser/media/session/audio_focus_test_util.h b/content/browser/media/session/audio_focus_test_util.h
new file mode 100644
index 0000000..3ed51660
--- /dev/null
+++ b/content/browser/media/session/audio_focus_test_util.h
@@ -0,0 +1,54 @@
+// 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 CONTENT_BROWSER_MEDIA_SESSION_AUDIO_FOCUS_TEST_UTIL_H_
+#define CONTENT_BROWSER_MEDIA_SESSION_AUDIO_FOCUS_TEST_UTIL_H_
+
+#include "base/run_loop.h"
+#include "content/browser/media/session/audio_focus_observer.h"
+#include "services/media_session/public/mojom/audio_focus.mojom.h"
+
+namespace content {
+namespace test {
+
+class TestAudioFocusObserver : public AudioFocusObserver {
+ public:
+  TestAudioFocusObserver();
+  ~TestAudioFocusObserver() override;
+
+  void OnFocusGained(media_session::mojom::MediaSessionInfoPtr,
+                     media_session::mojom::AudioFocusType) override;
+
+  void OnFocusLost(media_session::mojom::MediaSessionInfoPtr) override;
+
+  void WaitForGainedEvent();
+  void WaitForLostEvent();
+
+  media_session::mojom::AudioFocusType focus_gained_type() const {
+    DCHECK(!focus_gained_session_.is_null());
+    return focus_gained_type_;
+  }
+
+  // These store the values we received.
+  media_session::mojom::MediaSessionInfoPtr focus_gained_session_;
+  media_session::mojom::MediaSessionInfoPtr focus_lost_session_;
+
+ private:
+  media_session::mojom::AudioFocusType focus_gained_type_;
+
+  // If either of these are true we will quit the run loop if we observe a gain
+  // or lost event.
+  bool wait_for_gained_ = false;
+  bool wait_for_lost_ = false;
+
+  base::RunLoop run_loop_;
+};
+
+media_session::mojom::MediaSessionInfoPtr GetMediaSessionInfoSync(
+    media_session::mojom::MediaSession*);
+
+}  // namespace test
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_MEDIA_SESSION_AUDIO_FOCUS_TEST_UTIL_H_
diff --git a/content/browser/media/session/media_session_impl.cc b/content/browser/media/session/media_session_impl.cc
index 2f712fd0..47b45ec 100644
--- a/content/browser/media/session/media_session_impl.cc
+++ b/content/browser/media/session/media_session_impl.cc
@@ -29,6 +29,7 @@
 namespace content {
 
 using MediaSessionUserAction = MediaSessionUmaHelper::MediaSessionUserAction;
+using media_session::mojom::MediaSessionInfo;
 
 namespace {
 
@@ -43,6 +44,11 @@
 
 using MapRenderFrameHostToDepth = std::map<RenderFrameHost*, size_t>;
 
+using media_session::mojom::AudioFocusType;
+
+using MediaSessionSuspendedSource =
+    MediaSessionUmaHelper::MediaSessionSuspendedSource;
+
 size_t ComputeFrameDepth(RenderFrameHost* rfh,
                          MapRenderFrameHostToDepth* map_rfh_to_depth) {
   DCHECK(rfh);
@@ -90,11 +96,6 @@
 
 }  // anonymous namespace
 
-using media_session::mojom::AudioFocusType;
-
-using MediaSessionSuspendedSource =
-    MediaSessionUmaHelper::MediaSessionSuspendedSource;
-
 MediaSessionImpl::PlayerIdentifier::PlayerIdentifier(
     MediaSessionPlayerObserver* observer,
     int player_id)
@@ -105,6 +106,12 @@
   return this->observer == other.observer && this->player_id == other.player_id;
 }
 
+bool MediaSessionImpl::PlayerIdentifier::operator<(
+    const PlayerIdentifier& other) const {
+  return MediaSessionImpl::PlayerIdentifier::Hash()(*this) <
+         MediaSessionImpl::PlayerIdentifier::Hash()(other);
+}
+
 size_t MediaSessionImpl::PlayerIdentifier::Hash::operator()(
     const PlayerIdentifier& player_identifier) const {
   size_t hash = BASE_HASH_NAMESPACE::hash<MediaSessionPlayerObserver*>()(
@@ -151,6 +158,7 @@
   normal_players_.clear();
   pepper_players_.clear();
   one_shot_players_.clear();
+
   AbandonSystemAudioFocusIfNeeded();
 }
 
@@ -217,19 +225,27 @@
   else
     required_audio_focus_type = AudioFocusType::kGainTransientMayDuck;
 
+  PlayerIdentifier key(observer, player_id);
+
   // If the audio focus is already granted and is of type Content, there is
   // nothing to do. If it is granted of type Transient the requested type is
   // also transient, there is also nothing to do. Otherwise, the session needs
   // to request audio focus again.
   if (audio_focus_state_ == State::ACTIVE) {
-    AudioFocusType current_focus_type = delegate_->GetCurrentFocusType();
+    base::Optional<AudioFocusType> current_focus_type =
+        delegate_->GetCurrentFocusType();
     if (current_focus_type == AudioFocusType::kGain ||
         current_focus_type == required_audio_focus_type) {
-      normal_players_.insert(PlayerIdentifier(observer, player_id));
+      auto iter = normal_players_.find(key);
+      if (iter == normal_players_.end())
+        normal_players_.emplace(std::move(key), required_audio_focus_type);
+      else
+        iter->second = required_audio_focus_type;
       return true;
     }
   }
 
+  bool old_controllable = IsControllable();
   State old_audio_focus_state = audio_focus_state_;
   RequestSystemAudioFocus(required_audio_focus_type);
 
@@ -241,10 +257,19 @@
   if (old_audio_focus_state != State::ACTIVE)
     normal_players_.clear();
 
-  normal_players_.insert(PlayerIdentifier(observer, player_id));
+  auto iter = normal_players_.find(key);
+  if (iter == normal_players_.end())
+    normal_players_.emplace(std::move(key), required_audio_focus_type);
+  else
+    iter->second = required_audio_focus_type;
 
   UpdateRoutedService();
-  NotifyAboutStateChange();
+
+  if (old_audio_focus_state != audio_focus_state_ ||
+      old_controllable != IsControllable()) {
+    NotifyAboutStateChange();
+  }
+
   return true;
 }
 
@@ -254,11 +279,11 @@
 
   PlayerIdentifier identifier(observer, player_id);
 
-  auto it = normal_players_.find(identifier);
-  if (it != normal_players_.end())
-    normal_players_.erase(it);
+  auto iter = normal_players_.find(identifier);
+  if (iter != normal_players_.end())
+    normal_players_.erase(iter);
 
-  it = pepper_players_.find(identifier);
+  auto it = pepper_players_.find(identifier);
   if (it != pepper_players_.end())
     pepper_players_.erase(it);
 
@@ -280,7 +305,7 @@
   bool was_controllable = IsControllable();
 
   for (auto it = normal_players_.begin(); it != normal_players_.end();) {
-    if (it->observer == observer)
+    if (it->first.observer == observer)
       normal_players_.erase(it++);
     else
       ++it;
@@ -361,11 +386,14 @@
   // must be requested.
   if (suspend_type != SuspendType::kSystem) {
     // Request audio focus again in case we lost it because another app started
-    // playing while the playback was paused.
-    State audio_focus_state = RequestSystemAudioFocus(desired_audio_focus_type_)
-                                  ? State::ACTIVE
-                                  : State::INACTIVE;
-    SetAudioFocusState(audio_focus_state);
+    // playing while the playback was paused. If the audio focus request is
+    // delayed we will resume the player when the request completes.
+    AudioFocusDelegate::AudioFocusResult result =
+        RequestSystemAudioFocus(desired_audio_focus_type_);
+
+    SetAudioFocusState(result != AudioFocusDelegate::AudioFocusResult::kFailed
+                           ? State::ACTIVE
+                           : State::INACTIVE);
 
     if (audio_focus_state_ != State::ACTIVE)
       return;
@@ -413,12 +441,12 @@
 
 void MediaSessionImpl::SeekForward(base::TimeDelta seek_time) {
   for (const auto& it : normal_players_)
-    it.observer->OnSeekForward(it.player_id, seek_time);
+    it.first.observer->OnSeekForward(it.first.player_id, seek_time);
 }
 
 void MediaSessionImpl::SeekBackward(base::TimeDelta seek_time) {
   for (const auto& it : normal_players_)
-    it.observer->OnSeekBackward(it.player_id, seek_time);
+    it.first.observer->OnSeekBackward(it.first.player_id, seek_time);
 }
 
 bool MediaSessionImpl::IsControllable() const {
@@ -448,6 +476,7 @@
     return;
   is_ducking_ = true;
   UpdateVolumeMultiplier();
+  NotifyObserversInfoChanged();
 }
 
 void MediaSessionImpl::StopDucking() {
@@ -455,11 +484,15 @@
     return;
   is_ducking_ = false;
   UpdateVolumeMultiplier();
+  NotifyObserversInfoChanged();
 }
 
 void MediaSessionImpl::UpdateVolumeMultiplier() {
-  for (const auto& it : normal_players_)
-    it.observer->OnSetVolumeMultiplier(it.player_id, GetVolumeMultiplier());
+  for (const auto& it : normal_players_) {
+    it.first.observer->OnSetVolumeMultiplier(it.first.player_id,
+                                             GetVolumeMultiplier());
+  }
+
   for (const auto& it : pepper_players_)
     it.observer->OnSetVolumeMultiplier(it.player_id, GetVolumeMultiplier());
 }
@@ -502,6 +535,12 @@
   AbandonSystemAudioFocusIfNeeded();
 }
 
+void MediaSessionImpl::OnSystemAudioFocusRequested(bool result) {
+  uma_helper_.RecordRequestAudioFocusResult(result);
+  if (result)
+    StopDucking();
+}
+
 void MediaSessionImpl::OnSuspendInternal(SuspendType suspend_type,
                                          State new_state) {
   DCHECK(!HasPepper());
@@ -548,7 +587,7 @@
     // the page in which case the player is already paused.
     // Otherwise, the players need to be paused.
     for (const auto& it : normal_players_)
-      it.observer->OnSuspend(it.player_id);
+      it.first.observer->OnSuspend(it.first.player_id);
   }
 
   for (const auto& it : pepper_players_)
@@ -565,7 +604,7 @@
   SetAudioFocusState(State::ACTIVE);
 
   for (const auto& it : normal_players_)
-    it.observer->OnResume(it.player_id);
+    it.first.observer->OnResume(it.first.player_id);
 
   for (const auto& it : pepper_players_)
     it.observer->OnSetVolumeMultiplier(it.player_id, GetVolumeMultiplier());
@@ -589,29 +628,37 @@
   delegate_ = AudioFocusDelegate::Create(this);
 }
 
-bool MediaSessionImpl::RequestSystemAudioFocus(
+AudioFocusDelegate::AudioFocusResult MediaSessionImpl::RequestSystemAudioFocus(
     AudioFocusType audio_focus_type) {
-  bool result = delegate_->RequestAudioFocus(audio_focus_type);
-  uma_helper_.RecordRequestAudioFocusResult(result);
-
-  // Make sure we are unducked.
-  if (result)
-    StopDucking();
-
-  // MediaSessionImpl must change its state & audio focus type AFTER requesting
-  // audio focus.
-  SetAudioFocusState(result ? State::ACTIVE : State::INACTIVE);
+  AudioFocusDelegate::AudioFocusResult result =
+      delegate_->RequestAudioFocus(audio_focus_type);
   desired_audio_focus_type_ = audio_focus_type;
+
+  bool success = result != AudioFocusDelegate::AudioFocusResult::kFailed;
+  SetAudioFocusState(success ? State::ACTIVE : State::INACTIVE);
+
+  // If we are delayed then we should return now and wait for the response from
+  // the audio focus delegate.
+  if (result == AudioFocusDelegate::AudioFocusResult::kDelayed)
+    return result;
+
+  OnSystemAudioFocusRequested(success);
   return result;
 }
 
-const MediaSessionImpl::DebugInfo MediaSessionImpl::GetDebugInfo() {
-  MediaSessionImpl::DebugInfo debug_info;
+void MediaSessionImpl::BindToMojoRequest(
+    mojo::InterfaceRequest<media_session::mojom::MediaSession> request) {
+  bindings_.AddBinding(this, std::move(request));
+}
+
+void MediaSessionImpl::GetDebugInfo(GetDebugInfoCallback callback) {
+  media_session::mojom::MediaSessionDebugInfoPtr info(
+      media_session::mojom::MediaSessionDebugInfo::New());
 
   // Convert the address of |this| to a string and use it as the name.
   std::stringstream stream;
   stream << this;
-  debug_info.name = stream.str();
+  info->name = stream.str();
 
   // Add the title and the url to the owner.
   std::vector<std::string> owner_parts;
@@ -619,21 +666,84 @@
                       base::UTF16ToUTF8(web_contents()->GetTitle()));
   MaybePushBackString(owner_parts,
                       web_contents()->GetLastCommittedURL().spec());
-  debug_info.owner = base::JoinString(owner_parts, kDebugInfoOwnerSeparator);
+  info->owner = base::JoinString(owner_parts, kDebugInfoOwnerSeparator);
 
   // Add the ducking state to state.
   std::vector<std::string> state_parts;
   MaybePushBackString(state_parts,
                       IsActive() ? kDebugInfoActive : kDebugInfoInactive);
   MaybePushBackString(state_parts, is_ducking_ ? kDebugInfoDucked : "");
-  debug_info.state = base::JoinString(state_parts, kDebugInfoStateSeparator);
+  info->state = base::JoinString(state_parts, kDebugInfoStateSeparator);
 
-  return debug_info;
+  std::move(callback).Run(std::move(info));
 }
 
-void MediaSessionImpl::BindToMojoRequest(
-    mojo::InterfaceRequest<media_session::mojom::MediaSession> request) {
-  bindings_.AddBinding(this, std::move(request));
+media_session::mojom::MediaSessionInfoPtr
+MediaSessionImpl::GetMediaSessionInfoSync() {
+  media_session::mojom::MediaSessionInfoPtr info(
+      media_session::mojom::MediaSessionInfo::New());
+
+  switch (audio_focus_state_) {
+    case State::ACTIVE:
+      info->state = MediaSessionInfo::SessionState::kActive;
+      break;
+    case State::SUSPENDED:
+      info->state = MediaSessionInfo::SessionState::kSuspended;
+      break;
+    case State::INACTIVE:
+      info->state = MediaSessionInfo::SessionState::kInactive;
+      break;
+  }
+
+  // The state should always be kDucked if we are ducked.
+  if (is_ducking_)
+    info->state = MediaSessionInfo::SessionState::kDucking;
+
+  // If we have Pepper players then we should force ducking.
+  info->force_duck = HasPepper();
+  return info;
+}
+
+void MediaSessionImpl::GetMediaSessionInfo(
+    GetMediaSessionInfoCallback callback) {
+  std::move(callback).Run(GetMediaSessionInfoSync());
+}
+
+void MediaSessionImpl::AddObserver(
+    media_session::mojom::MediaSessionObserverPtr observer) {
+  observer->MediaSessionInfoChanged(GetMediaSessionInfoSync());
+  mojo_observers_.AddPtr(std::move(observer));
+}
+
+void MediaSessionImpl::FinishSystemAudioFocusRequest(
+    AudioFocusType audio_focus_type,
+    bool result) {
+  // If the media session is not active then we do not need to enforce the
+  // result of the audio focus request.
+  if (audio_focus_state_ != State::ACTIVE) {
+    AbandonSystemAudioFocusIfNeeded();
+    return;
+  }
+
+  OnSystemAudioFocusRequested(result);
+
+  if (!result) {
+    switch (audio_focus_type) {
+      case AudioFocusType::kGain:
+        // If the gain audio focus request failed then we should suspend the
+        // media session.
+        OnSuspendInternal(SuspendType::kSystem, State::SUSPENDED);
+        break;
+      case AudioFocusType::kGainTransientMayDuck:
+        // The focus request failed, we should suspend any players that have
+        // the same audio focus type.
+        for (auto& player : normal_players_) {
+          if (audio_focus_type == player.second)
+            player.first.observer->OnSuspend(player.first.player_id);
+        }
+        break;
+    }
+  }
 }
 
 void MediaSessionImpl::AbandonSystemAudioFocusIfNeeded() {
@@ -672,24 +782,44 @@
       uma_helper_.OnSessionInactive();
       break;
   }
+
+  NotifyObserversInfoChanged();
+}
+
+void MediaSessionImpl::FlushForTesting() {
+  mojo_observers_.FlushForTesting();
+}
+
+void MediaSessionImpl::NotifyObserversInfoChanged() {
+  media_session::mojom::MediaSessionInfoPtr current_info =
+      GetMediaSessionInfoSync();
+
+  mojo_observers_.ForAllPtrs(
+      [&current_info](media_session::mojom::MediaSessionObserver* observer) {
+        observer->MediaSessionInfoChanged(current_info.Clone());
+      });
 }
 
 bool MediaSessionImpl::AddPepperPlayer(MediaSessionPlayerObserver* observer,
                                        int player_id) {
-  bool success = RequestSystemAudioFocus(AudioFocusType::kGain);
-  DCHECK(success);
+  AudioFocusDelegate::AudioFocusResult result =
+      RequestSystemAudioFocus(AudioFocusType::kGain);
+  DCHECK_NE(AudioFocusDelegate::AudioFocusResult::kFailed, result);
 
   pepper_players_.insert(PlayerIdentifier(observer, player_id));
 
   observer->OnSetVolumeMultiplier(player_id, GetVolumeMultiplier());
 
   NotifyAboutStateChange();
-  return success;
+  return result != AudioFocusDelegate::AudioFocusResult::kFailed;
 }
 
 bool MediaSessionImpl::AddOneShotPlayer(MediaSessionPlayerObserver* observer,
                                         int player_id) {
-  if (!RequestSystemAudioFocus(AudioFocusType::kGain))
+  AudioFocusDelegate::AudioFocusResult result =
+      RequestSystemAudioFocus(AudioFocusType::kGain);
+
+  if (result == AudioFocusDelegate::AudioFocusResult::kFailed)
     return false;
 
   one_shot_players_.insert(PlayerIdentifier(observer, player_id));
@@ -762,8 +892,8 @@
     RenderFrameHost* rfh_of_routed_service =
         routed_service_ ? routed_service_->GetRenderFrameHost() : nullptr;
     for (const auto& player : normal_players_) {
-      if (player.observer->render_frame_host() != rfh_of_routed_service)
-        player.observer->OnSuspend(player.player_id);
+      if (player.first.observer->render_frame_host() != rfh_of_routed_service)
+        player.first.observer->OnSuspend(player.first.player_id);
     }
     for (const auto& player : pepper_players_) {
       if (player.observer->render_frame_host() != rfh_of_routed_service) {
@@ -806,7 +936,7 @@
   // prefer the top-most frame.
   std::set<RenderFrameHost*> frames;
   for (const auto& player : normal_players_) {
-    RenderFrameHost* frame = player.observer->render_frame_host();
+    RenderFrameHost* frame = player.first.observer->render_frame_host();
     if (frame)
       frames.insert(frame);
   }
diff --git a/content/browser/media/session/media_session_impl.h b/content/browser/media/session/media_session_impl.h
index 42f85c7..daf67db 100644
--- a/content/browser/media/session/media_session_impl.h
+++ b/content/browser/media/session/media_session_impl.h
@@ -15,6 +15,7 @@
 #include "base/macros.h"
 #include "base/observer_list.h"
 #include "base/optional.h"
+#include "content/browser/media/session/audio_focus_delegate.h"
 #include "content/browser/media/session/audio_focus_manager.h"
 #include "content/browser/media/session/media_session_uma_helper.h"
 #include "content/common/content_export.h"
@@ -25,6 +26,7 @@
 #include "content/public/common/media_metadata.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
+#include "mojo/public/cpp/bindings/interface_ptr_set.h"
 #include "services/media_session/public/mojom/audio_focus.mojom.h"
 
 #if defined(OS_ANDROID)
@@ -37,15 +39,8 @@
 enum class MediaContentType;
 }  // namespace media
 
-namespace media_session {
-namespace mojom {
-enum class AudioFocusType;
-}  // namespace mojom
-}  // namespace media_session
-
 namespace content {
 
-class AudioFocusDelegate;
 class AudioFocusManagerTest;
 class MediaSessionImplServiceRoutingTest;
 class MediaSessionImplStateObserver;
@@ -124,43 +119,6 @@
   CONTENT_EXPORT void OnPlayerPaused(MediaSessionPlayerObserver* observer,
                                      int player_id);
 
-  // Resume the media session.
-  // |type| represents the origin of the request.
-  CONTENT_EXPORT void Resume(MediaSession::SuspendType suspend_type) override;
-
-  // Suspend the media session.
-  // |type| represents the origin of the request.
-  CONTENT_EXPORT void Suspend(MediaSession::SuspendType suspend_type) override;
-
-  // Stop the media session.
-  // |type| represents the origin of the request.
-  CONTENT_EXPORT void Stop(MediaSession::SuspendType suspend_type) override;
-
-  // Seek the media session forward.
-  CONTENT_EXPORT void SeekForward(base::TimeDelta seek_time) override;
-
-  // Seek the media session backward.
-  CONTENT_EXPORT void SeekBackward(base::TimeDelta seek_time) override;
-
-  // Returns if the session can be controlled by Resume() and Suspend() calls
-  // above.
-  CONTENT_EXPORT bool IsControllable() const override;
-
-  // Compute if the actual playback state is paused by combining the
-  // MediaSessionService declared state and guessed state (audio_focus_state_).
-  CONTENT_EXPORT bool IsActuallyPaused() const override;
-
-  // Set the volume multiplier applied during ducking.
-  CONTENT_EXPORT void SetDuckingVolumeMultiplier(double multiplier) override;
-
-  // Let the media session start ducking such that the volume multiplier is
-  // reduced.
-  CONTENT_EXPORT void StartDucking() override;
-
-  // Let the media session stop ducking such that the volume multiplier is
-  // recovered.
-  CONTENT_EXPORT void StopDucking() override;
-
   void AddObserver(MediaSessionObserver* observer) override;
   void RemoveObserver(MediaSessionObserver* observer) override;
 
@@ -198,32 +156,78 @@
   // observers if the service is currently routed.
   void OnMediaSessionActionsChanged(MediaSessionServiceImpl* service);
 
-  // Called when a MediaSessionAction is received. The action will be forwarded
-  // to blink::MediaSession corresponding to the current routed service.
-  void DidReceiveAction(blink::mojom::MediaSessionAction action) override;
-
   // Requests audio focus to the AudioFocusDelegate.
   // Returns whether the request was granted.
-  CONTENT_EXPORT bool RequestSystemAudioFocus(
+  CONTENT_EXPORT AudioFocusDelegate::AudioFocusResult RequestSystemAudioFocus(
       media_session::mojom::AudioFocusType audio_focus_type);
 
-  // Returns debugging information to be displayed on chrome://media-internals.
-  struct DebugInfo {
-    // A unique name for the MediaSession.
-    std::string name;
-
-    // The title and URL of the owning WebContents.
-    std::string owner;
-
-    // State information stored in a string e.g. Ducked.
-    std::string state;
-  };
-  const DebugInfo GetDebugInfo();
-
   // Creates a binding between |this| and |request|.
   void BindToMojoRequest(
       mojo::InterfaceRequest<media_session::mojom::MediaSession> request);
 
+  // Returns information about the MediaSession.
+  media_session::mojom::MediaSessionInfoPtr GetMediaSessionInfoSync();
+
+  // MediaSession overrides ---------------------------------------------------
+
+  // Resume the media session.
+  // |type| represents the origin of the request.
+  CONTENT_EXPORT void Resume(MediaSession::SuspendType suspend_type) override;
+
+  // Stop the media session.
+  // |type| represents the origin of the request.
+  CONTENT_EXPORT void Stop(MediaSession::SuspendType suspend_type) override;
+
+  // Seek the media session forward.
+  CONTENT_EXPORT void SeekForward(base::TimeDelta seek_time) override;
+
+  // Seek the media session backward.
+  CONTENT_EXPORT void SeekBackward(base::TimeDelta seek_time) override;
+
+  // Returns if the session can be controlled by Resume() and Suspend() calls
+  // above.
+  CONTENT_EXPORT bool IsControllable() const override;
+
+  // Compute if the actual playback state is paused by combining the
+  // MediaSessionService declared state and guessed state (audio_focus_state_).
+  CONTENT_EXPORT bool IsActuallyPaused() const override;
+
+  // Called when a MediaSessionAction is received. The action will be forwarded
+  // to blink::MediaSession corresponding to the current routed service.
+  void DidReceiveAction(blink::mojom::MediaSessionAction action) override;
+
+  // Set the volume multiplier applied during ducking.
+  CONTENT_EXPORT void SetDuckingVolumeMultiplier(double multiplier) override;
+
+  // Suspend the media session.
+  // |type| represents the origin of the request.
+  CONTENT_EXPORT void Suspend(MediaSession::SuspendType suspend_type) override;
+
+  // Let the media session start ducking such that the volume multiplier is
+  // reduced.
+  CONTENT_EXPORT void StartDucking() override;
+
+  // Let the media session stop ducking such that the volume multiplier is
+  // recovered.
+  CONTENT_EXPORT void StopDucking() override;
+
+  // Returns information about the MediaSession. The sync method is not actually
+  // slower and should be used over the async one which is available over mojo.
+  void GetMediaSessionInfo(GetMediaSessionInfoCallback callback) override;
+
+  // Returns debugging information to be displayed on chrome://media-internals.
+  void GetDebugInfo(GetDebugInfoCallback) override;
+
+  // Adds a mojo based observer to listen to events related to this session.
+  void AddObserver(
+      media_session::mojom::MediaSessionObserverPtr observer) override;
+
+  // Called by |AudioFocusDelegate| when an async audio focus request is
+  // completed.
+  CONTENT_EXPORT void FinishSystemAudioFocusRequest(
+      media_session::mojom::AudioFocusType type,
+      bool result);
+
  private:
   friend class content::WebContentsUserData<MediaSessionImpl>;
   friend class ::MediaSessionImplBrowserTest;
@@ -232,6 +236,7 @@
   friend class content::MediaSessionImplServiceRoutingTest;
   friend class content::MediaSessionImplStateObserver;
   friend class content::MediaSessionServiceImplBrowserTest;
+  friend class MediaSessionImplTest;
   friend class MediaInternalsAudioFocusTest;
 
   CONTENT_EXPORT void SetDelegateForTests(
@@ -246,6 +251,7 @@
 
     void operator=(const PlayerIdentifier&) = delete;
     bool operator==(const PlayerIdentifier& player_identifier) const;
+    bool operator<(const PlayerIdentifier&) const;
 
     // Hash operator for base::hash_map<>.
     struct Hash {
@@ -262,6 +268,10 @@
 
   void Initialize();
 
+  // Called when system audio focus has been requested and whether the request
+  // was granted.
+  void OnSystemAudioFocusRequested(bool result);
+
   CONTENT_EXPORT void OnSuspendInternal(MediaSession::SuspendType suspend_type,
                                         State new_state);
   CONTENT_EXPORT void OnResumeInternal(MediaSession::SuspendType suspend_type);
@@ -280,6 +290,12 @@
   // It sets audio_focus_state_ and notifies observers about the state change.
   void SetAudioFocusState(State audio_focus_state);
 
+  // Flushes any mojo bindings for testing.
+  CONTENT_EXPORT void FlushForTesting();
+
+  // Notifies mojo observers that the MediaSessionInfo has changed.
+  void NotifyObserversInfoChanged();
+
   // Update the volume multiplier when ducking state changes.
   void UpdateVolumeMultiplier();
 
@@ -311,7 +327,8 @@
   CONTENT_EXPORT MediaSessionServiceImpl* ComputeServiceForRouting();
 
   std::unique_ptr<AudioFocusDelegate> delegate_;
-  PlayersMap normal_players_;
+  std::map<PlayerIdentifier, media_session::mojom::AudioFocusType>
+      normal_players_;
   PlayersMap pepper_players_;
   PlayersMap one_shot_players_;
 
@@ -352,6 +369,9 @@
   // Bindings for Mojo pointers to |this| held by media route providers.
   mojo::BindingSet<media_session::mojom::MediaSession> bindings_;
 
+  mojo::InterfacePtrSet<media_session::mojom::MediaSessionObserver>
+      mojo_observers_;
+
   DISALLOW_COPY_AND_ASSIGN(MediaSessionImpl);
 };
 
diff --git a/content/browser/media/session/media_session_impl_browsertest.cc b/content/browser/media/session/media_session_impl_browsertest.cc
index f7ba08d..ca9dfdc9 100644
--- a/content/browser/media/session/media_session_impl_browsertest.cc
+++ b/content/browser/media/session/media_session_impl_browsertest.cc
@@ -51,21 +51,44 @@
 
 class MockAudioFocusDelegate : public AudioFocusDelegate {
  public:
-  MockAudioFocusDelegate() {}
+  MockAudioFocusDelegate(MediaSessionImpl* media_session, bool async_mode)
+      : media_session_(media_session), async_mode_(async_mode) {}
 
   MOCK_METHOD0(AbandonAudioFocus, void());
 
-  bool RequestAudioFocus(AudioFocusType audio_focus_type) {
-    audio_focus_type_ = audio_focus_type;
-    return true;
+  AudioFocusDelegate::AudioFocusResult RequestAudioFocus(
+      AudioFocusType audio_focus_type) {
+    if (async_mode_) {
+      requests_.push_back(audio_focus_type);
+      return AudioFocusDelegate::AudioFocusResult::kDelayed;
+    } else {
+      audio_focus_type_ = audio_focus_type;
+      return AudioFocusDelegate::AudioFocusResult::kSuccess;
+    }
   }
 
-  AudioFocusType GetCurrentFocusType() const {
-    DCHECK(audio_focus_type_.has_value());
-    return audio_focus_type_.value();
+  base::Optional<AudioFocusType> GetCurrentFocusType() const {
+    return audio_focus_type_;
   }
 
+  void ResolveRequest(bool result) {
+    if (!async_mode_)
+      return;
+
+    audio_focus_type_ = requests_.front();
+    requests_.pop_front();
+
+    media_session_->FinishSystemAudioFocusRequest(audio_focus_type_.value(),
+                                                  result);
+  }
+
+  bool HasRequests() const { return !requests_.empty(); }
+
  private:
+  MediaSessionImpl* media_session_;
+  const bool async_mode_ = false;
+
+  std::list<AudioFocusType> requests_;
   base::Optional<AudioFocusType> audio_focus_type_;
 };
 
@@ -88,7 +111,8 @@
     media_session_ = MediaSessionImpl::Get(shell()->web_contents());
     mock_media_session_observer_.reset(
         new NiceMock<content::MockMediaSessionObserver>(media_session_));
-    mock_audio_focus_delegate_ = new NiceMock<MockAudioFocusDelegate>;
+    mock_audio_focus_delegate_ = new NiceMock<MockAudioFocusDelegate>(
+        media_session_, true /* async_mode */);
     media_session_->SetDelegateForTests(
         base::WrapUnique(mock_audio_focus_delegate_));
     ASSERT_TRUE(media_session_);
@@ -106,8 +130,10 @@
 
   void StartNewPlayer(MockMediaSessionPlayerObserver* player_observer,
                       media::MediaContentType media_content_type) {
-    bool result = AddPlayer(player_observer, player_observer->StartNewPlayer(),
-                            media_content_type);
+    int player_id = player_observer->StartNewPlayer();
+
+    bool result = AddPlayer(player_observer, player_id, media_content_type);
+
     EXPECT_TRUE(result);
   }
 
@@ -133,7 +159,7 @@
 
   bool IsActive() { return media_session_->IsActive(); }
 
-  AudioFocusType GetSessionAudioFocusType() {
+  base::Optional<AudioFocusType> GetSessionAudioFocusType() {
     return mock_audio_focus_delegate_->GetCurrentFocusType();
   }
 
@@ -175,6 +201,18 @@
     mock_media_session_service_->SetPlaybackState(state);
   }
 
+  void ResolveAudioFocusSuccess() {
+    mock_audio_focus_delegate()->ResolveRequest(true /* result */);
+  }
+
+  void ResolveAudioFocusFailure() {
+    mock_audio_focus_delegate()->ResolveRequest(false /* result */);
+  }
+
+  bool HasUnresolvedAudioFocusRequest() {
+    return mock_audio_focus_delegate()->HasRequests();
+  }
+
   content::MockMediaSessionObserver* mock_media_session_observer() {
     return mock_media_session_observer_.get();
   }
@@ -191,6 +229,14 @@
     return media_session_->uma_helper_for_test();
   }
 
+  void SetAudioFocusDelegateForTests(MockAudioFocusDelegate* delegate) {
+    mock_audio_focus_delegate_ = delegate;
+    media_session_->SetDelegateForTests(
+        base::WrapUnique(mock_audio_focus_delegate_));
+  }
+
+  bool IsDucking() const { return media_session_->is_ducking_; }
+
  protected:
   MediaSessionImpl* media_session_;
   std::unique_ptr<content::MockMediaSessionObserver>
@@ -201,20 +247,37 @@
   DISALLOW_COPY_AND_ASSIGN(MediaSessionImplBrowserTest);
 };
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+class MediaSessionImplParamBrowserTest
+    : public MediaSessionImplBrowserTest,
+      public testing::WithParamInterface<bool> {
+ protected:
+  MediaSessionImplParamBrowserTest() = default;
+
+  void SetUpOnMainThread() override {
+    MediaSessionImplBrowserTest::SetUpOnMainThread();
+
+    SetAudioFocusDelegateForTests(
+        new NiceMock<MockAudioFocusDelegate>(media_session_, GetParam()));
+  }
+};
+
+INSTANTIATE_TEST_CASE_P(, MediaSessionImplParamBrowserTest, testing::Bool());
+
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        PlayersFromSameObserverDoNotStopEachOtherInSameSession) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   EXPECT_TRUE(player_observer->IsPlaying(0));
   EXPECT_TRUE(player_observer->IsPlaying(1));
   EXPECT_TRUE(player_observer->IsPlaying(2));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        PlayersFromManyObserverDoNotStopEachOtherInSameSession) {
   auto player_observer_1 = std::make_unique<MockMediaSessionPlayerObserver>();
   auto player_observer_2 = std::make_unique<MockMediaSessionPlayerObserver>();
@@ -223,19 +286,21 @@
   StartNewPlayer(player_observer_1.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer_2.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer_3.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   EXPECT_TRUE(player_observer_1->IsPlaying(0));
   EXPECT_TRUE(player_observer_2->IsPlaying(0));
   EXPECT_TRUE(player_observer_3->IsPlaying(0));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        SuspendedMediaSessionStopsPlayers) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   SystemSuspend(true);
 
@@ -244,13 +309,14 @@
   EXPECT_FALSE(player_observer->IsPlaying(2));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ResumedMediaSessionRestartsPlayers) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   SystemSuspend(true);
   SystemResume();
@@ -260,11 +326,12 @@
   EXPECT_TRUE(player_observer->IsPlaying(2));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        StartedPlayerOnSuspendedSessionPlaysAlone) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   EXPECT_TRUE(player_observer->IsPlaying(0));
 
@@ -273,6 +340,7 @@
   EXPECT_FALSE(player_observer->IsPlaying(0));
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   EXPECT_FALSE(player_observer->IsPlaying(0));
   EXPECT_TRUE(player_observer->IsPlaying(1));
@@ -284,7 +352,8 @@
   EXPECT_TRUE(player_observer->IsPlaying(2));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, InitialVolumeMultiplier) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       InitialVolumeMultiplier) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
@@ -292,9 +361,14 @@
 
   EXPECT_EQ(kDefaultVolumeMultiplier, player_observer->GetVolumeMultiplier(0));
   EXPECT_EQ(kDefaultVolumeMultiplier, player_observer->GetVolumeMultiplier(1));
+
+  ResolveAudioFocusSuccess();
+
+  EXPECT_EQ(kDefaultVolumeMultiplier, player_observer->GetVolumeMultiplier(0));
+  EXPECT_EQ(kDefaultVolumeMultiplier, player_observer->GetVolumeMultiplier(1));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        StartDuckingReducesVolumeMultiplier) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
@@ -310,7 +384,7 @@
   EXPECT_EQ(kDuckingVolumeMultiplier, player_observer->GetVolumeMultiplier(2));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        StopDuckingRecoversVolumeMultiplier) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
@@ -327,7 +401,7 @@
   EXPECT_EQ(kDefaultVolumeMultiplier, player_observer->GetVolumeMultiplier(2));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        DuckingUsesConfiguredMultiplier) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
@@ -344,14 +418,16 @@
   EXPECT_EQ(kDefaultVolumeMultiplier, player_observer->GetVolumeMultiplier(1));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, AudioFocusInitialState) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       AudioFocusInitialState) {
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        AddPlayerOnSuspendedFocusUnducks) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   UISuspend();
   EXPECT_FALSE(IsActive());
@@ -361,53 +437,65 @@
 
   EXPECT_TRUE(
       AddPlayer(player_observer.get(), 0, media::MediaContentType::Persistent));
+  ResolveAudioFocusSuccess();
   EXPECT_EQ(kDefaultVolumeMultiplier, player_observer->GetVolumeMultiplier(0));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        CanRequestFocusBeforePlayerCreation) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   media_session_->RequestSystemAudioFocus(AudioFocusType::kGain);
   EXPECT_TRUE(IsActive());
 
+  ResolveAudioFocusSuccess();
+  EXPECT_TRUE(IsActive());
+
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, StartPlayerGivesFocus) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       StartPlayerGivesFocus) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  EXPECT_TRUE(IsActive());
 
+  ResolveAudioFocusSuccess();
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        SuspendGivesAwayAudioFocus) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   SystemSuspend(true);
 
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, StopGivesAwayAudioFocus) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       StopGivesAwayAudioFocus) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   media_session_->Stop(MediaSession::SuspendType::kUI);
 
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, ResumeGivesBackAudioFocus) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       SystemResumeGivesBackAudioFocus) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   SystemSuspend(true);
   SystemResume();
@@ -415,13 +503,30 @@
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       UIResumeGivesBackAudioFocus) {
+  auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
+
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
+
+  UISuspend();
+
+  UIResume();
+  EXPECT_TRUE(IsActive());
+
+  ResolveAudioFocusSuccess();
+  EXPECT_TRUE(IsActive());
+}
+
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        RemovingLastPlayerDropsAudioFocus) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   RemovePlayer(player_observer.get(), 0);
   EXPECT_TRUE(IsActive());
@@ -431,7 +536,7 @@
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        RemovingLastPlayerFromManyObserversDropsAudioFocus) {
   auto player_observer_1 = std::make_unique<MockMediaSessionPlayerObserver>();
   auto player_observer_2 = std::make_unique<MockMediaSessionPlayerObserver>();
@@ -440,6 +545,7 @@
   StartNewPlayer(player_observer_1.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer_2.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer_3.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   RemovePlayer(player_observer_1.get(), 0);
   EXPECT_TRUE(IsActive());
@@ -449,7 +555,7 @@
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        RemovingAllPlayersFromObserversDropsAudioFocus) {
   auto player_observer_1 = std::make_unique<MockMediaSessionPlayerObserver>();
   auto player_observer_2 = std::make_unique<MockMediaSessionPlayerObserver>();
@@ -458,6 +564,7 @@
   StartNewPlayer(player_observer_1.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer_2.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer_2.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   RemovePlayers(player_observer_1.get());
   EXPECT_TRUE(IsActive());
@@ -465,29 +572,40 @@
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, ResumePlayGivesAudioFocus) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       ResumePlayGivesAudioFocus) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   RemovePlayer(player_observer.get(), 0);
   EXPECT_FALSE(IsActive());
 
   EXPECT_TRUE(
       AddPlayer(player_observer.get(), 0, media::MediaContentType::Persistent));
+  ResolveAudioFocusSuccess();
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ResumeSuspendSeekAreSentOnlyOncePerPlayers) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
+  EXPECT_EQ(0, player_observer->received_suspend_calls());
+  EXPECT_EQ(0, player_observer->received_resume_calls());
+
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
 
   EXPECT_EQ(0, player_observer->received_suspend_calls());
   EXPECT_EQ(0, player_observer->received_resume_calls());
+
+  ResolveAudioFocusSuccess();
+
+  EXPECT_EQ(0, player_observer->received_suspend_calls());
+  EXPECT_EQ(0, player_observer->received_resume_calls());
   EXPECT_EQ(0, player_observer->received_seek_forward_calls());
   EXPECT_EQ(0, player_observer->received_seek_backward_calls());
 
@@ -504,14 +622,22 @@
   EXPECT_EQ(3, player_observer->received_seek_backward_calls());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ResumeSuspendSeekAreSentOnlyOncePerPlayersAddedTwice) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
+  EXPECT_EQ(0, player_observer->received_suspend_calls());
+  EXPECT_EQ(0, player_observer->received_resume_calls());
+
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
 
+  EXPECT_EQ(0, player_observer->received_suspend_calls());
+  EXPECT_EQ(0, player_observer->received_resume_calls());
+
+  ResolveAudioFocusSuccess();
+
   // Adding the three players above again.
   EXPECT_TRUE(
       AddPlayer(player_observer.get(), 0, media::MediaContentType::Persistent));
@@ -538,33 +664,38 @@
   EXPECT_EQ(3, player_observer->received_seek_backward_calls());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        RemovingTheSamePlayerTwiceIsANoop) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   RemovePlayer(player_observer.get(), 0);
   RemovePlayer(player_observer.get(), 0);
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, AudioFocusType) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest, AudioFocusType) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   // Starting a player with a given type should set the session to that type.
   StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
+  ResolveAudioFocusSuccess();
   EXPECT_EQ(AudioFocusType::kGainTransientMayDuck, GetSessionAudioFocusType());
 
   // Adding a player of the same type should have no effect on the type.
   StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
+  EXPECT_FALSE(HasUnresolvedAudioFocusRequest());
   EXPECT_EQ(AudioFocusType::kGainTransientMayDuck, GetSessionAudioFocusType());
 
   // Adding a player of Content type should override the current type.
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   EXPECT_EQ(AudioFocusType::kGain, GetSessionAudioFocusType());
 
   // Adding a player of the Transient type should have no effect on the type.
   StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
+  EXPECT_FALSE(HasUnresolvedAudioFocusRequest());
   EXPECT_EQ(AudioFocusType::kGain, GetSessionAudioFocusType());
 
   EXPECT_TRUE(player_observer->IsPlaying(0));
@@ -591,7 +722,8 @@
   EXPECT_EQ(AudioFocusType::kGain, GetSessionAudioFocusType());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, ControlsShowForContent) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       ControlsShowForContent) {
   EXPECT_CALL(*mock_media_session_observer(),
               MediaSessionStateChanged(true, false));
 
@@ -599,12 +731,13 @@
 
   // Starting a player with a content type should show the media controls.
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   EXPECT_TRUE(IsControllable());
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsNoShowForTransient) {
   EXPECT_CALL(*mock_media_session_observer(),
               MediaSessionStateChanged(false, false));
@@ -613,12 +746,14 @@
 
   // Starting a player with a transient type should not show the media controls.
   StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
+  ResolveAudioFocusSuccess();
 
   EXPECT_FALSE(IsControllable());
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, ControlsHideWhenStopped) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       ControlsHideWhenStopped) {
   Expectation showControls = EXPECT_CALL(*mock_media_session_observer(),
                                          MediaSessionStateChanged(true, false));
   EXPECT_CALL(*mock_media_session_observer(),
@@ -628,6 +763,7 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   RemovePlayers(player_observer.get());
 
@@ -635,7 +771,7 @@
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsShownAcceptTransient) {
   EXPECT_CALL(*mock_media_session_observer(),
               MediaSessionStateChanged(true, false));
@@ -643,6 +779,7 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   // Transient player join the session without affecting the controls.
   StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
@@ -651,7 +788,7 @@
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsShownAfterContentAdded) {
   Expectation dontShowControls = EXPECT_CALL(
       *mock_media_session_observer(), MediaSessionStateChanged(false, false));
@@ -662,15 +799,17 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
+  ResolveAudioFocusSuccess();
 
   // The controls are shown when the content player is added.
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   EXPECT_TRUE(IsControllable());
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsStayIfOnlyOnePlayerHasBeenPaused) {
   EXPECT_CALL(*mock_media_session_observer(),
               MediaSessionStateChanged(true, false));
@@ -678,6 +817,8 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
+
   StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
 
   // Removing only content player doesn't hide the controls since the session
@@ -688,7 +829,7 @@
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsHideWhenTheLastPlayerIsRemoved) {
   Expectation showControls = EXPECT_CALL(*mock_media_session_observer(),
                                          MediaSessionStateChanged(true, false));
@@ -700,6 +841,7 @@
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   RemovePlayer(player_observer.get(), 0);
 
@@ -712,7 +854,7 @@
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsHideWhenAllThePlayersAreRemoved) {
   Expectation showControls = EXPECT_CALL(*mock_media_session_observer(),
                                          MediaSessionStateChanged(true, false));
@@ -724,6 +866,7 @@
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   RemovePlayers(player_observer.get());
 
@@ -731,7 +874,7 @@
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsNotHideWhenTheLastPlayerIsPaused) {
   Expectation showControls = EXPECT_CALL(*mock_media_session_observer(),
                                          MediaSessionStateChanged(true, false));
@@ -743,6 +886,7 @@
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   OnPlayerPaused(player_observer.get(), 0);
 
@@ -755,7 +899,7 @@
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        SuspendTemporaryUpdatesControls) {
   Expectation showControls = EXPECT_CALL(*mock_media_session_observer(),
                                          MediaSessionStateChanged(true, false));
@@ -766,6 +910,7 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   SystemSuspend(true);
 
@@ -773,7 +918,7 @@
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsUpdatedWhenResumed) {
   Expectation showControls = EXPECT_CALL(*mock_media_session_observer(),
                                          MediaSessionStateChanged(true, false));
@@ -787,6 +932,8 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
+
   SystemSuspend(true);
   SystemResume();
 
@@ -794,7 +941,7 @@
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsHideWhenSessionSuspendedPermanently) {
   Expectation showControls = EXPECT_CALL(*mock_media_session_observer(),
                                          MediaSessionStateChanged(true, false));
@@ -805,6 +952,7 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   SystemSuspend(false);
 
@@ -812,8 +960,8 @@
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
-                       ConstrolsHideWhenSessionStops) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       ControlsHideWhenSessionStops) {
   Expectation showControls = EXPECT_CALL(*mock_media_session_observer(),
                                          MediaSessionStateChanged(true, false));
   Expectation pauseControls = EXPECT_CALL(*mock_media_session_observer(),
@@ -826,6 +974,7 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   media_session_->Stop(MediaSession::SuspendType::kUI);
 
@@ -833,7 +982,7 @@
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsHideWhenSessionChangesFromContentToTransient) {
   Expectation showControls = EXPECT_CALL(*mock_media_session_observer(),
                                          MediaSessionStateChanged(true, false));
@@ -847,17 +996,19 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   SystemSuspend(true);
 
   // This should reset the session and change it to a transient, so
   // hide the controls.
   StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
+  ResolveAudioFocusSuccess();
 
   EXPECT_FALSE(IsControllable());
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsUpdatedWhenNewPlayerResetsSession) {
   Expectation showControls = EXPECT_CALL(*mock_media_session_observer(),
                                          MediaSessionStateChanged(true, false));
@@ -871,16 +1022,18 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   SystemSuspend(true);
 
   // This should reset the session and update the controls.
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   EXPECT_TRUE(IsControllable());
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsResumedWhenPlayerIsResumed) {
   Expectation showControls = EXPECT_CALL(*mock_media_session_observer(),
                                          MediaSessionStateChanged(true, false));
@@ -894,16 +1047,18 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   SystemSuspend(true);
 
   // This should resume the session and update the controls.
   AddPlayer(player_observer.get(), 0, media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   EXPECT_TRUE(IsControllable());
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsUpdatedDueToResumeSessionAction) {
   Expectation showControls = EXPECT_CALL(*mock_media_session_observer(),
                                          MediaSessionStateChanged(true, false));
@@ -914,13 +1069,14 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   UISuspend();
 
   EXPECT_TRUE(IsControllable());
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsUpdatedDueToSuspendSessionAction) {
   Expectation showControls = EXPECT_CALL(*mock_media_session_observer(),
                                          MediaSessionStateChanged(true, false));
@@ -934,14 +1090,19 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   UISuspend();
-  UIResume();
 
+  UIResume();
+  EXPECT_TRUE(IsControllable());
+  EXPECT_TRUE(IsActive());
+
+  ResolveAudioFocusSuccess();
   EXPECT_TRUE(IsControllable());
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsDontShowWhenOneShotIsPresent) {
   EXPECT_CALL(*mock_media_session_observer(),
               MediaSessionStateChanged(false, false));
@@ -949,6 +1110,7 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::OneShot);
+  ResolveAudioFocusSuccess();
 
   EXPECT_FALSE(IsControllable());
   EXPECT_TRUE(IsActive());
@@ -962,7 +1124,7 @@
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsHiddenAfterRemoveOneShotWithoutOtherPlayers) {
   Expectation expect_1 = EXPECT_CALL(*mock_media_session_observer(),
                                      MediaSessionStateChanged(false, false));
@@ -976,13 +1138,14 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::OneShot);
+  ResolveAudioFocusSuccess();
   RemovePlayer(player_observer.get(), 0);
 
   EXPECT_FALSE(IsControllable());
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ControlsShowAfterRemoveOneShotWithPersistentPresent) {
   Expectation uncontrollable = EXPECT_CALL(
       *mock_media_session_observer(), MediaSessionStateChanged(false, false));
@@ -996,6 +1159,7 @@
   StartNewPlayer(player_observer.get(), media::MediaContentType::OneShot);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   RemovePlayer(player_observer.get(), 0);
 
@@ -1003,13 +1167,14 @@
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        DontSuspendWhenOneShotIsPresent) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::OneShot);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   SystemSuspend(false);
 
@@ -1019,11 +1184,12 @@
   EXPECT_EQ(0, player_observer->received_suspend_calls());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        DontResumeBySystemUISuspendedSessions) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   UISuspend();
   EXPECT_TRUE(IsControllable());
@@ -1034,80 +1200,103 @@
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        AllowUIResumeForSystemSuspend) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   SystemSuspend(true);
   EXPECT_TRUE(IsControllable());
   EXPECT_FALSE(IsActive());
 
   UIResume();
+  ResolveAudioFocusSuccess();
+
   EXPECT_TRUE(IsControllable());
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, ResumeSuspendFromUI) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest, ResumeSuspendFromUI) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   UISuspend();
   EXPECT_TRUE(IsControllable());
   EXPECT_FALSE(IsActive());
 
   UIResume();
+  EXPECT_TRUE(IsActive());
+
+  ResolveAudioFocusSuccess();
   EXPECT_TRUE(IsControllable());
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, ResumeSuspendFromSystem) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       ResumeSuspendFromSystem) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   SystemSuspend(true);
   EXPECT_TRUE(IsControllable());
   EXPECT_FALSE(IsActive());
 
   SystemResume();
+  EXPECT_FALSE(HasUnresolvedAudioFocusRequest());
   EXPECT_TRUE(IsControllable());
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, OneShotTakesGainFocus) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       OneShotTakesGainFocus) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::OneShot);
+  ResolveAudioFocusSuccess();
+
   StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
+  EXPECT_FALSE(HasUnresolvedAudioFocusRequest());
+
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  EXPECT_FALSE(HasUnresolvedAudioFocusRequest());
 
   EXPECT_EQ(AudioFocusType::kGain,
             mock_audio_focus_delegate()->GetCurrentFocusType());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, RemovingOneShotDropsFocus) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       RemovingOneShotDropsFocus) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   EXPECT_CALL(*mock_audio_focus_delegate(), AbandonAudioFocus());
   StartNewPlayer(player_observer.get(), media::MediaContentType::OneShot);
+  ResolveAudioFocusSuccess();
+
   RemovePlayer(player_observer.get(), 0);
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        RemovingOneShotWhileStillHavingOtherPlayersKeepsFocus) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   EXPECT_CALL(*mock_audio_focus_delegate(), AbandonAudioFocus())
       .Times(1);  // Called in TearDown
   StartNewPlayer(player_observer.get(), media::MediaContentType::OneShot);
+  ResolveAudioFocusSuccess();
+
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  EXPECT_FALSE(HasUnresolvedAudioFocusRequest());
+
   RemovePlayer(player_observer.get(), 0);
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ActualPlaybackStateWhilePlayerPaused) {
   EnsureMediaSessionService();
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>(
@@ -1131,6 +1320,8 @@
       .InSequence(s);
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
+
   OnPlayerPaused(player_observer.get(), 0);
   SetPlaybackState(blink::mojom::MediaSessionPlaybackState::PLAYING);
   SetPlaybackState(blink::mojom::MediaSessionPlaybackState::PAUSED);
@@ -1141,7 +1332,7 @@
   ::testing::Mock::VerifyAndClear(mock_media_session_observer());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ActualPlaybackStateWhilePlayerPlaying) {
   EnsureMediaSessionService();
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>(
@@ -1161,6 +1352,8 @@
       .InSequence(s);
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
+
   SetPlaybackState(blink::mojom::MediaSessionPlaybackState::PLAYING);
   SetPlaybackState(blink::mojom::MediaSessionPlaybackState::PAUSED);
   SetPlaybackState(blink::mojom::MediaSessionPlaybackState::NONE);
@@ -1170,7 +1363,7 @@
   ::testing::Mock::VerifyAndClear(mock_media_session_observer());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        ActualPlaybackStateWhilePlayerRemoved) {
   EnsureMediaSessionService();
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>(
@@ -1185,6 +1378,7 @@
       .InSequence(s);
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   RemovePlayer(player_observer.get(), 0);
 
   SetPlaybackState(blink::mojom::MediaSessionPlaybackState::PLAYING);
@@ -1196,12 +1390,13 @@
   ::testing::Mock::VerifyAndClear(mock_media_session_observer());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        UMA_Suspended_SystemTransient) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
   base::HistogramTester tester;
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   SystemSuspend(true);
 
   std::unique_ptr<base::HistogramSamples> samples(
@@ -1212,12 +1407,13 @@
   EXPECT_EQ(0, samples->GetCount(2));  // UI
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        UMA_Suspended_SystemPermantent) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
   base::HistogramTester tester;
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   SystemSuspend(false);
 
   std::unique_ptr<base::HistogramSamples> samples(
@@ -1228,12 +1424,13 @@
   EXPECT_EQ(0, samples->GetCount(2));  // UI
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, UMA_Suspended_UI) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest, UMA_Suspended_UI) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   base::HistogramTester tester;
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   UISuspend();
 
   std::unique_ptr<base::HistogramSamples> samples(
@@ -1244,20 +1441,24 @@
   EXPECT_EQ(1, samples->GetCount(2));  // UI
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, UMA_Suspended_Multiple) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       UMA_Suspended_Multiple) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
   base::HistogramTester tester;
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   UISuspend();
   UIResume();
+  ResolveAudioFocusSuccess();
 
   SystemSuspend(true);
   SystemResume();
 
   UISuspend();
   UIResume();
+  ResolveAudioFocusSuccess();
 
   SystemSuspend(false);
 
@@ -1269,16 +1470,19 @@
   EXPECT_EQ(2, samples->GetCount(2));  // UI
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, UMA_Suspended_Crossing) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
+                       UMA_Suspended_Crossing) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
   base::HistogramTester tester;
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   UISuspend();
   SystemSuspend(true);
   SystemSuspend(false);
   UIResume();
+  ResolveAudioFocusSuccess();
 
   SystemSuspend(true);
   SystemSuspend(true);
@@ -1293,11 +1497,12 @@
   EXPECT_EQ(1, samples->GetCount(2));  // UI
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, UMA_Suspended_Stop) {
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest, UMA_Suspended_Stop) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
   base::HistogramTester tester;
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   media_session_->Stop(MediaSession::SuspendType::kUI);
 
   std::unique_ptr<base::HistogramSamples> samples(
@@ -1308,7 +1513,7 @@
   EXPECT_EQ(1, samples->GetCount(2));  // UI
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        UMA_ActiveTime_NoActivation) {
   base::HistogramTester tester;
 
@@ -1321,7 +1526,7 @@
   EXPECT_EQ(0, samples->TotalCount());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        UMA_ActiveTime_SimpleActivation) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
   base::HistogramTester tester;
@@ -1332,6 +1537,7 @@
   media_session_uma_helper->SetClockForTest(&clock);
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   clock.Advance(base::TimeDelta::FromMilliseconds(1000));
   media_session_->Stop(MediaSession::SuspendType::kUI);
@@ -1342,7 +1548,7 @@
   EXPECT_EQ(1, samples->GetCount(1000));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        UMA_ActiveTime_ActivationWithUISuspension) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
   base::HistogramTester tester;
@@ -1353,12 +1559,14 @@
   media_session_uma_helper->SetClockForTest(&clock);
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   clock.Advance(base::TimeDelta::FromMilliseconds(1000));
   UISuspend();
 
   clock.Advance(base::TimeDelta::FromMilliseconds(2000));
   UIResume();
+  ResolveAudioFocusSuccess();
 
   clock.Advance(base::TimeDelta::FromMilliseconds(1000));
   media_session_->Stop(MediaSession::SuspendType::kUI);
@@ -1369,7 +1577,7 @@
   EXPECT_EQ(1, samples->GetCount(2000));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        UMA_ActiveTime_ActivationWithSystemSuspension) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
   base::HistogramTester tester;
@@ -1380,6 +1588,7 @@
   media_session_uma_helper->SetClockForTest(&clock);
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   clock.Advance(base::TimeDelta::FromMilliseconds(1000));
   SystemSuspend(true);
@@ -1396,7 +1605,7 @@
   EXPECT_EQ(1, samples->GetCount(2000));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        UMA_ActiveTime_ActivateSuspendedButNotStopped) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
   base::HistogramTester tester;
@@ -1407,6 +1616,7 @@
   media_session_uma_helper->SetClockForTest(&clock);
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   clock.Advance(base::TimeDelta::FromMilliseconds(500));
   SystemSuspend(true);
 
@@ -1427,7 +1637,7 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        UMA_ActiveTime_ActivateSuspendStopTwice) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
   base::HistogramTester tester;
@@ -1438,11 +1648,13 @@
   media_session_uma_helper->SetClockForTest(&clock);
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   clock.Advance(base::TimeDelta::FromMilliseconds(500));
   SystemSuspend(true);
   media_session_->Stop(MediaSession::SuspendType::kUI);
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   clock.Advance(base::TimeDelta::FromMilliseconds(5000));
   SystemResume();
   media_session_->Stop(MediaSession::SuspendType::kUI);
@@ -1454,7 +1666,7 @@
   EXPECT_EQ(1, samples->GetCount(5000));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        UMA_ActiveTime_MultipleActivations) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
   base::HistogramTester tester;
@@ -1465,10 +1677,12 @@
   media_session_uma_helper->SetClockForTest(&clock);
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   clock.Advance(base::TimeDelta::FromMilliseconds(10000));
   RemovePlayer(player_observer.get(), 0);
 
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
   clock.Advance(base::TimeDelta::FromMilliseconds(1000));
   media_session_->Stop(MediaSession::SuspendType::kUI);
 
@@ -1479,7 +1693,7 @@
   EXPECT_EQ(1, samples->GetCount(10000));
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        AddingObserverNotifiesCurrentInformation_EmptyInfo) {
   media_session_->RemoveObserver(mock_media_session_observer());
   EXPECT_CALL(*mock_media_session_observer(),
@@ -1492,7 +1706,7 @@
   media_session_->AddObserver(mock_media_session_observer());
 }
 
-IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
                        AddingObserverNotifiesCurrentInformation_WithInfo) {
   // Set up the service and information.
   EnsureMediaSessionService();
@@ -1512,6 +1726,7 @@
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>(
       shell()->web_contents()->GetMainFrame());
   StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  ResolveAudioFocusSuccess();
 
   // Check if the expectations are met when the observer is newly added.
   media_session_->RemoveObserver(mock_media_session_observer());
@@ -1523,3 +1738,217 @@
               MediaSessionActionsChanged(Eq(expectedActions)));
   media_session_->AddObserver(mock_media_session_observer());
 }
+
+IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, Async_RequestFailure_Gain) {
+  auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
+
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
+
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+  EXPECT_TRUE(IsActive());
+
+  // The gain request failed so we should suspend the whole session.
+  ResolveAudioFocusFailure();
+  EXPECT_FALSE(player_observer->IsPlaying(0));
+  EXPECT_FALSE(player_observer->IsPlaying(1));
+  EXPECT_FALSE(IsActive());
+
+  ResolveAudioFocusSuccess();
+  EXPECT_FALSE(player_observer->IsPlaying(0));
+  EXPECT_FALSE(player_observer->IsPlaying(1));
+  EXPECT_FALSE(IsActive());
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+                       Async_RequestFailure_GainTransient) {
+  auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
+
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
+
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+  EXPECT_TRUE(IsActive());
+
+  ResolveAudioFocusSuccess();
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+  EXPECT_TRUE(IsActive());
+
+  // A transient audio focus failure should only affect transient players.
+  ResolveAudioFocusFailure();
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_FALSE(player_observer->IsPlaying(1));
+  EXPECT_TRUE(IsActive());
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, Async_GainThenTransient) {
+  auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
+
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
+
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+
+  ResolveAudioFocusSuccess();
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+
+  ResolveAudioFocusSuccess();
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, Async_TransientThenGain) {
+  auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
+
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+
+  ResolveAudioFocusSuccess();
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+
+  ResolveAudioFocusSuccess();
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest,
+                       Async_SuspendBeforeResolve) {
+  auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
+
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+
+  SystemSuspend(true);
+  EXPECT_FALSE(player_observer->IsPlaying(0));
+  EXPECT_FALSE(IsActive());
+
+  ResolveAudioFocusSuccess();
+  EXPECT_FALSE(player_observer->IsPlaying(0));
+  EXPECT_FALSE(IsActive());
+
+  SystemResume();
+  EXPECT_TRUE(IsActive());
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, Async_ResumeBeforeResolve) {
+  auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
+
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  EXPECT_TRUE(IsActive());
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+
+  UISuspend();
+  EXPECT_FALSE(IsActive());
+  EXPECT_FALSE(player_observer->IsPlaying(0));
+
+  UIResume();
+  EXPECT_TRUE(IsActive());
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+
+  ResolveAudioFocusSuccess();
+  EXPECT_TRUE(IsActive());
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+
+  ResolveAudioFocusFailure();
+  EXPECT_FALSE(IsActive());
+  EXPECT_FALSE(player_observer->IsPlaying(0));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, Async_RemoveBeforeResolve) {
+  {
+    auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
+
+    EXPECT_CALL(*mock_audio_focus_delegate(), AbandonAudioFocus());
+    StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+    EXPECT_TRUE(player_observer->IsPlaying(0));
+
+    RemovePlayer(player_observer.get(), 0);
+  }
+
+  ResolveAudioFocusSuccess();
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, Async_StopBeforeResolve) {
+  auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
+
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Transient);
+  ResolveAudioFocusSuccess();
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  EXPECT_TRUE(player_observer->IsPlaying(1));
+
+  media_session_->Stop(MediaSession::SuspendType::kUI);
+  ResolveAudioFocusSuccess();
+
+  EXPECT_FALSE(player_observer->IsPlaying(0));
+  EXPECT_FALSE(player_observer->IsPlaying(1));
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, Async_Unducking_Failure) {
+  auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
+
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  EXPECT_TRUE(IsActive());
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+
+  SystemStartDucking();
+  EXPECT_TRUE(IsDucking());
+
+  ResolveAudioFocusFailure();
+  EXPECT_TRUE(IsDucking());
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, Async_Unducking_Inactive) {
+  auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
+
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  EXPECT_TRUE(IsActive());
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+
+  media_session_->Stop(MediaSession::SuspendType::kUI);
+  SystemStartDucking();
+  EXPECT_TRUE(IsDucking());
+
+  ResolveAudioFocusSuccess();
+  EXPECT_TRUE(IsDucking());
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, Async_Unducking_Success) {
+  auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
+
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  EXPECT_TRUE(IsActive());
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+
+  SystemStartDucking();
+  EXPECT_TRUE(IsDucking());
+
+  ResolveAudioFocusSuccess();
+  EXPECT_FALSE(IsDucking());
+}
+
+IN_PROC_BROWSER_TEST_F(MediaSessionImplBrowserTest, Async_Unducking_Suspended) {
+  auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
+
+  StartNewPlayer(player_observer.get(), media::MediaContentType::Persistent);
+  EXPECT_TRUE(IsActive());
+  EXPECT_TRUE(player_observer->IsPlaying(0));
+
+  UISuspend();
+  SystemStartDucking();
+  EXPECT_TRUE(IsDucking());
+
+  ResolveAudioFocusSuccess();
+  EXPECT_TRUE(IsDucking());
+}
diff --git a/content/browser/media/session/media_session_impl_unittest.cc b/content/browser/media/session/media_session_impl_unittest.cc
new file mode 100644
index 0000000..176c21e9
--- /dev/null
+++ b/content/browser/media/session/media_session_impl_unittest.cc
@@ -0,0 +1,298 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/media/session/media_session_impl.h"
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "build/build_config.h"
+#include "content/browser/media/session/audio_focus_test_util.h"
+#include "content/browser/media/session/media_session_player_observer.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/test/test_web_contents.h"
+#include "media/base/media_content_type.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "services/media_session/public/cpp/switches.h"
+#include "services/media_session/public/mojom/media_session.mojom.h"
+
+namespace content {
+
+using media_session::mojom::AudioFocusType;
+using media_session::mojom::MediaSessionInfo;
+using media_session::mojom::MediaSessionInfoPtr;
+
+namespace {
+
+class MockMediaSessionPlayerObserver : public MediaSessionPlayerObserver {
+ public:
+  void OnSuspend(int player_id) override {}
+  void OnResume(int player_id) override {}
+  void OnSeekForward(int player_id, base::TimeDelta seek_time) override {}
+  void OnSeekBackward(int player_id, base::TimeDelta seek_time) override {}
+  void OnSetVolumeMultiplier(int player_id, double volume_multiplier) override {
+  }
+  RenderFrameHost* render_frame_host() const override { return nullptr; }
+};
+
+class MockMediaSessionMojoObserver
+    : public media_session::mojom::MediaSessionObserver {
+ public:
+  explicit MockMediaSessionMojoObserver(MediaSessionImpl* media_session)
+      : binding_(this) {
+    media_session::mojom::MediaSessionObserverPtr observer;
+    binding_.Bind(mojo::MakeRequest(&observer));
+    media_session->AddObserver(std::move(observer));
+  }
+
+  void MediaSessionInfoChanged(MediaSessionInfoPtr session) override {
+    session_info_ = std::move(session);
+  }
+
+  MediaSessionInfoPtr session_info_;
+
+ private:
+  mojo::Binding<media_session::mojom::MediaSessionObserver> binding_;
+};
+
+}  // anonymous namespace
+
+class MediaSessionImplTest : public testing::Test {
+ public:
+  MediaSessionImplTest() = default;
+
+  void SetUp() override {
+    base::CommandLine::ForCurrentProcess()->AppendSwitch(
+        media_session::switches::kEnableAudioFocus);
+
+    rph_factory_.reset(new MockRenderProcessHostFactory());
+    RenderProcessHostImpl::set_render_process_host_factory_for_testing(
+        rph_factory_.get());
+    browser_context_.reset(new TestBrowserContext());
+    pepper_observer_.reset(new MockMediaSessionPlayerObserver());
+  }
+
+  void TearDown() override {
+    browser_context_.reset();
+    RenderProcessHostImpl::set_render_process_host_factory_for_testing(nullptr);
+    rph_factory_.reset();
+  }
+
+  void RequestAudioFocus(MediaSessionImpl* session,
+                         AudioFocusType audio_focus_type) {
+    session->RequestSystemAudioFocus(audio_focus_type);
+  }
+
+  void AbandonAudioFocus(MediaSessionImpl* session) {
+    session->AbandonSystemAudioFocusIfNeeded();
+  }
+
+  bool GetForceDuck(MediaSessionImpl* session) {
+    return test::GetMediaSessionInfoSync(session)->force_duck;
+  }
+
+  MediaSessionInfo::SessionState GetState(MediaSessionImpl* session) {
+    return test::GetMediaSessionInfoSync(session)->state;
+  }
+
+  bool HasMojoObservers(MediaSessionImpl* session) {
+    return !session->mojo_observers_.empty();
+  }
+
+  void FlushForTesting(MediaSessionImpl* session) {
+    session->FlushForTesting();
+  }
+
+  std::unique_ptr<WebContents> CreateWebContents() {
+    return TestWebContents::Create(
+        browser_context_.get(), SiteInstance::Create(browser_context_.get()));
+  }
+
+  std::unique_ptr<MediaSessionPlayerObserver> pepper_observer_;
+
+ private:
+  TestBrowserThreadBundle test_browser_thread_bundle_;
+
+  std::unique_ptr<MockRenderProcessHostFactory> rph_factory_;
+  std::unique_ptr<TestBrowserContext> browser_context_;
+};
+
+TEST_F(MediaSessionImplTest, SessionInfoState) {
+  std::unique_ptr<WebContents> web_contents(CreateWebContents());
+  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+  EXPECT_EQ(MediaSessionInfo::SessionState::kInactive, GetState(media_session));
+
+  {
+    MockMediaSessionMojoObserver observer(media_session);
+    RequestAudioFocus(media_session, AudioFocusType::kGain);
+    FlushForTesting(media_session);
+
+    EXPECT_EQ(MediaSessionInfo::SessionState::kActive, GetState(media_session));
+    EXPECT_TRUE(observer.session_info_.Equals(
+        test::GetMediaSessionInfoSync(media_session)));
+  }
+
+  {
+    MockMediaSessionMojoObserver observer(media_session);
+    media_session->StartDucking();
+    FlushForTesting(media_session);
+
+    EXPECT_EQ(MediaSessionInfo::SessionState::kDucking,
+              GetState(media_session));
+    EXPECT_TRUE(observer.session_info_.Equals(
+        test::GetMediaSessionInfoSync(media_session)));
+  }
+
+  {
+    MockMediaSessionMojoObserver observer(media_session);
+    media_session->StopDucking();
+    FlushForTesting(media_session);
+
+    EXPECT_EQ(MediaSessionInfo::SessionState::kActive, GetState(media_session));
+    EXPECT_TRUE(observer.session_info_.Equals(
+        test::GetMediaSessionInfoSync(media_session)));
+  }
+
+  {
+    MockMediaSessionMojoObserver observer(media_session);
+    media_session->Suspend(MediaSession::SuspendType::kSystem);
+    FlushForTesting(media_session);
+
+    EXPECT_EQ(MediaSessionInfo::SessionState::kSuspended,
+              GetState(media_session));
+    EXPECT_TRUE(observer.session_info_.Equals(
+        test::GetMediaSessionInfoSync(media_session)));
+  }
+
+  {
+    MockMediaSessionMojoObserver observer(media_session);
+    media_session->Resume(MediaSession::SuspendType::kSystem);
+    FlushForTesting(media_session);
+
+    EXPECT_EQ(MediaSessionInfo::SessionState::kActive, GetState(media_session));
+    EXPECT_TRUE(observer.session_info_.Equals(
+        test::GetMediaSessionInfoSync(media_session)));
+  }
+
+  {
+    MockMediaSessionMojoObserver observer(media_session);
+    AbandonAudioFocus(media_session);
+    FlushForTesting(media_session);
+
+    EXPECT_EQ(MediaSessionInfo::SessionState::kInactive,
+              GetState(media_session));
+    EXPECT_TRUE(observer.session_info_.Equals(
+        test::GetMediaSessionInfoSync(media_session)));
+  }
+}
+
+TEST_F(MediaSessionImplTest, PepperForcesDuckAndRequestsFocus) {
+  std::unique_ptr<WebContents> web_contents(CreateWebContents());
+  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+
+  media_session->AddPlayer(pepper_observer_.get(), 0,
+                           media::MediaContentType::Pepper);
+  EXPECT_EQ(MediaSessionInfo::SessionState::kActive, GetState(media_session));
+  EXPECT_TRUE(GetForceDuck(media_session));
+
+  media_session->RemovePlayer(pepper_observer_.get(), 0);
+  EXPECT_EQ(MediaSessionInfo::SessionState::kInactive, GetState(media_session));
+  EXPECT_FALSE(GetForceDuck(media_session));
+}
+
+TEST_F(MediaSessionImplTest, RegisterMojoObserver) {
+  std::unique_ptr<WebContents> web_contents(CreateWebContents());
+  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+
+  EXPECT_FALSE(HasMojoObservers(media_session));
+
+  MockMediaSessionMojoObserver observer(media_session);
+  FlushForTesting(media_session);
+
+  EXPECT_TRUE(HasMojoObservers(media_session));
+}
+
+#if !defined(OS_ANDROID)
+
+TEST_F(MediaSessionImplTest, WebContentsDestroyed_ReleasesFocus) {
+  std::unique_ptr<WebContents> web_contents(CreateWebContents());
+  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+
+  {
+    test::TestAudioFocusObserver observer;
+    RequestAudioFocus(media_session, AudioFocusType::kGain);
+    observer.WaitForGainedEvent();
+  }
+
+  EXPECT_EQ(MediaSessionInfo::SessionState::kActive, GetState(media_session));
+
+  {
+    test::TestAudioFocusObserver observer;
+    web_contents.reset();
+    observer.WaitForLostEvent();
+  }
+}
+
+TEST_F(MediaSessionImplTest, WebContentsDestroyed_ReleasesTransients) {
+  std::unique_ptr<WebContents> web_contents(CreateWebContents());
+  MediaSessionImpl* media_session = MediaSessionImpl::Get(web_contents.get());
+
+  {
+    test::TestAudioFocusObserver observer;
+    RequestAudioFocus(media_session, AudioFocusType::kGainTransientMayDuck);
+    observer.WaitForGainedEvent();
+  }
+
+  EXPECT_EQ(MediaSessionInfo::SessionState::kActive, GetState(media_session));
+
+  {
+    test::TestAudioFocusObserver observer;
+    web_contents.reset();
+    observer.WaitForLostEvent();
+  }
+}
+
+TEST_F(MediaSessionImplTest, WebContentsDestroyed_StopsDucking) {
+  std::unique_ptr<WebContents> web_contents_1(CreateWebContents());
+  MediaSessionImpl* media_session_1 =
+      MediaSessionImpl::Get(web_contents_1.get());
+
+  std::unique_ptr<WebContents> web_contents_2(CreateWebContents());
+  MediaSessionImpl* media_session_2 =
+      MediaSessionImpl::Get(web_contents_2.get());
+
+  {
+    test::TestAudioFocusObserver observer;
+    RequestAudioFocus(media_session_1, AudioFocusType::kGain);
+    observer.WaitForGainedEvent();
+  }
+
+  EXPECT_NE(MediaSessionInfo::SessionState::kDucking,
+            GetState(media_session_1));
+
+  {
+    test::TestAudioFocusObserver observer;
+    RequestAudioFocus(media_session_2, AudioFocusType::kGainTransientMayDuck);
+    observer.WaitForGainedEvent();
+  }
+
+  EXPECT_EQ(MediaSessionInfo::SessionState::kDucking,
+            GetState(media_session_1));
+
+  {
+    test::TestAudioFocusObserver observer;
+    web_contents_2.reset();
+    observer.WaitForLostEvent();
+  }
+
+  EXPECT_NE(MediaSessionInfo::SessionState::kDucking,
+            GetState(media_session_1));
+}
+
+#endif  // !defined(OS_ANDROID)
+
+}  // namespace content
diff --git a/content/browser/process_internals/process_internals_ui.cc b/content/browser/process_internals/process_internals_ui.cc
index 8c3558c..0394d232 100644
--- a/content/browser/process_internals/process_internals_ui.cc
+++ b/content/browser/process_internals/process_internals_ui.cc
@@ -35,6 +35,7 @@
       WebUIDataSource::Create(kChromeUIProcessInternalsHost);
 
   source->AddResourcePath("process_internals.js", IDR_PROCESS_INTERNALS_JS);
+  source->AddResourcePath("process_internals.css", IDR_PROCESS_INTERNALS_CSS);
   source->AddResourcePath("process_internals.mojom.js",
                           IDR_PROCESS_INTERNALS_MOJO_JS);
   source->SetDefaultResource(IDR_PROCESS_INTERNALS_HTML);
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 0e6b97a..ac24be9a 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -2236,10 +2236,18 @@
       &RenderProcessHostImpl::CreateRendererHost, base::Unretained(this)));
 
   if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
+    // Using an opaque origin here should be safe - the URLLoaderFactory created
+    // for such origin shouldn't have any special privileges.
+    //
+    // TODO(lukasza): https://crbug.com/871827: Use the actual origin that will
+    // be used as |request_initiator|.  The origin should come from the browser
+    // process.
+    const url::Origin kSafeOrigin = url::Origin();
+
     AddUIThreadInterface(
         registry.get(),
         base::Bind(&RenderProcessHostImpl::CreateURLLoaderFactory,
-                   base::Unretained(this)));
+                   base::Unretained(this), kSafeOrigin));
   }
 
   registry->AddInterface(
@@ -2516,6 +2524,7 @@
 }
 
 void RenderProcessHostImpl::CreateURLLoaderFactory(
+    const url::Origin& origin,
     network::mojom::URLLoaderFactoryRequest request) {
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
     base::PostTaskWithTraits(
@@ -2524,15 +2533,26 @@
                        std::move(request)));
     return;
   }
-  network::mojom::URLLoaderFactoryParamsPtr params =
-      network::mojom::URLLoaderFactoryParams::New();
-  params->process_id = id_;
-  params->disable_web_security =
-      base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kDisableWebSecurity);
-  SiteIsolationPolicy::PopulateURLLoaderFactoryParamsPtrForCORB(params.get());
-  storage_partition_impl_->GetNetworkContext()->CreateURLLoaderFactory(
-      std::move(request), std::move(params));
+
+  network::mojom::NetworkContext* network_context =
+      storage_partition_impl_->GetNetworkContext();
+  network::mojom::URLLoaderFactoryPtrInfo embedder_provided_factory =
+      GetContentClient()->browser()->CreateURLLoaderFactoryForNetworkRequests(
+          this, network_context, origin);
+  if (embedder_provided_factory) {
+    mojo::FuseInterface(std::move(request),
+                        std::move(embedder_provided_factory));
+  } else {
+    network::mojom::URLLoaderFactoryParamsPtr params =
+        network::mojom::URLLoaderFactoryParams::New();
+    params->process_id = GetID();
+    params->disable_web_security =
+        base::CommandLine::ForCurrentProcess()->HasSwitch(
+            switches::kDisableWebSecurity);
+    SiteIsolationPolicy::PopulateURLLoaderFactoryParamsPtrForCORB(params.get());
+    network_context->CreateURLLoaderFactory(std::move(request),
+                                            std::move(params));
+  }
 }
 
 void RenderProcessHostImpl::SetIsNeverSuitableForReuse() {
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index bba58ee..bd316ca5 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -218,6 +218,7 @@
   resource_coordinator::ProcessResourceCoordinator*
   GetProcessResourceCoordinator() override;
   void CreateURLLoaderFactory(
+      const url::Origin& origin,
       network::mojom::URLLoaderFactoryRequest request) override;
 
   void SetIsNeverSuitableForReuse() override;
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 61220b8..b2857d65 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -269,6 +269,10 @@
 
 class UnboundWidgetInputHandler : public mojom::WidgetInputHandler {
  public:
+  void FlushForTesting(FlushForTestingCallback callback) override {
+    CHECK(false) << "Flush for testing called on an unbound interface.";
+  }
+
   void SetFocus(bool focused) override {
     DLOG(WARNING) << "Input request on unbound interface";
   }
@@ -981,6 +985,12 @@
   focused_widget->SetPageFocus(false);
 }
 
+void RenderWidgetHostImpl::FlushForTesting(FlushForTestingCallback callback) {
+  // Flush IPC messages on WidgetInputHandler to ensure that SetFocus() messages
+  // have been processed.
+  GetWidgetInputHandler()->FlushForTesting(std::move(callback));
+}
+
 void RenderWidgetHostImpl::SetPageFocus(bool focused) {
   is_focused_ = focused;
 
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index 9f16c81..c2257b5 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -187,6 +187,7 @@
   void NotifyTextDirection() override;
   void Focus() override;
   void Blur() override;
+  void FlushForTesting(FlushForTestingCallback callback) override;
   void SetActive(bool active) override;
   void ForwardMouseEvent(const blink::WebMouseEvent& mouse_event) override;
   void ForwardWheelEvent(const blink::WebMouseWheelEvent& wheel_event) override;
diff --git a/content/browser/resources/process/process_internals.css b/content/browser/resources/process/process_internals.css
new file mode 100644
index 0000000..0fbc588a
--- /dev/null
+++ b/content/browser/resources/process/process_internals.css
@@ -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. */
+
+* {
+  box-sizing: border-box;
+}
+
+html {
+  height: 100%;
+}
+
+body {
+  color: rgb(48, 57, 66);
+  display: flex;
+  flex-direction: column;
+  font-size: 13px;
+  height: 100%;
+  margin: 0;
+  overflow: auto;
+}
+
+#container {
+  display: flex;
+  height: 100%
+}
+
+#navigation {
+  flex-shrink: 0;
+  padding-top: 20px;
+  width: 200px;
+}
+
+#content {
+  flex-grow: 1;
+}
+
+#caption {
+  color: rgb(92, 97, 102);
+  font-size: 1.5rem;
+  padding-bottom: 10px;
+  padding-inline-start: 20px;
+}
+
+.tab-header {
+  -webkit-border-start: 6px solid transparent;
+  padding-left: 15px;
+}
+
+.tab-header.selected {
+  -webkit-border-start-color: rgb(78, 87, 100);
+}
+
+.tab-header > button {
+  background-color: white;
+  border: 0;
+  cursor: pointer;
+  font: inherit;
+  line-height: 17px;
+  margin: 6px 0;
+  padding: 0 2px;
+}
+
+.tab-header:not(.selected) > button {
+  color: #999;
+}
+
+#content > div {
+  min-width: 32em;
+  padding: 0 20px 65px 0;
+}
+#content > div:not(.selected) {
+  display: none;
+}
+
+.content-header {
+  background: linear-gradient(white, white 40%, rgba(255, 255, 255, 0.92));
+  border-bottom: 1px solid #eee;
+  font-size: 150%;
+  padding: 20px 0 10px 0;
+  z-index: 1;
+}
diff --git a/content/browser/resources/process/process_internals.html b/content/browser/resources/process/process_internals.html
index 9b63631e..36d2fd2 100644
--- a/content/browser/resources/process/process_internals.html
+++ b/content/browser/resources/process/process_internals.html
@@ -6,11 +6,33 @@
 <head>
   <meta charset="utf-8">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
+  <link rel="stylesheet" href="process_internals.css">
   <script src="chrome://resources/js/mojo_bindings.js"></script>
+  <script src="chrome://resources/js/util.js"></script>
   <script src="process_internals.mojom.js"></script>
   <script src='process_internals.js'></script>
   <title>Process Model Internals</title>
 </head>
-<div id="site-isolation-mode">Site Isolation mode: <span id='isolation-mode'>unknown</span></div>
-<div id="isolated-origins-container">Number of isolated origins: <span id='isolated-origins'></span></div>
+<body>
+
+<div id="container">
+  <div id="navigation">
+    <div id="caption">Process Internals</div>
+  </div>
+  <div id="content">
+    <div id="general">
+      <div class="content-header">General info</div>
+      <div id="general-info">
+        <div id="site-isolation-mode">Site Isolation mode: <span id='isolation-mode'>unknown</span></div>
+        <div id="isolated-origins-container">Number of isolated origins: <span id='isolated-origins'></span></div>
+      </div>
+    </div>
+    <div id="web-contents">
+      <div class="content-header">WebContents</div>
+      <div id="wc-list" class="list pages"></div>
+    </div>
+  </div>
+</div>
+
+</body>
 </html>
diff --git a/content/browser/resources/process/process_internals.js b/content/browser/resources/process/process_internals.js
index 1558474..d9181a2 100644
--- a/content/browser/resources/process/process_internals.js
+++ b/content/browser/resources/process/process_internals.js
@@ -2,16 +2,61 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-console.log('process internals initializing');
-
 (function() {
 'use strict';
 
 /**
- * Reference to the backend.
+ * Reference to the backend providing all the data.
  * @type {mojom.ProcessInternalsHandlerPtr}
  */
-var uiHandler = null;
+let uiHandler = null;
+
+/**
+ * @param {string} id Tab id.
+ * @return {boolean} True if successful.
+ */
+function selectTab(id) {
+  const tabContents = document.querySelectorAll('#content > div');
+  const tabHeaders = $('navigation').querySelectorAll('.tab-header');
+  let found = false;
+  for (let i = 0; i < tabContents.length; i++) {
+    const tabContent = tabContents[i];
+    const tabHeader = tabHeaders[i];
+    const isTargetTab = tabContent.id == id;
+
+    found = found || isTargetTab;
+    tabContent.classList.toggle('selected', isTargetTab);
+    tabHeader.classList.toggle('selected', isTargetTab);
+  }
+  if (!found)
+    return false;
+  window.location.hash = id;
+  return true;
+}
+
+function onHashChange() {
+  let hash = window.location.hash.slice(1).toLowerCase();
+  if (!selectTab(hash))
+    selectTab('general');
+}
+
+function setupTabs() {
+  const tabContents = document.querySelectorAll('#content > div');
+  for (let i = 0; i < tabContents.length; i++) {
+    const tabContent = tabContents[i];
+    const tabName = tabContent.querySelector('.content-header').textContent;
+
+    let tabHeader = document.createElement('div');
+    tabHeader.className = 'tab-header';
+    let button = document.createElement('button');
+    button.textContent = tabName;
+    tabHeader.appendChild(button);
+    tabHeader.addEventListener('click', selectTab.bind(null, tabContent.id));
+    $('navigation').appendChild(tabHeader);
+  }
+  onHashChange();
+}
+
 
 document.addEventListener('DOMContentLoaded', function() {
   // Setup Mojo interface to the backend.
@@ -27,6 +72,9 @@
   uiHandler.getIsolatedOriginsSize().then((response) => {
     document.getElementById('isolated-origins').innerText = response.size;
   });
+
+  // Setup the UI
+  setupTabs();
 });
 
 })();
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index df32985..0413d3f 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -239,26 +239,27 @@
   }
 
   static void CreateLoaderAndStart(
-      RenderProcessHost* process,
+      RenderFrameHost* frame,
       int route_id,
       int request_id,
       const network::ResourceRequest& resource_request) {
     network::mojom::URLLoaderPtr loader;
     network::TestURLLoaderClient client;
-    CreateLoaderAndStart(process, mojo::MakeRequest(&loader), route_id,
+    CreateLoaderAndStart(frame, mojo::MakeRequest(&loader), route_id,
                          request_id, resource_request,
                          client.CreateInterfacePtr().PassInterface());
   }
 
   static void CreateLoaderAndStart(
-      RenderProcessHost* process,
+      RenderFrameHost* frame,
       network::mojom::URLLoaderRequest request,
       int route_id,
       int request_id,
       const network::ResourceRequest& resource_request,
       network::mojom::URLLoaderClientPtrInfo client) {
     network::mojom::URLLoaderFactoryPtr factory;
-    process->CreateURLLoaderFactory(mojo::MakeRequest(&factory));
+    frame->GetProcess()->CreateURLLoaderFactory(frame->GetLastCommittedOrigin(),
+                                                mojo::MakeRequest(&factory));
     factory->CreateLoaderAndStart(
         std::move(request), route_id, request_id,
         network::mojom::kURLLoadOptionNone, resource_request,
@@ -289,12 +290,12 @@
     network::mojom::URLLoaderPtr loader1, loader2;
     network::TestURLLoaderClient client1, client2;
 
-    CreateLoaderAndStart(rfh->GetProcess(), mojo::MakeRequest(&loader1),
-                         rfh->GetRoutingID(), kRequestIdNotPreviouslyUsed,
-                         request, client1.CreateInterfacePtr().PassInterface());
-    CreateLoaderAndStart(rfh->GetProcess(), mojo::MakeRequest(&loader2),
-                         rfh->GetRoutingID(), kRequestIdNotPreviouslyUsed,
-                         request, client2.CreateInterfacePtr().PassInterface());
+    CreateLoaderAndStart(rfh, mojo::MakeRequest(&loader1), rfh->GetRoutingID(),
+                         kRequestIdNotPreviouslyUsed, request,
+                         client1.CreateInterfacePtr().PassInterface());
+    CreateLoaderAndStart(rfh, mojo::MakeRequest(&loader2), rfh->GetRoutingID(),
+                         kRequestIdNotPreviouslyUsed, request,
+                         client2.CreateInterfacePtr().PassInterface());
     EXPECT_EQ(bad_message::RDH_INVALID_REQUEST_ID, kill_waiter.Wait());
   }
 
@@ -531,7 +532,7 @@
   {
     RenderProcessHostKillWaiter kill_waiter(web_rfh->GetProcess());
 
-    CreateLoaderAndStart(web_rfh->GetProcess(), web_rfh->GetRoutingID(),
+    CreateLoaderAndStart(web_rfh, web_rfh->GetRoutingID(),
                          kRequestIdNotPreviouslyUsed, chrome_origin_msg);
     EXPECT_EQ(bad_message::RDH_ILLEGAL_ORIGIN, kill_waiter.Wait());
   }
@@ -546,7 +547,7 @@
         "Origin", "", base::Bind(&OnHttpHeaderReceived));
 
     RenderProcessHostKillWaiter kill_waiter(web_rfh->GetProcess());
-    CreateLoaderAndStart(web_rfh->GetProcess(), web_rfh->GetRoutingID(),
+    CreateLoaderAndStart(web_rfh, web_rfh->GetRoutingID(),
                          kRequestIdNotPreviouslyUsed,
                          embedder_isolated_origin_msg);
     EXPECT_EQ(bad_message::RDH_ILLEGAL_ORIGIN, kill_waiter.Wait());
@@ -556,7 +557,7 @@
   NavigateToURL(shell(), web_url);
   {
     RenderProcessHostKillWaiter kill_waiter(web_rfh->GetProcess());
-    CreateLoaderAndStart(web_rfh->GetProcess(), web_rfh->GetRoutingID(),
+    CreateLoaderAndStart(web_rfh, web_rfh->GetRoutingID(),
                          kRequestIdNotPreviouslyUsed, invalid_origin_msg);
     EXPECT_EQ(bad_message::RDH_ILLEGAL_ORIGIN, kill_waiter.Wait());
   }
@@ -565,7 +566,7 @@
   NavigateToURL(shell(), web_url);
   {
     RenderProcessHostKillWaiter kill_waiter(web_rfh->GetProcess());
-    CreateLoaderAndStart(web_rfh->GetProcess(), web_rfh->GetRoutingID(),
+    CreateLoaderAndStart(web_rfh, web_rfh->GetRoutingID(),
                          kRequestIdNotPreviouslyUsed,
                          invalid_scheme_origin_msg);
     EXPECT_EQ(bad_message::RDH_ILLEGAL_ORIGIN, kill_waiter.Wait());
diff --git a/content/browser/service_worker/embedded_worker_instance.cc b/content/browser/service_worker/embedded_worker_instance.cc
index 55a03e89..e8f2b46 100644
--- a/content/browser/service_worker/embedded_worker_instance.cc
+++ b/content/browser/service_worker/embedded_worker_instance.cc
@@ -83,10 +83,11 @@
 
 std::unique_ptr<URLLoaderFactoryBundleInfo> CreateFactoryBundle(
     RenderProcessHost* rph,
+    const url::Origin& origin,
     bool use_non_network_factories) {
   auto factory_bundle = std::make_unique<URLLoaderFactoryBundleInfo>();
   network::mojom::URLLoaderFactoryPtrInfo default_factory_info;
-  rph->CreateURLLoaderFactory(mojo::MakeRequest(&default_factory_info));
+  rph->CreateURLLoaderFactory(origin, mojo::MakeRequest(&default_factory_info));
   factory_bundle->default_factory_info() = std::move(default_factory_info);
 
   if (use_non_network_factories) {
@@ -217,10 +218,11 @@
     // importScripts a non-http(s) URL.
     bool use_non_network_factories = !params->script_url.SchemeIsHTTPOrHTTPS();
 
+    url::Origin origin = url::Origin::Create(params->script_url);
     factory_bundle_for_browser =
-        CreateFactoryBundle(rph, use_non_network_factories);
+        CreateFactoryBundle(rph, origin, use_non_network_factories);
     factory_bundle_for_renderer =
-        CreateFactoryBundle(rph, use_non_network_factories);
+        CreateFactoryBundle(rph, origin, use_non_network_factories);
   }
 
   // Register to DevTools and update params accordingly.
diff --git a/content/browser/shared_worker/shared_worker_service_impl.cc b/content/browser/shared_worker/shared_worker_service_impl.cc
index 1033953..00b3d6b 100644
--- a/content/browser/shared_worker/shared_worker_service_impl.cc
+++ b/content/browser/shared_worker/shared_worker_service_impl.cc
@@ -105,9 +105,16 @@
   // NetworkService is off. If NetworkService is on the default factory is
   // set in CreateScriptLoaderOnIO().
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
+    // Using an opaque origin here should be safe - the URLLoaderFactory created
+    // for such origin shouldn't have any special privileges.  Additionally, the
+    // origin should not be inspected at all in the legacy, non-NetworkService
+    // path.
+    const url::Origin kSafeOrigin = url::Origin();
+
     network::mojom::URLLoaderFactoryPtr default_factory;
     RenderProcessHost::FromID(process_id)
-        ->CreateURLLoaderFactory(mojo::MakeRequest(&default_factory));
+        ->CreateURLLoaderFactory(kSafeOrigin,
+                                 mojo::MakeRequest(&default_factory));
     factory_bundle->default_factory_info() = default_factory.PassInterface();
   }
 
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 67e8094..cabe460 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -1350,12 +1350,6 @@
           switches::kDisableWebSecurity);
   if (g_url_loader_factory_callback_for_test.Get().is_null()) {
     auto request = mojo::MakeRequest(&url_loader_factory_for_browser_process_);
-
-    if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
-      GetContentClient()->browser()->WillCreateURLLoaderFactory(
-          browser_context(), nullptr, false /* is_navigation */, url::Origin(),
-          &request, nullptr /* bypass_redirect_checks */);
-    }
     GetNetworkContext()->CreateURLLoaderFactory(std::move(request),
                                                 std::move(params));
     is_test_url_loader_factory_for_browser_process_ = false;
diff --git a/content/common/input/input_handler.mojom b/content/common/input/input_handler.mojom
index 235ea92..df7e9e12 100644
--- a/content/common/input/input_handler.mojom
+++ b/content/common/input/input_handler.mojom
@@ -198,6 +198,10 @@
 // an input interface for an associated Widget object. See FrameInputHandler
 // for an interface at the frame level.
 interface WidgetInputHandler {
+  // A simple mechanism by which the caller can ensure that all previous
+  // messages have been flushed.
+  FlushForTesting() => ();
+
   // Tells widget focus has been changed.
   SetFocus(bool focused);
 
diff --git a/content/common/url_loader_factory_bundle.cc b/content/common/url_loader_factory_bundle.cc
index 03d1494..da7a5bb 100644
--- a/content/common/url_loader_factory_bundle.cc
+++ b/content/common/url_loader_factory_bundle.cc
@@ -7,18 +7,37 @@
 #include <utility>
 
 #include "base/logging.h"
+#include "services/network/public/cpp/resource_request.h"
 #include "url/gurl.h"
 
 namespace content {
 
+namespace {
+
+template <typename TKey>
+void BindPtrInfoMapToPtrMap(
+    std::map<TKey, network::mojom::URLLoaderFactoryPtr>* target,
+    std::map<TKey, network::mojom::URLLoaderFactoryPtrInfo> input) {
+  for (auto& it : input) {
+    const TKey& key = it.first;
+    network::mojom::URLLoaderFactoryPtrInfo& factory_info = it.second;
+    (*target)[key].Bind(std::move(factory_info));
+  }
+}
+
+}  // namespace
+
 URLLoaderFactoryBundleInfo::URLLoaderFactoryBundleInfo() = default;
 
 URLLoaderFactoryBundleInfo::URLLoaderFactoryBundleInfo(
     network::mojom::URLLoaderFactoryPtrInfo default_factory_info,
     SchemeMap scheme_specific_factory_infos,
+    OriginMap initiator_specific_factory_infos,
     bool bypass_redirect_checks)
     : default_factory_info_(std::move(default_factory_info)),
       scheme_specific_factory_infos_(std::move(scheme_specific_factory_infos)),
+      initiator_specific_factory_infos_(
+          std::move(initiator_specific_factory_infos)),
       bypass_redirect_checks_(bypass_redirect_checks) {}
 
 URLLoaderFactoryBundleInfo::~URLLoaderFactoryBundleInfo() = default;
@@ -29,6 +48,8 @@
   other->default_factory_info_ = std::move(default_factory_info_);
   other->scheme_specific_factory_infos_ =
       std::move(scheme_specific_factory_infos_);
+  other->initiator_specific_factory_infos_ =
+      std::move(initiator_specific_factory_infos_);
   other->bypass_redirect_checks_ = bypass_redirect_checks_;
 
   return base::MakeRefCounted<URLLoaderFactoryBundle>(std::move(other));
@@ -50,12 +71,19 @@
   default_factory_ = std::move(factory);
 }
 
-network::mojom::URLLoaderFactory* URLLoaderFactoryBundle::GetFactoryForURL(
-    const GURL& url) {
-  auto it = scheme_specific_factories_.find(url.scheme());
+network::mojom::URLLoaderFactory* URLLoaderFactoryBundle::GetFactory(
+    const network::ResourceRequest& request) {
+  auto it = scheme_specific_factories_.find(request.url.scheme());
   if (it != scheme_specific_factories_.end())
     return it->second.get();
 
+  if (request.request_initiator.has_value()) {
+    auto it2 =
+        initiator_specific_factories_.find(request.request_initiator.value());
+    if (it2 != initiator_specific_factories_.end())
+      return it2->second.get();
+  }
+
   return default_factory_.get();
 }
 
@@ -67,8 +95,7 @@
     const network::ResourceRequest& request,
     network::mojom::URLLoaderClientPtr client,
     const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
-  network::mojom::URLLoaderFactory* factory_ptr = GetFactoryForURL(request.url);
-
+  network::mojom::URLLoaderFactory* factory_ptr = GetFactory(request);
   factory_ptr->CreateLoaderAndStart(std::move(loader), routing_id, request_id,
                                     options, request, std::move(client),
                                     traffic_annotation);
@@ -85,16 +112,10 @@
   if (default_factory_)
     default_factory_->Clone(mojo::MakeRequest(&default_factory_info));
 
-  URLLoaderFactoryBundleInfo::SchemeMap scheme_specific_factory_infos;
-  for (auto& factory : scheme_specific_factories_) {
-    network::mojom::URLLoaderFactoryPtrInfo factory_info;
-    factory.second->Clone(mojo::MakeRequest(&factory_info));
-    scheme_specific_factory_infos.emplace(factory.first,
-                                          std::move(factory_info));
-  }
-
   return std::make_unique<URLLoaderFactoryBundleInfo>(
-      std::move(default_factory_info), std::move(scheme_specific_factory_infos),
+      std::move(default_factory_info),
+      ClonePtrMapToPtrInfoMap(scheme_specific_factories_),
+      ClonePtrMapToPtrInfoMap(initiator_specific_factories_),
       bypass_redirect_checks_);
 }
 
@@ -106,9 +127,10 @@
     std::unique_ptr<URLLoaderFactoryBundleInfo> info) {
   if (info->default_factory_info())
     default_factory_.Bind(std::move(info->default_factory_info()));
-  for (auto& factory_info : info->scheme_specific_factory_infos())
-    scheme_specific_factories_[factory_info.first].Bind(
-        std::move(factory_info.second));
+  BindPtrInfoMapToPtrMap(&scheme_specific_factories_,
+                         std::move(info->scheme_specific_factory_infos()));
+  BindPtrInfoMapToPtrMap(&initiator_specific_factories_,
+                         std::move(info->initiator_specific_factory_infos()));
   bypass_redirect_checks_ = info->bypass_redirect_checks();
 }
 
diff --git a/content/common/url_loader_factory_bundle.h b/content/common/url_loader_factory_bundle.h
index 56048a2c..33622f2 100644
--- a/content/common/url_loader_factory_bundle.h
+++ b/content/common/url_loader_factory_bundle.h
@@ -8,13 +8,17 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <utility>
 
 #include "base/macros.h"
 #include "content/common/content_export.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "url/origin.h"
 
-class GURL;
+namespace network {
+struct ResourceRequest;
+};
 
 namespace content {
 
@@ -29,10 +33,17 @@
   using SchemeMap =
       std::map<std::string, network::mojom::URLLoaderFactoryPtrInfo>;
 
+  // Map from origin of request initiator to URLLoaderFactoryPtrInfo for
+  // handling this initiator's requests (e.g. for relaxing CORB for requests
+  // initiated from content scripts).
+  using OriginMap =
+      std::map<url::Origin, network::mojom::URLLoaderFactoryPtrInfo>;
+
   URLLoaderFactoryBundleInfo();
   URLLoaderFactoryBundleInfo(
       network::mojom::URLLoaderFactoryPtrInfo default_factory_info,
       SchemeMap scheme_specific_factory_infos,
+      OriginMap initiator_specific_factory_infos,
       bool bypass_redirect_checks);
   ~URLLoaderFactoryBundleInfo() override;
 
@@ -43,6 +54,9 @@
   SchemeMap& scheme_specific_factory_infos() {
     return scheme_specific_factory_infos_;
   }
+  OriginMap& initiator_specific_factory_infos() {
+    return initiator_specific_factory_infos_;
+  }
 
   bool bypass_redirect_checks() const { return bypass_redirect_checks_; }
   void set_bypass_redirect_checks(bool bypass_redirect_checks) {
@@ -55,6 +69,7 @@
 
   network::mojom::URLLoaderFactoryPtrInfo default_factory_info_;
   SchemeMap scheme_specific_factory_infos_;
+  OriginMap initiator_specific_factory_infos_;
   bool bypass_redirect_checks_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(URLLoaderFactoryBundleInfo);
@@ -94,10 +109,24 @@
  protected:
   ~URLLoaderFactoryBundle() override;
 
-  // Returns a factory which can be used to acquire a loader for |url|. If no
-  // registered factory matches |url|'s scheme, the default factory is used. It
-  // is undefined behavior to call this when no default factory is set.
-  virtual network::mojom::URLLoaderFactory* GetFactoryForURL(const GURL& url);
+  // Returns a factory which can be used to acquire a loader for |request|.
+  virtual network::mojom::URLLoaderFactory* GetFactory(
+      const network::ResourceRequest& request);
+
+  template <typename TKey>
+  static std::map<TKey, network::mojom::URLLoaderFactoryPtrInfo>
+  ClonePtrMapToPtrInfoMap(
+      const std::map<TKey, network::mojom::URLLoaderFactoryPtr>& input) {
+    std::map<TKey, network::mojom::URLLoaderFactoryPtrInfo> output;
+    for (const auto& it : input) {
+      const TKey& key = it.first;
+      const network::mojom::URLLoaderFactoryPtr& factory = it.second;
+      network::mojom::URLLoaderFactoryPtrInfo factory_info;
+      factory->Clone(mojo::MakeRequest(&factory_info));
+      output.emplace(key, std::move(factory_info));
+    }
+    return output;
+  }
 
   network::mojom::URLLoaderFactoryPtr default_factory_;
 
@@ -108,6 +137,11 @@
   using SchemeMap = std::map<std::string, network::mojom::URLLoaderFactoryPtr>;
   SchemeMap scheme_specific_factories_;
 
+  // Map from origin of request initiator to URLLoaderFactoryPtr for handling
+  // this initiator's requests. See also URLLoaderFactoryBundleInfo::OriginMap.
+  using OriginMap = std::map<url::Origin, network::mojom::URLLoaderFactoryPtr>;
+  OriginMap initiator_specific_factories_;
+
   bool bypass_redirect_checks_ = false;
 };
 
diff --git a/content/common/url_loader_factory_bundle.mojom b/content/common/url_loader_factory_bundle.mojom
index 0b708f8..eb99321 100644
--- a/content/common/url_loader_factory_bundle.mojom
+++ b/content/common/url_loader_factory_bundle.mojom
@@ -5,6 +5,7 @@
 module content.mojom;
 
 import "services/network/public/mojom/url_loader_factory.mojom";
+import "url/mojom/origin.mojom";
 
 // Serializes a collection of URLLoaderFactory interfaces.
 struct URLLoaderFactoryBundle {
@@ -17,6 +18,10 @@
   // A mapping from URL scheme to factory interface.
   map<string, network.mojom.URLLoaderFactory> scheme_specific_factories;
 
+  // A mapping from request-initiator-origin to factory interface.
+  map<url.mojom.Origin, network.mojom.URLLoaderFactory>
+      initiator_specific_factories;
+
   // Whether redirect checks should be bypassed, since they are happening in the
   // browser.
   bool bypass_redirect_checks = false;
diff --git a/content/common/url_loader_factory_bundle_struct_traits.cc b/content/common/url_loader_factory_bundle_struct_traits.cc
index e6c9402b..f0236343 100644
--- a/content/common/url_loader_factory_bundle_struct_traits.cc
+++ b/content/common/url_loader_factory_bundle_struct_traits.cc
@@ -7,6 +7,8 @@
 #include <memory>
 #include <utility>
 
+#include "url/mojom/origin_mojom_traits.h"
+
 namespace mojo {
 
 using Traits =
@@ -26,6 +28,12 @@
 }
 
 // static
+content::URLLoaderFactoryBundleInfo::OriginMap
+Traits::initiator_specific_factories(BundleInfoType& bundle) {
+  return std::move(bundle->initiator_specific_factory_infos());
+}
+
+// static
 bool Traits::bypass_redirect_checks(BundleInfoType& bundle) {
   return bundle->bypass_redirect_checks();
 }
@@ -40,6 +48,9 @@
   if (!data.ReadSchemeSpecificFactories(
           &(*out_bundle)->scheme_specific_factory_infos()))
     return false;
+  if (!data.ReadInitiatorSpecificFactories(
+          &(*out_bundle)->initiator_specific_factory_infos()))
+    return false;
 
   (*out_bundle)->set_bypass_redirect_checks(data.bypass_redirect_checks());
 
diff --git a/content/common/url_loader_factory_bundle_struct_traits.h b/content/common/url_loader_factory_bundle_struct_traits.h
index d6d6c65..84f9bb62 100644
--- a/content/common/url_loader_factory_bundle_struct_traits.h
+++ b/content/common/url_loader_factory_bundle_struct_traits.h
@@ -28,6 +28,9 @@
   static content::URLLoaderFactoryBundleInfo::SchemeMap
   scheme_specific_factories(BundleInfoType& bundle);
 
+  static content::URLLoaderFactoryBundleInfo::OriginMap
+  initiator_specific_factories(BundleInfoType& bundle);
+
   static bool bypass_redirect_checks(BundleInfoType& bundle);
 
   static bool Read(content::mojom::URLLoaderFactoryBundleDataView data,
diff --git a/content/content_resources.grd b/content/content_resources.grd
index b178e346..e085e5f 100644
--- a/content/content_resources.grd
+++ b/content/content_resources.grd
@@ -38,6 +38,7 @@
       <include name="IDR_NETWORK_ERROR_LISTING_CSS" file="browser/resources/net/network_errors_listing.css" flattenhtml="true" type="BINDATA" />
       <include name="IDR_PROCESS_INTERNALS_HTML" file="browser/resources/process/process_internals.html" flattenhtml="true" allowexternalscript="true" compress="gzip" type="BINDATA" />
       <include name="IDR_PROCESS_INTERNALS_MOJO_JS" file="${root_gen_dir}/content/browser/process_internals/process_internals.mojom.js" use_base_dir="false" type="BINDATA" compress="gzip" />
+      <include name="IDR_PROCESS_INTERNALS_CSS" file="browser/resources/process/process_internals.css" flattenhtml="true" compress="gzip" type="BINDATA" />
       <include name="IDR_PROCESS_INTERNALS_JS" file="browser/resources/process/process_internals.js" flattenhtml="true" compress="gzip" type="BINDATA" />
       <include name="IDR_SERVICE_WORKER_INTERNALS_HTML" file="browser/resources/service_worker/serviceworker_internals.html" flattenhtml="true" allowexternalscript="true" compress="gzip" type="BINDATA" />
       <include name="IDR_SERVICE_WORKER_INTERNALS_JS" file="browser/resources/service_worker/serviceworker_internals.js" flattenhtml="true" compress="gzip" type="BINDATA" />
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/JavaScriptCallback.java b/content/public/android/java/src/org/chromium/content_public/browser/JavaScriptCallback.java
index 43bc0391..eaaefe3 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/JavaScriptCallback.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/JavaScriptCallback.java
@@ -8,7 +8,7 @@
 public interface JavaScriptCallback {
     /**
      * Called from native in response to evaluateJavaScript().
-     * @param jsonResult json result curresponds to JS execution
+     * @param jsonResult json result corresponding to JS execution
      */
     void handleJavaScriptResult(String jsonResult);
 }
diff --git a/content/public/browser/browser_task_traits.h b/content/public/browser/browser_task_traits.h
index c370cec..e5a537cc 100644
--- a/content/public/browser/browser_task_traits.h
+++ b/content/public/browser/browser_task_traits.h
@@ -41,13 +41,20 @@
 // Posting to a BrowserThread must only be done after it was initialized (ref.
 // BrowserMainLoop::CreateThreads() phase).
 class CONTENT_EXPORT BrowserTaskTraitsExtension {
+  using BrowserThreadIDFilter =
+      base::trait_helpers::RequiredEnumTraitFilter<BrowserThread::ID>;
+  using NonNestableFilter =
+      base::trait_helpers::BooleanTraitFilter<NonNestable>;
+
  public:
   static constexpr uint8_t kExtensionId =
       base::TaskTraitsExtensionStorage::kFirstEmbedderExtensionId;
 
-  struct ValidTrait {
-    ValidTrait(BrowserThread::ID) {}
-    ValidTrait(NonNestable) {}
+  struct ValidTrait : public base::TaskTraits::ValidTrait {
+    using base::TaskTraits::ValidTrait::ValidTrait;
+
+    ValidTrait(BrowserThread::ID);
+    ValidTrait(NonNestable);
   };
 
   template <
@@ -55,11 +62,10 @@
       class CheckArgumentsAreValid = std::enable_if_t<
           base::trait_helpers::AreValidTraits<ValidTrait, ArgTypes...>::value>>
   constexpr BrowserTaskTraitsExtension(ArgTypes... args)
-      : browser_thread_(base::trait_helpers::GetValueFromArgList(
-            base::trait_helpers::RequiredEnumArgGetter<BrowserThread::ID>(),
-            args...)),
-        nestable_(!base::trait_helpers::GetValueFromArgList(
-            base::trait_helpers::BooleanArgGetter<NonNestable>(),
+      : browser_thread_(
+            base::trait_helpers::GetTraitFromArgList<BrowserThreadIDFilter>(
+                args...)),
+        nestable_(!base::trait_helpers::GetTraitFromArgList<NonNestableFilter>(
             args...)) {}
 
   constexpr base::TaskTraitsExtensionStorage Serialize() const {
diff --git a/content/public/browser/browser_task_traits_unittest.nc b/content/public/browser/browser_task_traits_unittest.nc
index 3fccbebb..e74f178 100644
--- a/content/public/browser/browser_task_traits_unittest.nc
+++ b/content/public/browser/browser_task_traits_unittest.nc
@@ -10,7 +10,7 @@
 
 namespace content {
 
-#if defined(NCTEST_BROWSER_TASK_TRAITS_NO_THREAD)  // [r"no member named 'GetDefaultValue' in 'base::trait_helpers::RequiredEnumArgGetter<content::BrowserThread::ID>'"]
+#if defined(NCTEST_BROWSER_TASK_TRAITS_NO_THREAD)  // [r"TaskTraits contains a Trait that must be explicity initialized in its constructor."]
 constexpr base::TaskTraits traits = {NonNestable()};
 #elif defined(NCTEST_BROWSER_TASK_TRAITS_MULTIPLE_THREADS)  // [r"Multiple arguments of the same type were provided to the constructor of TaskTraits."]
 constexpr base::TaskTraits traits = {BrowserThread::UI,
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index de9a22f..d220899 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -126,6 +126,14 @@
     int render_process_id,
     ResourceType resource_type) {}
 
+network::mojom::URLLoaderFactoryPtrInfo
+ContentBrowserClient::CreateURLLoaderFactoryForNetworkRequests(
+    RenderProcessHost* process,
+    network::mojom::NetworkContext* network_context,
+    const url::Origin& request_initiator) {
+  return network::mojom::URLLoaderFactoryPtrInfo();
+}
+
 void ContentBrowserClient::GetAdditionalViewSourceSchemes(
     std::vector<std::string>* additional_schemes) {
   GetAdditionalWebUISchemes(additional_schemes);
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 236fbd71..058f398e 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -323,6 +323,28 @@
       int render_process_id,
       ResourceType resource_type);
 
+  // Called to create a URLLoaderFactory for network requests in the following
+  // cases:
+  // - The default factory to be used by a frame.  In this case
+  //   |request_initiator| is the origin being committed in the frame (or the
+  //   last origin committed in the frame).
+  // - The initiator-specific factory to be used by a frame.  This happens for
+  //   origins covered via
+  //   RenderFrameHost::MarkInitiatorAsRequiringSeparateURLLoaderFactory.
+  //
+  // This method allows the //content embedder to provide a URLLoaderFactory
+  // with |request_initiator|-specific properties (e.g. with relaxed
+  // Cross-Origin Read Blocking enforcement as needed by some extensions).
+  //
+  // If the embedder doesn't want to override the URLLoaderFactory for the given
+  // |request_initiator|, then it should return an invalid
+  // mojo::InterfacePtrInfo.
+  virtual network::mojom::URLLoaderFactoryPtrInfo
+  CreateURLLoaderFactoryForNetworkRequests(
+      RenderProcessHost* process,
+      network::mojom::NetworkContext* network_context,
+      const url::Origin& request_initiator);
+
   // Returns a list additional WebUI schemes, if any.  These additional schemes
   // act as aliases to the chrome: scheme.  The additional schemes may or may
   // not serve specific WebUI pages depending on the particular URLDataSource
@@ -1114,9 +1136,7 @@
       NonNetworkURLLoaderFactoryMap* factories);
 
   // Allows the embedder to intercept URLLoaderFactory interfaces used for
-  // navigation or being brokered on behalf of a renderer fetching subresources,
-  // or for non-navigation requests initiated by the browser on behalf of a
-  // BrowserContext.
+  // navigation or being brokered on behalf of a renderer fetching subresources.
   //
   // |is_navigation| is true when it's a request used for navigation.
   //
@@ -1126,9 +1146,7 @@
   // it's a request for a renderer fetching subresources. It's not set when
   // creating a factory for navigation requests, because navigation requests are
   // made on behalf of the browser, rather than on behalf of any particular
-  // origin. It's not set in the case of browser-initiated, non-navigation
-  // requests, because in that case the factory is cached and it can be used for
-  // multiple URLs.
+  // origin.
   //
   // |*factory_request| is always valid upon entry and MUST be valid upon
   // return. The embedder may swap out the value of |*factory_request| for its
@@ -1141,10 +1159,6 @@
   //
   // Always called on the UI thread and only when the Network Service is
   // enabled.
-  //
-  // Note that |frame| may be null if this is a browser-initiated,
-  // non-navigation request, e.g. a request made via
-  // |StoragePartition::GetURLLoaderFactoryForBrowserProcess()|.
   virtual bool WillCreateURLLoaderFactory(
       BrowserContext* browser_context,
       RenderFrameHost* frame,
diff --git a/content/public/browser/media_session.h b/content/public/browser/media_session.h
index b819f86..b886735a 100644
--- a/content/public/browser/media_session.h
+++ b/content/public/browser/media_session.h
@@ -8,7 +8,7 @@
 #include "base/macros.h"
 #include "base/time/time.h"
 #include "content/common/content_export.h"
-#include "services/media_session/public/mojom/audio_focus.mojom.h"
+#include "services/media_session/public/mojom/media_session.mojom.h"
 
 namespace blink {
 namespace mojom {
@@ -28,15 +28,6 @@
 // and allows clients to resume/suspend/stop the managed players.
 class MediaSession : public media_session::mojom::MediaSession {
  public:
-  enum class SuspendType {
-    // Suspended by the system because a transient sound needs to be played.
-    kSystem,
-    // Suspended by the UI.
-    kUI,
-    // Suspended by the page via script or user interaction.
-    kContent,
-  };
-
   // Returns the MediaSession associated to this WebContents. Creates one if
   // none is currently available.
   CONTENT_EXPORT static MediaSession* Get(WebContents* contents);
@@ -47,10 +38,6 @@
   // |type| represents the origin of the request.
   virtual void Resume(SuspendType suspend_type) = 0;
 
-  // Suspend the media session.
-  // |type| represents the origin of the request.
-  virtual void Suspend(SuspendType suspend_type) = 0;
-
   // Stop the media session.
   // |type| represents the origin of the request.
   virtual void Stop(SuspendType suspend_type) = 0;
@@ -74,13 +61,29 @@
   // Set the volume multiplier applied during ducking.
   virtual void SetDuckingVolumeMultiplier(double multiplier) = 0;
 
+  // media_session.mojom.MediaSession overrides -------------------------------
+
+  // Suspend the media session.
+  // |type| represents the origin of the request.
+  void Suspend(SuspendType suspend_type) override = 0;
+
   // Let the media session start ducking such that the volume multiplier is
   // reduced.
-  virtual void StartDucking() = 0;
+  void StartDucking() override = 0;
 
   // Let the media session stop ducking such that the volume multiplier is
   // recovered.
-  virtual void StopDucking() = 0;
+  void StopDucking() override = 0;
+
+  // Returns information about the MediaSession.
+  void GetMediaSessionInfo(GetMediaSessionInfoCallback callback) override = 0;
+
+  // Returns debug information about the MediaSession.
+  void GetDebugInfo(GetDebugInfoCallback callback) override = 0;
+
+  // Adds an observer to listen to events related to this MediaSession.
+  void AddObserver(
+      media_session::mojom::MediaSessionObserverPtr observer) override = 0;
 
  protected:
   MediaSession() = default;
diff --git a/content/public/browser/render_frame_host.h b/content/public/browser/render_frame_host.h
index 76279d8..ce6a283 100644
--- a/content/public/browser/render_frame_host.h
+++ b/content/public/browser/render_frame_host.h
@@ -321,6 +321,15 @@
   virtual bool CreateNetworkServiceDefaultFactory(
       network::mojom::URLLoaderFactoryRequest default_factory_request) = 0;
 
+  // Requests that future URLLoaderFactoryBundle(s) sent to the renderer should
+  // use a separate URLLoaderFactory for requests initiated by any of the
+  // origins listed in |request_initiators|.  The URLLoaderFactory(s) for each
+  // origin will be created via
+  // ContentBrowserClient::CreateURLLoaderFactoryForNetworkRequests method.
+  virtual void MarkInitiatorsAsRequiringSeparateURLLoaderFactory(
+      std::vector<url::Origin> request_initiators,
+      bool push_to_renderer_now) = 0;
+
  private:
   // This interface should only be implemented inside content.
   friend class RenderFrameHostImpl;
diff --git a/content/public/browser/render_process_host.h b/content/public/browser/render_process_host.h
index ef2a042..12d380a 100644
--- a/content/public/browser/render_process_host.h
+++ b/content/public/browser/render_process_host.h
@@ -396,7 +396,9 @@
   virtual resource_coordinator::ProcessResourceCoordinator*
   GetProcessResourceCoordinator() = 0;
 
-  // Create an URLLoaderFactory for this process.
+  // Create an URLLoaderFactory that can be used by |origin| being hosted in
+  // |this| process.
+  //
   // When NetworkService is enabled, |request| will be bound with a new
   // URLLoaderFactory created from the storage partition's Network Context. Note
   // that the URLLoaderFactory returned by this method does NOT support
@@ -404,6 +406,7 @@
   // When NetworkService is not enabled, |request| will be bound with a
   // URLLoaderFactory which routes requests to ResourceDispatcherHost.
   virtual void CreateURLLoaderFactory(
+      const url::Origin& origin,
       network::mojom::URLLoaderFactoryRequest request) = 0;
 
   // Whether this process is locked out from ever being reused for sites other
diff --git a/content/public/browser/render_widget_host.h b/content/public/browser/render_widget_host.h
index 514b39f..7ccb8771 100644
--- a/content/public/browser/render_widget_host.h
+++ b/content/public/browser/render_widget_host.h
@@ -165,6 +165,10 @@
   virtual void Focus() = 0;
   virtual void Blur() = 0;
 
+  // Tests may need to flush IPCs to ensure deterministic behavior.
+  using FlushForTestingCallback = base::OnceClosure;
+  virtual void FlushForTesting(FlushForTestingCallback callback) = 0;
+
   // Sets whether the renderer should show controls in an active state.  On all
   // platforms except mac, that's the same as focused. On mac, the frontmost
   // window will show active controls even if the focus is not in the web
diff --git a/content/public/browser/site_isolation_policy.cc b/content/public/browser/site_isolation_policy.cc
index 834a5cc..5d1b90a0 100644
--- a/content/public/browser/site_isolation_policy.cc
+++ b/content/public/browser/site_isolation_policy.cc
@@ -59,13 +59,6 @@
   params->is_corb_enabled = true;
   params->corb_detachable_resource_type = RESOURCE_TYPE_PREFETCH;
   params->corb_excluded_resource_type = RESOURCE_TYPE_PLUGIN_RESOURCE;
-
-  const char* initiator_scheme_exception =
-      GetContentClient()
-          ->browser()
-          ->GetInitiatorSchemeBypassingDocumentBlocking();
-  if (initiator_scheme_exception)
-    params->corb_excluded_initiator_scheme = initiator_scheme_exception;
 }
 
 // static
diff --git a/content/public/test/mock_render_process_host.cc b/content/public/test/mock_render_process_host.cc
index 3bc31a64..86f3de9 100644
--- a/content/public/test/mock_render_process_host.cc
+++ b/content/public/test/mock_render_process_host.cc
@@ -399,6 +399,7 @@
 }
 
 void MockRenderProcessHost::CreateURLLoaderFactory(
+    const url::Origin& origin,
     network::mojom::URLLoaderFactoryRequest request) {
   url_loader_factory_->Clone(std::move(request));
 }
diff --git a/content/public/test/mock_render_process_host.h b/content/public/test/mock_render_process_host.h
index ca37e77e..92596ac 100644
--- a/content/public/test/mock_render_process_host.h
+++ b/content/public/test/mock_render_process_host.h
@@ -136,6 +136,7 @@
   resource_coordinator::ProcessResourceCoordinator*
   GetProcessResourceCoordinator() override;
   void CreateURLLoaderFactory(
+      const url::Origin& origin,
       network::mojom::URLLoaderFactoryRequest request) override;
 
   void SetIsNeverSuitableForReuse() override;
diff --git a/content/renderer/input/widget_input_handler_impl.cc b/content/renderer/input/widget_input_handler_impl.cc
index f50fe69d..ee9924f 100644
--- a/content/renderer/input/widget_input_handler_impl.cc
+++ b/content/renderer/input/widget_input_handler_impl.cc
@@ -67,6 +67,18 @@
       base::BindOnce(&WidgetInputHandlerImpl::Release, base::Unretained(this)));
 }
 
+void WidgetInputHandlerImpl::FlushForTesting(FlushForTestingCallback callback) {
+  auto run_on_this_thread = base::BindOnce(
+      [](scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+         FlushForTestingCallback callback) {
+        task_runner->PostTask(FROM_HERE, std::move(callback));
+      },
+      base::ThreadTaskRunnerHandle::Get(), std::move(callback));
+
+  // Hop to main thread to ensure everything there is flushed.
+  RunOnMainThread(std::move(run_on_this_thread));
+}
+
 void WidgetInputHandlerImpl::SetFocus(bool focused) {
   RunOnMainThread(
       base::BindOnce(&RenderWidget::OnSetFocus, render_widget_, focused));
diff --git a/content/renderer/input/widget_input_handler_impl.h b/content/renderer/input/widget_input_handler_impl.h
index 689f973a..704742d 100644
--- a/content/renderer/input/widget_input_handler_impl.h
+++ b/content/renderer/input/widget_input_handler_impl.h
@@ -32,6 +32,7 @@
       mojom::WidgetInputHandlerAssociatedRequest interface_request);
   void SetBinding(mojom::WidgetInputHandlerRequest interface_request);
 
+  void FlushForTesting(FlushForTestingCallback callback) override;
   void SetFocus(bool focused) override;
   void MouseCaptureLost() override;
   void SetEditCommandsForNextKeyEvent(
diff --git a/content/renderer/loader/child_url_loader_factory_bundle.cc b/content/renderer/loader/child_url_loader_factory_bundle.cc
index d000662..c7f2695 100644
--- a/content/renderer/loader/child_url_loader_factory_bundle.cc
+++ b/content/renderer/loader/child_url_loader_factory_bundle.cc
@@ -94,6 +94,19 @@
   network::mojom::URLLoaderClientPtr client_sink_;
 };
 
+template <typename TKey>
+static std::map<TKey, network::mojom::URLLoaderFactoryPtrInfo>
+PassInterfacePtrMapToPtrInfoMap(
+    std::map<TKey, network::mojom::URLLoaderFactoryPtr> input) {
+  std::map<TKey, network::mojom::URLLoaderFactoryPtrInfo> output;
+  for (auto& it : input) {
+    const TKey& key = it.first;
+    network::mojom::URLLoaderFactoryPtr& factory = it.second;
+    output.emplace(key, factory.PassInterface());
+  }
+  return output;
+}
+
 }  // namespace
 
 ChildURLLoaderFactoryBundleInfo::ChildURLLoaderFactoryBundleInfo() = default;
@@ -103,15 +116,18 @@
     : URLLoaderFactoryBundleInfo(
           std::move(base_info->default_factory_info()),
           std::move(base_info->scheme_specific_factory_infos()),
+          std::move(base_info->initiator_specific_factory_infos()),
           base_info->bypass_redirect_checks()) {}
 
 ChildURLLoaderFactoryBundleInfo::ChildURLLoaderFactoryBundleInfo(
     network::mojom::URLLoaderFactoryPtrInfo default_factory_info,
     SchemeMap scheme_specific_factory_infos,
+    OriginMap initiator_specific_factory_infos,
     PossiblyAssociatedURLLoaderFactoryPtrInfo direct_network_factory_info,
     bool bypass_redirect_checks)
     : URLLoaderFactoryBundleInfo(std::move(default_factory_info),
                                  std::move(scheme_specific_factory_infos),
+                                 std::move(initiator_specific_factory_infos),
                                  bypass_redirect_checks),
       direct_network_factory_info_(std::move(direct_network_factory_info)) {}
 
@@ -123,6 +139,8 @@
   other->default_factory_info_ = std::move(default_factory_info_);
   other->scheme_specific_factory_infos_ =
       std::move(scheme_specific_factory_infos_);
+  other->initiator_specific_factory_infos_ =
+      std::move(initiator_specific_factory_infos_);
   other->direct_network_factory_info_ = std::move(direct_network_factory_info_);
   other->bypass_redirect_checks_ = bypass_redirect_checks_;
 
@@ -145,10 +163,10 @@
 
 ChildURLLoaderFactoryBundle::~ChildURLLoaderFactoryBundle() = default;
 
-network::mojom::URLLoaderFactory* ChildURLLoaderFactoryBundle::GetFactoryForURL(
-    const GURL& url) {
+network::mojom::URLLoaderFactory* ChildURLLoaderFactoryBundle::GetFactory(
+    const network::ResourceRequest& request) {
   network::mojom::URLLoaderFactory* base_result =
-      URLLoaderFactoryBundle::GetFactoryForURL(url);
+      URLLoaderFactoryBundle::GetFactory(request);
   if (base_result)
     return base_result;
 
@@ -238,14 +256,6 @@
   if (include_default && default_factory_)
     default_factory_->Clone(mojo::MakeRequest(&default_factory_info));
 
-  URLLoaderFactoryBundleInfo::SchemeMap scheme_specific_factory_infos;
-  for (auto& factory : scheme_specific_factories_) {
-    network::mojom::URLLoaderFactoryPtrInfo factory_info;
-    factory.second->Clone(mojo::MakeRequest(&factory_info));
-    scheme_specific_factory_infos.emplace(factory.first,
-                                          std::move(factory_info));
-  }
-
   network::mojom::URLLoaderFactoryPtrInfo direct_network_factory_info;
   if (direct_network_factory_) {
     direct_network_factory_->Clone(
@@ -256,7 +266,9 @@
   // therefore |subresource_overrides| are not shared with the clones.
 
   return std::make_unique<ChildURLLoaderFactoryBundleInfo>(
-      std::move(default_factory_info), std::move(scheme_specific_factory_infos),
+      std::move(default_factory_info),
+      ClonePtrMapToPtrInfoMap(scheme_specific_factories_),
+      ClonePtrMapToPtrInfoMap(initiator_specific_factories_),
       std::move(direct_network_factory_info), bypass_redirect_checks_);
 }
 
@@ -268,12 +280,6 @@
   if (default_factory_)
     default_factory_info = default_factory_.PassInterface();
 
-  URLLoaderFactoryBundleInfo::SchemeMap scheme_specific_factory_infos;
-  for (auto& factory : scheme_specific_factories_) {
-    scheme_specific_factory_infos.emplace(factory.first,
-                                          factory.second.PassInterface());
-  }
-
   PossiblyAssociatedInterfacePtrInfo<network::mojom::URLLoaderFactory>
       direct_network_factory_info;
   if (direct_network_factory_) {
@@ -281,7 +287,9 @@
   }
 
   return std::make_unique<ChildURLLoaderFactoryBundleInfo>(
-      std::move(default_factory_info), std::move(scheme_specific_factory_infos),
+      std::move(default_factory_info),
+      PassInterfacePtrMapToPtrInfoMap(std::move(scheme_specific_factories_)),
+      PassInterfacePtrMapToPtrInfoMap(std::move(initiator_specific_factories_)),
       std::move(direct_network_factory_info), bypass_redirect_checks_);
 }
 
diff --git a/content/renderer/loader/child_url_loader_factory_bundle.h b/content/renderer/loader/child_url_loader_factory_bundle.h
index 05c4fbda..333518c 100644
--- a/content/renderer/loader/child_url_loader_factory_bundle.h
+++ b/content/renderer/loader/child_url_loader_factory_bundle.h
@@ -33,6 +33,7 @@
   ChildURLLoaderFactoryBundleInfo(
       network::mojom::URLLoaderFactoryPtrInfo default_factory_info,
       SchemeMap scheme_specific_factory_infos,
+      OriginMap initiator_specific_factory_infos,
       PossiblyAssociatedURLLoaderFactoryPtrInfo direct_network_factory_info,
       bool bypass_redirect_checks);
   ~ChildURLLoaderFactoryBundleInfo() override;
@@ -101,7 +102,8 @@
   ~ChildURLLoaderFactoryBundle() override;
 
   // URLLoaderFactoryBundle overrides.
-  network::mojom::URLLoaderFactory* GetFactoryForURL(const GURL& url) override;
+  network::mojom::URLLoaderFactory* GetFactory(
+      const network::ResourceRequest& request) override;
 
  private:
   void InitDirectNetworkFactoryIfNecessary();
diff --git a/content/renderer/loader/tracked_child_url_loader_factory_bundle.cc b/content/renderer/loader/tracked_child_url_loader_factory_bundle.cc
index edbd4d48..d51228c 100644
--- a/content/renderer/loader/tracked_child_url_loader_factory_bundle.cc
+++ b/content/renderer/loader/tracked_child_url_loader_factory_bundle.cc
@@ -16,13 +16,16 @@
 TrackedChildURLLoaderFactoryBundleInfo::TrackedChildURLLoaderFactoryBundleInfo(
     network::mojom::URLLoaderFactoryPtrInfo default_factory_info,
     SchemeMap scheme_specific_factory_infos,
+    OriginMap initiator_specific_factory_infos,
     PossiblyAssociatedURLLoaderFactoryPtrInfo direct_network_factory_info,
     std::unique_ptr<HostPtrAndTaskRunner> main_thread_host_bundle,
     bool bypass_redirect_checks)
-    : ChildURLLoaderFactoryBundleInfo(std::move(default_factory_info),
-                                      std::move(scheme_specific_factory_infos),
-                                      std::move(direct_network_factory_info),
-                                      bypass_redirect_checks),
+    : ChildURLLoaderFactoryBundleInfo(
+          std::move(default_factory_info),
+          std::move(scheme_specific_factory_infos),
+          std::move(initiator_specific_factory_infos),
+          std::move(direct_network_factory_info),
+          bypass_redirect_checks),
       main_thread_host_bundle_(std::move(main_thread_host_bundle)) {}
 
 TrackedChildURLLoaderFactoryBundleInfo::
@@ -34,6 +37,8 @@
   other->default_factory_info_ = std::move(default_factory_info_);
   other->scheme_specific_factory_infos_ =
       std::move(scheme_specific_factory_infos_);
+  other->initiator_specific_factory_infos_ =
+      std::move(initiator_specific_factory_infos_);
   other->direct_network_factory_info_ = std::move(direct_network_factory_info_);
   other->main_thread_host_bundle_ = std::move(main_thread_host_bundle_);
   other->bypass_redirect_checks_ = bypass_redirect_checks_;
@@ -69,6 +74,7 @@
   return std::make_unique<TrackedChildURLLoaderFactoryBundleInfo>(
       std::move(info->default_factory_info()),
       std::move(info->scheme_specific_factory_infos()),
+      std::move(info->initiator_specific_factory_infos()),
       std::move(info->direct_network_factory_info()),
       std::move(main_thread_host_bundle_clone), info->bypass_redirect_checks());
 }
@@ -132,6 +138,7 @@
   return std::make_unique<TrackedChildURLLoaderFactoryBundleInfo>(
       std::move(info->default_factory_info()),
       std::move(info->scheme_specific_factory_infos()),
+      std::move(info->initiator_specific_factory_infos()),
       std::move(info->direct_network_factory_info()),
       std::move(main_thread_host_bundle_clone), info->bypass_redirect_checks());
 }
@@ -149,6 +156,7 @@
   return std::make_unique<TrackedChildURLLoaderFactoryBundleInfo>(
       std::move(info->default_factory_info()),
       std::move(info->scheme_specific_factory_infos()),
+      std::move(info->initiator_specific_factory_infos()),
       std::move(info->direct_network_factory_info()),
       std::move(main_thread_host_bundle_clone), info->bypass_redirect_checks());
 }
diff --git a/content/renderer/loader/tracked_child_url_loader_factory_bundle.h b/content/renderer/loader/tracked_child_url_loader_factory_bundle.h
index b52d322..7348be1 100644
--- a/content/renderer/loader/tracked_child_url_loader_factory_bundle.h
+++ b/content/renderer/loader/tracked_child_url_loader_factory_bundle.h
@@ -30,6 +30,7 @@
   TrackedChildURLLoaderFactoryBundleInfo(
       network::mojom::URLLoaderFactoryPtrInfo default_factory_info,
       SchemeMap scheme_specific_factory_infos,
+      OriginMap initiator_specific_factory_infos,
       PossiblyAssociatedURLLoaderFactoryPtrInfo direct_network_factory_info,
       std::unique_ptr<HostPtrAndTaskRunner> main_thread_host_bundle,
       bool bypass_redirect_checks);
diff --git a/content/renderer/media/webrtc/media_stream_remote_video_source.cc b/content/renderer/media/webrtc/media_stream_remote_video_source.cc
index a3869e8..a87a956 100644
--- a/content/renderer/media/webrtc/media_stream_remote_video_source.cc
+++ b/content/renderer/media/webrtc/media_stream_remote_video_source.cc
@@ -20,6 +20,7 @@
 #include "media/base/video_util.h"
 #include "third_party/webrtc/api/video/i420_buffer.h"
 #include "third_party/webrtc/api/video/video_sink_interface.h"
+#include "third_party/webrtc/rtc_base/timeutils.h"  // for TimeMicros
 
 namespace content {
 
diff --git a/content/shell/browser/layout_test/blink_test_controller.cc b/content/shell/browser/layout_test/blink_test_controller.cc
index f35a2bec..add1727 100644
--- a/content/shell/browser/layout_test/blink_test_controller.cc
+++ b/content/shell/browser/layout_test/blink_test_controller.cc
@@ -408,6 +408,14 @@
     if (is_devtools_js_test) {
       LoadDevToolsJSTest();
     } else {
+      // Flush IPC messages on the widget.
+      base::RunLoop run_loop;
+      main_window_->web_contents()
+          ->GetRenderViewHost()
+          ->GetWidget()
+          ->FlushForTesting(run_loop.QuitClosure());
+      run_loop.Run();
+
       // Loading the URL will immediately start the layout test. Manually call
       // LoadURLWithParams on the WebContents to avoid extraneous calls from
       // content::Shell such as SetFocus(), which could race with the layout
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 8a6d59cb..313dbfb 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -47,6 +47,8 @@
     "../browser/background_fetch/background_fetch_test_data_manager.h",
     "../browser/background_fetch/mock_background_fetch_delegate.cc",
     "../browser/background_fetch/mock_background_fetch_delegate.h",
+    "../browser/media/session/audio_focus_test_util.cc",
+    "../browser/media/session/audio_focus_test_util.h",
     "../browser/media/session/mock_media_session_observer.cc",
     "../browser/media/session/mock_media_session_observer.h",
     "../browser/service_worker/embedded_worker_test_helper.cc",
@@ -1460,6 +1462,7 @@
     "../browser/media/session/media_session_controllers_manager_unittest.cc",
     "../browser/media/session/media_session_impl_service_routing_unittest.cc",
     "../browser/media/session/media_session_impl_uma_unittest.cc",
+    "../browser/media/session/media_session_impl_unittest.cc",
     "../browser/media/session/media_session_uma_helper_unittest.cc",
     "../browser/memory/memory_coordinator_impl_unittest.cc",
     "../browser/memory/memory_monitor_android_unittest.cc",
diff --git a/content/test/mock_widget_input_handler.cc b/content/test/mock_widget_input_handler.cc
index 09fc3b1f9..71f96b1 100644
--- a/content/test/mock_widget_input_handler.cc
+++ b/content/test/mock_widget_input_handler.cc
@@ -26,6 +26,10 @@
 
 MockWidgetInputHandler::~MockWidgetInputHandler() {}
 
+void MockWidgetInputHandler::FlushForTesting(FlushForTestingCallback callback) {
+  std::move(callback).Run();
+}
+
 void MockWidgetInputHandler::SetFocus(bool focused) {
   dispatched_messages_.emplace_back(
       std::make_unique<DispatchedFocusMessage>(focused));
diff --git a/content/test/mock_widget_input_handler.h b/content/test/mock_widget_input_handler.h
index 4e87ef92..f886125 100644
--- a/content/test/mock_widget_input_handler.h
+++ b/content/test/mock_widget_input_handler.h
@@ -192,6 +192,7 @@
   };
 
   // mojom::WidgetInputHandler override.
+  void FlushForTesting(FlushForTestingCallback callback) override;
   void SetFocus(bool focused) override;
   void MouseCaptureLost() override;
   void SetEditCommandsForNextKeyEvent(
diff --git a/docs/security/faq.md b/docs/security/faq.md
index 89c21628..218d66dd 100644
--- a/docs/security/faq.md
+++ b/docs/security/faq.md
@@ -157,8 +157,14 @@
 as that forces us to embargo the information in the bug.
 
 Note that the XSSAuditor is not able to defend against persistent XSS or
-DOM-based XSS. There will also be a number of infrequently occurring reflected
-XSS corner cases that it will never be able to cover. Among these are:
+DOM-based XSS. Nor is it able to defend against injections deep inside
+existing JavaScript blocks, [for
+example](https://bugs.chromium.org/p/chromium/issues/detail?id=135029), since
+the XSSAuditor is part of the HTML parser, not the JavaScript parser.
+
+There will also be a number of infrequently occurring reflected XSS corner
+case in an HTML context that it will never be able to cover. Among
+these are:
 *    Multiple unsanitized variables injected into the page.
 *    Unexpected server side transformation or decoding of the payload.
 
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn
index c548631..6c1177c 100644
--- a/extensions/browser/BUILD.gn
+++ b/extensions/browser/BUILD.gn
@@ -337,6 +337,8 @@
     "uninstall_ping_sender.h",
     "uninstall_reason.h",
     "update_observer.h",
+    "url_loader_factory_manager.cc",
+    "url_loader_factory_manager.h",
     "url_request_util.cc",
     "url_request_util.h",
     "user_script_loader.cc",
diff --git a/extensions/browser/api/BUILD.gn b/extensions/browser/api/BUILD.gn
index b4fb58b..1adcdad 100644
--- a/extensions/browser/api/BUILD.gn
+++ b/extensions/browser/api/BUILD.gn
@@ -145,6 +145,7 @@
       "//chromeos",
       "//chromeos:media_perception_proto",
       "//chromeos/services/media_perception/public/mojom",
+      "//chromeos/services/media_perception/public/mojom:mojom_js_data_deps",
     ]
   }
 }
diff --git a/extensions/browser/api/media_perception_private/media_perception_api_delegate.h b/extensions/browser/api/media_perception_private/media_perception_api_delegate.h
index 6692365e..56e789f 100644
--- a/extensions/browser/api/media_perception_private/media_perception_api_delegate.h
+++ b/extensions/browser/api/media_perception_private/media_perception_api_delegate.h
@@ -5,12 +5,21 @@
 #ifndef EXTENSIONS_BROWSER_API_MEDIA_PERCEPTION_PRIVATE_MEDIA_PERCEPTION_API_DELEGATE_H_
 #define EXTENSIONS_BROWSER_API_MEDIA_PERCEPTION_PRIVATE_MEDIA_PERCEPTION_API_DELEGATE_H_
 
+#include <memory>
+
 #include "base/callback.h"
 #include "base/files/file_path.h"
+#include "chromeos/services/media_perception/public/mojom/media_perception_service.mojom.h"
 #include "extensions/common/api/media_perception_private.h"
 #include "services/video_capture/public/mojom/device_factory.mojom.h"
 #include "services/video_capture/public/mojom/device_factory_provider.mojom.h"
 
+namespace content {
+
+class RenderFrameHost;
+
+}  //  namespace content
+
 namespace extensions {
 
 class MediaPerceptionAPIDelegate {
@@ -20,6 +29,9 @@
   using LoadCrOSComponentCallback =
       base::OnceCallback<void(bool success, const base::FilePath& mount_point)>;
 
+  using MediaPerceptionRequestHandler = base::RepeatingCallback<void(
+      chromeos::media_perception::mojom::MediaPerceptionRequest request)>;
+
   virtual ~MediaPerceptionAPIDelegate() {}
 
   // Provides an interface through which a media analytics Chrome OS component
@@ -34,6 +46,17 @@
   // |provider| is owned by the caller.
   virtual void BindDeviceFactoryProviderToVideoCaptureService(
       video_capture::mojom::DeviceFactoryProviderPtr* provider) = 0;
+
+  // Provides an interface to set a handler for an incoming
+  // MediaPerceptionRequest.
+  virtual void SetMediaPerceptionRequestHandler(
+      MediaPerceptionRequestHandler handler) = 0;
+
+  // Receives an incoming media perception request and forwards it to the
+  // request handler if set.
+  virtual void ForwardMediaPerceptionRequest(
+      chromeos::media_perception::mojom::MediaPerceptionRequest request,
+      content::RenderFrameHost* render_frame_host) = 0;
 };
 
 }  // namespace extensions
diff --git a/extensions/browser/api/media_perception_private/media_perception_api_manager.cc b/extensions/browser/api/media_perception_private/media_perception_api_manager.cc
index 62230df..fd9ca85a 100644
--- a/extensions/browser/api/media_perception_private/media_perception_api_manager.cc
+++ b/extensions/browser/api/media_perception_private/media_perception_api_manager.cc
@@ -18,41 +18,16 @@
 #include "extensions/browser/api/media_perception_private/media_perception_api_delegate.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_function.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
 #include "mojo/public/cpp/platform/platform_channel.h"
 #include "mojo/public/cpp/system/invitation.h"
+#include "services/video_capture/public/mojom/device_factory_provider.mojom.h"
 
 namespace extensions {
 
 namespace {
 
-class ConnectorImpl : public chromeos::media_perception::mojom::Connector {
- public:
-  // delegate is owned by the ExtensionsAPIClient.
-  explicit ConnectorImpl(MediaPerceptionAPIDelegate* delegate)
-      : delegate_(delegate) {}
-  ~ConnectorImpl() override = default;
-
-  // media_perception::mojom::Connector override.
-  void ConnectToVideoCaptureService(
-      video_capture::mojom::DeviceFactoryRequest request) override {
-    DCHECK(delegate_) << "Delegate not set.";
-    delegate_->BindDeviceFactoryProviderToVideoCaptureService(
-        &device_factory_provider_);
-    device_factory_provider_->ConnectToDeviceFactory(std::move(request));
-  }
-
- private:
-  // Provides access to methods for talking to core Chrome code.
-  MediaPerceptionAPIDelegate* delegate_;
-
-  // Bound to the VideoCaptureService to establish the connection to the
-  // media analytics process.
-  video_capture::mojom::DeviceFactoryProviderPtr device_factory_provider_;
-
-  DISALLOW_COPY_AND_ASSIGN(ConnectorImpl);
-};
-
 extensions::api::media_perception_private::State GetStateForServiceError(
     const extensions::api::media_perception_private::ServiceError
         service_error) {
@@ -102,6 +77,46 @@
 
 }  // namespace
 
+class MediaPerceptionAPIManager::MediaPerceptionControllerClient
+    : public chromeos::media_perception::mojom::
+          MediaPerceptionControllerClient {
+ public:
+  // delegate is owned by the ExtensionsAPIClient.
+  MediaPerceptionControllerClient(
+      MediaPerceptionAPIDelegate* delegate,
+      chromeos::media_perception::mojom::MediaPerceptionControllerClientRequest
+          request)
+      : delegate_(delegate), binding_(this, std::move(request)) {
+    DCHECK(delegate_) << "Delegate not set.";
+  }
+
+  ~MediaPerceptionControllerClient() override = default;
+
+  // media_perception::mojom::MediaPerceptionControllerClient:
+  void ConnectToVideoCaptureService(
+      video_capture::mojom::DeviceFactoryRequest request) override {
+    DCHECK(delegate_) << "Delegate not set.";
+    delegate_->BindDeviceFactoryProviderToVideoCaptureService(
+        &device_factory_provider_);
+    device_factory_provider_->ConnectToDeviceFactory(std::move(request));
+  }
+
+ private:
+  // Provides access to methods for talking to core Chrome code.
+  MediaPerceptionAPIDelegate* delegate_;
+
+  // Binding of the MediaPerceptionControllerClient to the message pipe.
+  mojo::Binding<
+      chromeos::media_perception::mojom::MediaPerceptionControllerClient>
+      binding_;
+
+  // Bound to the VideoCaptureService to establish the connection to the
+  // media analytics process.
+  video_capture::mojom::DeviceFactoryProviderPtr device_factory_provider_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaPerceptionControllerClient);
+};
+
 // static
 MediaPerceptionAPIManager* MediaPerceptionAPIManager::Get(
     content::BrowserContext* context) {
@@ -135,6 +150,12 @@
   upstart_client->StopMediaAnalytics();
 }
 
+void MediaPerceptionAPIManager::ActivateMediaPerception(
+    chromeos::media_perception::mojom::MediaPerceptionRequest request) {
+  if (media_perception_controller_.is_bound())
+    media_perception_controller_->ActivateMediaPerception(std::move(request));
+}
+
 void MediaPerceptionAPIManager::SetMountPointNonEmptyForTesting() {
   mount_point_ = "non-empty-string";
 }
@@ -390,7 +411,7 @@
   MediaPerceptionAPIDelegate* delegate =
       ExtensionsAPIClient::Get()->GetMediaPerceptionAPIDelegate();
   if (!delegate) {
-    LOG(WARNING) << "Could not get MediaPerceptionAPIDelegate.";
+    DLOG(WARNING) << "Could not get MediaPerceptionAPIDelegate.";
     std::move(callback).Run(GetProcessStateForServiceError(
         extensions::api::media_perception_private::
             SERVICE_ERROR_MOJO_CONNECTION_FAILURE));
@@ -405,10 +426,10 @@
                                  base::kNullProcessHandle,
                                  channel.TakeLocalEndpoint());
 
-  auto connector = std::make_unique<ConnectorImpl>(delegate);
-  mojo::MakeStrongBinding(std::move(connector),
-                          chromeos::media_perception::mojom::ConnectorRequest(
-                              std::move(server_pipe)));
+  media_perception_service_ =
+      chromeos::media_perception::mojom::MediaPerceptionServicePtr(
+          chromeos::media_perception::mojom::MediaPerceptionServicePtrInfo(
+              std::move(server_pipe), 0));
 
   base::ScopedFD fd =
       channel.TakeRemoteEndpoint().TakePlatformHandle().TakeFD();
@@ -435,6 +456,46 @@
   extensions::api::media_perception_private::ProcessState state_started;
   state_started.status =
       extensions::api::media_perception_private::PROCESS_STATUS_STARTED;
+
+  // Check if the extensions api client is available in this context. Code path
+  // used for testing.
+  if (!ExtensionsAPIClient::Get()) {
+    DLOG(ERROR) << "Could not get ExtensionsAPIClient.";
+    std::move(callback).Run(std::move(state_started));
+    return;
+  }
+
+  MediaPerceptionAPIDelegate* delegate =
+      ExtensionsAPIClient::Get()->GetMediaPerceptionAPIDelegate();
+  if (!delegate) {
+    DLOG(WARNING) << "Could not get MediaPerceptionAPIDelegate.";
+    std::move(callback).Run(GetProcessStateForServiceError(
+        extensions::api::media_perception_private::
+            SERVICE_ERROR_MOJO_CONNECTION_FAILURE));
+    return;
+  }
+
+  if (!media_perception_service_.is_bound()) {
+    DLOG(WARNING) << "MediaPerceptionService interface not bound.";
+    std::move(callback).Run(GetProcessStateForServiceError(
+        extensions::api::media_perception_private::
+            SERVICE_ERROR_MOJO_CONNECTION_FAILURE));
+    return;
+  }
+
+  auto controller_request = mojo::MakeRequest(&media_perception_controller_);
+
+  chromeos::media_perception::mojom::MediaPerceptionControllerClientPtr
+      client_ptr;
+  media_perception_controller_client_ =
+      std::make_unique<MediaPerceptionControllerClient>(
+          delegate, mojo::MakeRequest(&client_ptr));
+  delegate->SetMediaPerceptionRequestHandler(
+      base::BindRepeating(&MediaPerceptionAPIManager::ActivateMediaPerception,
+                          weak_ptr_factory_.GetWeakPtr()));
+
+  media_perception_service_->GetController(std::move(controller_request),
+                                           std::move(client_ptr));
   std::move(callback).Run(std::move(state_started));
 }
 
diff --git a/extensions/browser/api/media_perception_private/media_perception_api_manager.h b/extensions/browser/api/media_perception_private/media_perception_api_manager.h
index 45b63d2..4f38e6f 100644
--- a/extensions/browser/api/media_perception_private/media_perception_api_manager.h
+++ b/extensions/browser/api/media_perception_private/media_perception_api_manager.h
@@ -12,10 +12,9 @@
 #include "base/scoped_observer.h"
 #include "chromeos/dbus/media_analytics_client.h"
 #include "chromeos/dbus/media_perception/media_perception.pb.h"
-#include "chromeos/services/media_perception/public/mojom/connector.mojom.h"
+#include "chromeos/services/media_perception/public/mojom/media_perception_service.mojom.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
 #include "extensions/common/api/media_perception_private.h"
-#include "services/video_capture/public/mojom/device_factory_provider.mojom.h"
 
 namespace extensions {
 
@@ -47,6 +46,10 @@
   static BrowserContextKeyedAPIFactory<MediaPerceptionAPIManager>*
   GetFactoryInstance();
 
+  // Handler for clients of the API requesting a MediaPerception Mojo interface.
+  void ActivateMediaPerception(
+      chromeos::media_perception::mojom::MediaPerceptionRequest request);
+
   // Public functions for MediaPerceptionPrivateAPI implementation.
   void SetAnalyticsComponent(
       const extensions::api::media_perception_private::Component& component,
@@ -67,6 +70,8 @@
  private:
   friend class BrowserContextKeyedAPIFactory<MediaPerceptionAPIManager>;
 
+  class MediaPerceptionControllerClient;
+
   // BrowserContextKeyedAPI:
   static const char* service_name() { return "MediaPerceptionAPIManager"; }
 
@@ -142,6 +147,17 @@
   // is set.
   std::string mount_point_;
 
+  // Pointer to the MediaPerceptionService interface for communicating with the
+  // service over Mojo.
+  chromeos::media_perception::mojom::MediaPerceptionServicePtr
+      media_perception_service_;
+
+  chromeos::media_perception::mojom::MediaPerceptionControllerPtr
+      media_perception_controller_;
+
+  std::unique_ptr<MediaPerceptionControllerClient>
+      media_perception_controller_client_;
+
   ScopedObserver<chromeos::MediaAnalyticsClient, MediaPerceptionAPIManager>
       scoped_observer_;
   base::WeakPtrFactory<MediaPerceptionAPIManager> weak_ptr_factory_;
diff --git a/extensions/browser/api/media_perception_private/media_perception_private_apitest.cc b/extensions/browser/api/media_perception_private/media_perception_private_apitest.cc
index 1c83e66..305b1a0 100644
--- a/extensions/browser/api/media_perception_private/media_perception_private_apitest.cc
+++ b/extensions/browser/api/media_perception_private/media_perception_private_apitest.cc
@@ -49,7 +49,18 @@
 
   void BindDeviceFactoryProviderToVideoCaptureService(
       video_capture::mojom::DeviceFactoryProviderPtr* provider) override {
-    LOG(ERROR) << "Not implemented.";
+    NOTIMPLEMENTED();
+  }
+
+  void SetMediaPerceptionRequestHandler(
+      MediaPerceptionRequestHandler handler) override {
+    NOTIMPLEMENTED();
+  }
+
+  void ForwardMediaPerceptionRequest(
+      chromeos::media_perception::mojom::MediaPerceptionRequest request,
+      content::RenderFrameHost* render_frame_host) override {
+    NOTIMPLEMENTED();
   }
 };
 
diff --git a/extensions/browser/api/web_request/web_request_api.cc b/extensions/browser/api/web_request/web_request_api.cc
index 5202159..b005d50 100644
--- a/extensions/browser/api/web_request/web_request_api.cc
+++ b/extensions/browser/api/web_request/web_request_api.cc
@@ -600,20 +600,18 @@
     bool skip_proxy = true;
     // There are a few internal WebUIs that use WebView tag that are whitelisted
     // for webRequest.
-    if (frame) {
-      auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
-      if (web_contents && WebViewGuest::IsGuest(web_contents)) {
-        auto* guest_web_contents =
-            WebViewGuest::GetTopLevelWebContents(web_contents);
-        auto& guest_url = guest_web_contents->GetURL();
-        if (guest_url.SchemeIs(content::kChromeUIScheme)) {
-          auto* feature = FeatureProvider::GetAPIFeature("webRequestInternal");
-          if (feature
-                  ->IsAvailableToContext(nullptr, Feature::WEBUI_CONTEXT,
-                                         guest_url)
-                  .is_available()) {
-            skip_proxy = false;
-          }
+    auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
+    if (web_contents && WebViewGuest::IsGuest(web_contents)) {
+      auto* guest_web_contents =
+          WebViewGuest::GetTopLevelWebContents(web_contents);
+      auto& guest_url = guest_web_contents->GetURL();
+      if (guest_url.SchemeIs(content::kChromeUIScheme)) {
+        auto* feature = FeatureProvider::GetAPIFeature("webRequestInternal");
+        if (feature
+                ->IsAvailableToContext(nullptr, Feature::WEBUI_CONTEXT,
+                                       guest_url)
+                .is_available()) {
+          skip_proxy = false;
         }
       }
     }
@@ -639,25 +637,22 @@
   // NOTE: This request may be proxied on behalf of an incognito frame, but
   // |this| will always be bound to a regular profile (see
   // |BrowserContextKeyedAPI::kServiceRedirectedInIncognito|). As such, we use
-  // the frame's BrowserContext when |frame| is non-null.
-  auto* browser_context =
-      frame ? frame->GetProcess()->GetBrowserContext() : browser_context_;
+  // the frame's BrowserContext.
+  auto* browser_context = frame->GetProcess()->GetBrowserContext();
   DCHECK(browser_context == browser_context_ ||
          (browser_context->IsOffTheRecord() &&
           ExtensionsBrowserClient::Get()->GetOriginalContext(browser_context) ==
               browser_context_));
-  const bool is_for_browser_initiated_requests = is_navigation || !frame;
   base::PostTaskWithTraits(
       FROM_HERE, {BrowserThread::IO},
-      base::BindOnce(
-          &WebRequestProxyingURLLoaderFactory::StartProxying, browser_context,
-          browser_context->GetResourceContext(),
-          // Match the behavior of the WebRequestInfo constructor
-          // which takes a net::URLRequest*.
-          is_for_browser_initiated_requests ? -1 : frame->GetProcess()->GetID(),
-          request_id_generator_, std::move(navigation_ui_data),
-          base::Unretained(info_map_), std::move(proxied_request),
-          std::move(target_factory_info)));
+      base::BindOnce(&WebRequestProxyingURLLoaderFactory::StartProxying,
+                     browser_context, browser_context->GetResourceContext(),
+                     // Match the behavior of the WebRequestInfo constructor
+                     // which takes a net::URLRequest*.
+                     is_navigation ? -1 : frame->GetProcess()->GetID(),
+                     request_id_generator_, std::move(navigation_ui_data),
+                     base::Unretained(info_map_), std::move(proxied_request),
+                     std::move(target_factory_info)));
   return true;
 }
 
diff --git a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
index bfae9c4..2ea6f265 100644
--- a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
+++ b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
@@ -25,7 +25,6 @@
     int32_t network_service_request_id,
     int32_t routing_id,
     uint32_t options,
-    bool is_non_navigation_browser_request,
     const network::ResourceRequest& request,
     const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
     network::mojom::URLLoaderRequest loader_request,
@@ -36,7 +35,6 @@
       network_service_request_id_(network_service_request_id),
       routing_id_(routing_id),
       options_(options),
-      is_non_navigation_browser_request_(is_non_navigation_browser_request),
       traffic_annotation_(traffic_annotation),
       proxied_loader_binding_(this, std::move(loader_request)),
       target_client_(std::move(client)),
@@ -69,22 +67,6 @@
       routing_id_, factory_->resource_context_, request_,
       !(options_ & network::mojom::kURLLoadOptionSynchronous));
 
-  if (is_non_navigation_browser_request_) {
-    // ResourceRequest always has a valid-looking ResourceType value since it's
-    // non-optional and defaults to 0 (i.e. MAIN_FRAME), even of the
-    // corresponding request didn't actually come from a renderer. Because
-    // |info_| was blindly constructed from that ResourceRequest, it also now
-    // appears to pertain to a main-frame request.
-    //
-    // Because we already know this is a browser-originated request, we
-    // explicitly reset |info_->type| to null. A request having no ResourceType
-    // effectively implies a browser-originated request to any subsequent
-    // WebRequest logic that cares, e.g. some permission checking to determine
-    // when to filter certain kinds of requests.
-    info_->type.reset();
-    info_->web_request_type = WebRequestResourceType::OTHER;
-  }
-
   auto continuation =
       base::BindRepeating(&InProgressRequest::ContinueToBeforeSendHeaders,
                           weak_factory_.GetWeakPtr());
@@ -617,6 +599,9 @@
     const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
+  // Make sure we are not proxying a browser initiated non-navigation request.
+  DCHECK(render_process_id_ != -1 || navigation_ui_data_);
+
   // The request ID doesn't really matter in the Network Service path. It just
   // needs to be unique per-BrowserContext so extensions can make sense of it.
   // Note that |network_service_request_id_| by contrast is not necessarily
@@ -633,18 +618,11 @@
     network_request_id_to_web_request_id_.emplace(request_id, web_request_id);
   }
 
-  // The WebRequest API treats browser-originated non-navigation requests with a
-  // few additional restrictions, so we deduce and propagate that information
-  // here.
-  const bool is_non_navigation_browser_request =
-      render_process_id_ == -1 && !navigation_ui_data_;
-
   auto result = requests_.emplace(
       web_request_id,
       std::make_unique<InProgressRequest>(
-          this, web_request_id, request_id, routing_id, options,
-          is_non_navigation_browser_request, request, traffic_annotation,
-          std::move(loader_request), std::move(client)));
+          this, web_request_id, request_id, routing_id, options, request,
+          traffic_annotation, std::move(loader_request), std::move(client)));
   result.first->second->Restart();
 }
 
diff --git a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
index 9d47f00..798b144 100644
--- a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
+++ b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
@@ -55,7 +55,6 @@
         int32_t routing_id,
         int32_t network_service_request_id,
         uint32_t options,
-        bool is_non_navigation_browser_request,
         const network::ResourceRequest& request,
         const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
         network::mojom::URLLoaderRequest loader_request,
@@ -116,7 +115,6 @@
     const int32_t network_service_request_id_;
     const int32_t routing_id_;
     const uint32_t options_;
-    const bool is_non_navigation_browser_request_;
     const net::MutableNetworkTrafficAnnotationTag traffic_annotation_;
     mojo::Binding<network::mojom::URLLoader> proxied_loader_binding_;
     network::mojom::URLLoaderClientPtr target_client_;
diff --git a/extensions/browser/event_listener_map.h b/extensions/browser/event_listener_map.h
index b571218..7ca17395 100644
--- a/extensions/browser/event_listener_map.h
+++ b/extensions/browser/event_listener_map.h
@@ -144,6 +144,8 @@
 class EventListenerMap {
  public:
   using ListenerList = std::vector<std::unique_ptr<EventListener>>;
+  // The key here is an event name.
+  using ListenerMap = std::unordered_map<std::string, ListenerList>;
 
   class Delegate {
    public:
@@ -166,6 +168,9 @@
   // Returns true if the listener was removed .
   bool RemoveListener(const EventListener* listener);
 
+  // Get the map of all EventListeners.
+  const ListenerMap& listeners() const { return listeners_; };
+
   // Returns the set of listeners that want to be notified of |event|.
   std::set<const EventListener*> GetEventListeners(const Event& event);
 
@@ -218,8 +223,6 @@
                                  const base::DictionaryValue& filtered);
 
  private:
-  // The key here is an event name.
-  using ListenerMap = std::map<std::string, ListenerList>;
 
   void CleanupListener(EventListener* listener);
   bool IsFilteredEvent(const Event& event) const;
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 2f194bc4..34e05501 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1344,6 +1344,7 @@
   AUTOTESTPRIVATE_BOOTSTRAPMACHINELEARNINGSERVICE = 1281,
   AUTOTESTPRIVATE_RUNCROSTINIUNINSTALLER = 1282,
   AUTOTESTPRIVATE_TAKESCREENSHOT = 1283,
+  ACCESSIBILITY_PRIVATE_TOGGLEDICTATION = 1284,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/browser/extension_web_contents_observer.cc b/extensions/browser/extension_web_contents_observer.cc
index 65c9e6a..6bfc6a5 100644
--- a/extensions/browser/extension_web_contents_observer.cc
+++ b/extensions/browser/extension_web_contents_observer.cc
@@ -19,6 +19,7 @@
 #include "extensions/browser/mojo/interface_registration.h"
 #include "extensions/browser/process_manager.h"
 #include "extensions/browser/renderer_startup_helper.h"
+#include "extensions/browser/url_loader_factory_manager.h"
 #include "extensions/browser/view_type_utils.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
@@ -172,6 +173,8 @@
 
 void ExtensionWebContentsObserver::ReadyToCommitNavigation(
     content::NavigationHandle* navigation_handle) {
+  URLLoaderFactoryManager::ReadyToCommitNavigation(navigation_handle);
+
   if (navigation_handle->IsInMainFrame() &&
       !navigation_handle->IsSameDocument()) {
     ExtensionApiFrameIdMap::Get()->OnMainFrameReadyToCommitNavigation(
diff --git a/extensions/browser/script_executor.cc b/extensions/browser/script_executor.cc
index a07e6bc..7364de2 100644
--- a/extensions/browser/script_executor.cc
+++ b/extensions/browser/script_executor.cc
@@ -4,6 +4,9 @@
 
 #include "extensions/browser/script_executor.h"
 
+#include <set>
+#include <string>
+
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/hash.h"
@@ -17,6 +20,7 @@
 #include "extensions/browser/extension_api_frame_id_map.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/script_execution_observer.h"
+#include "extensions/browser/url_loader_factory_manager.h"
 #include "extensions/common/extension_messages.h"
 #include "ipc/ipc_message.h"
 #include "ipc/ipc_message_macros.h"
@@ -125,6 +129,7 @@
     if (!root_is_main_frame_ && !ShouldIncludeFrame(frame))
       return;
     pending_render_frames_.insert(frame);
+    URLLoaderFactoryManager::WillExecuteCode(frame, host_id_);
     frame->Send(new ExtensionMsg_ExecuteCode(frame->GetRoutingID(), params));
   }
 
diff --git a/extensions/browser/url_loader_factory_manager.cc b/extensions/browser/url_loader_factory_manager.cc
new file mode 100644
index 0000000..7a94d5f
--- /dev/null
+++ b/extensions/browser/url_loader_factory_manager.cc
@@ -0,0 +1,312 @@
+// 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 "extensions/browser/url_loader_factory_manager.h"
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/stl_util.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/process_map.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/manifest_handlers/content_scripts_handler.h"
+#include "extensions/common/user_script.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "url/scheme_host_port.h"
+#include "url/url_constants.h"
+
+namespace extensions {
+
+namespace {
+
+enum class FactoryUser {
+  kContentScript,
+  kExtensionProcess,
+};
+
+bool DoContentScriptsDependOnRelaxedCorb(const Extension& extension) {
+  // TODO(lukasza): https://crbug.com/846346: Return false if the
+  // |extension| doesn't need a special URLLoaderFactory based on
+  // Extensions.CrossOriginFetchFromContentScript2 Rappor data.
+
+  // All modern extension manifests depend on relaxed CORB.
+  return extension.manifest_version() <= 2;
+}
+
+bool DoExtensionPermissionsCoverCorsOrCorbRelatedOrigins(
+    const Extension& extension) {
+  // TODO(lukasza): https://crbug.com/846346: Return false if the |extension|
+  // doesn't need a special URLLoaderFactory based on |extension| permissions.
+  // For now we conservatively assume that all extensions need relaxed CORS/CORB
+  // treatment.
+  return true;
+}
+
+bool IsSpecialURLLoaderFactoryRequired(const Extension& extension,
+                                       FactoryUser factory_user) {
+  switch (factory_user) {
+    case FactoryUser::kContentScript:
+      return DoContentScriptsDependOnRelaxedCorb(extension) &&
+             DoExtensionPermissionsCoverCorsOrCorbRelatedOrigins(extension);
+    case FactoryUser::kExtensionProcess:
+      return DoExtensionPermissionsCoverCorsOrCorbRelatedOrigins(extension);
+  }
+}
+
+network::mojom::URLLoaderFactoryPtrInfo CreateURLLoaderFactory(
+    content::RenderProcessHost* process,
+    network::mojom::NetworkContext* network_context,
+    const Extension& extension) {
+  // Compute relaxed CORB config to be used by |extension|.
+  network::mojom::URLLoaderFactoryParamsPtr params =
+      network::mojom::URLLoaderFactoryParams::New();
+  params->process_id = process->GetID();
+  // TODO(lukasza): https://crbug.com/846346: Use more granular CORB enforcement
+  // based on the specific |extension|'s permissions.
+  params->is_corb_enabled = false;
+
+  // Create the URLLoaderFactory.
+  network::mojom::URLLoaderFactoryPtrInfo factory_info;
+  network_context->CreateURLLoaderFactory(mojo::MakeRequest(&factory_info),
+                                          std::move(params));
+  return factory_info;
+}
+
+// If |match_about_blank| is true, then traverses parent/opener chain until the
+// first non-about-scheme document and returns its url.  Otherwise, simply
+// returns |document_url|.
+//
+// This function approximates ScriptContext::GetEffectiveDocumentURL from the
+// renderer side.  Unlike the renderer version of this code (in
+// ScriptContext::GetEffectiveDocumentURL) the code below doesn't consider
+// whether security origin of |frame| can access |next_candidate|.  This is
+// okay, because our only caller (DoesContentScriptMatchNavigation) expects
+// false positives.
+GURL GetEffectiveDocumentURL(content::RenderFrameHost* frame,
+                             const GURL& document_url,
+                             bool match_about_blank) {
+  base::flat_set<content::RenderFrameHost*> already_visited_frames;
+
+  // Common scenario. If |match_about_blank| is false (as is the case in most
+  // extensions), or if the frame is not an about:-page, just return
+  // |document_url| (supposedly the URL of the frame).
+  if (!match_about_blank || !document_url.SchemeIs(url::kAboutScheme))
+    return document_url;
+
+  // Non-sandboxed about:blank and about:srcdoc pages inherit their security
+  // origin from their parent frame/window. So, traverse the frame/window
+  // hierarchy to find the closest non-about:-page and return its URL.
+  content::RenderFrameHost* found_frame = frame;
+  do {
+    DCHECK(found_frame);
+    already_visited_frames.insert(found_frame);
+
+    // The loop should only execute (and consider the parent chain) if the
+    // currently considered frame has about: scheme.
+    DCHECK(match_about_blank);
+    DCHECK(
+        ((found_frame == frame) && document_url.SchemeIs(url::kAboutScheme)) ||
+        (found_frame->GetLastCommittedURL().SchemeIs(url::kAboutScheme)));
+
+    // Attempt to find |next_candidate| - either a parent of opener of
+    // |found_frame|.
+    content::RenderFrameHost* next_candidate = found_frame->GetParent();
+    if (!next_candidate) {
+      next_candidate =
+          content::WebContents::FromRenderFrameHost(found_frame)->GetOpener();
+    }
+    if (!next_candidate ||
+        base::ContainsKey(already_visited_frames, next_candidate)) {
+      break;
+    }
+
+    found_frame = next_candidate;
+  } while (found_frame->GetLastCommittedURL().SchemeIs(url::kAboutScheme));
+
+  if (found_frame == frame)
+    return document_url;  // Not committed yet at ReadyToCommitNavigation time.
+  return found_frame->GetLastCommittedURL();
+}
+
+// If |user_script| will inject JavaScript content script into the target of
+// |navigation|, then DoesContentScriptMatchNavigation returns true.  Otherwise
+// it may return either true or false.  Note that this function ignores CSS
+// content scripts.
+//
+// This function approximates a subset of checks from
+// UserScriptSet::GetInjectionForScript (which runs in the renderer process).
+// Unlike the renderer version, the code below doesn't consider ability to
+// create an injection host or the results of ScriptInjector::CanExecuteOnFrame.
+// Additionally the |effective_url| calculations are also only an approximation.
+// This is okay, because we may return either true even if no content scripts
+// would be injected (i.e. it is okay to create a special URLLoaderFactory when
+// in reality the content script won't be injected and won't need the factory).
+bool DoesContentScriptMatchNavigation(const UserScript& user_script,
+                                      content::NavigationHandle* navigation) {
+  // A special URLLoaderFactory is only needed for Javascript content scripts
+  // (and is never needed for CSS-only injections).
+  if (user_script.js_scripts().empty())
+    return false;
+
+  content::RenderFrameHost* frame = navigation->GetRenderFrameHost();
+  GURL effective_url = GetEffectiveDocumentURL(frame, navigation->GetURL(),
+                                               user_script.match_about_blank());
+  bool is_subframe = frame->GetParent();
+  return user_script.MatchesDocument(effective_url, is_subframe);
+}
+
+// If |extension|'s manifest declares that it may inject JavaScript content
+// script into the target of |navigation|, then DoContentScriptsMatchNavigation
+// returns true.  Otherwise it may return either true or false.  Note that this
+// function ignores CSS content scripts.
+bool DoContentScriptsMatchNavigation(const Extension& extension,
+                                     content::NavigationHandle* navigation) {
+  const UserScriptList& list =
+      ContentScriptsInfo::GetContentScripts(&extension);
+  return std::any_of(list.begin(), list.end(),
+                     [navigation](const std::unique_ptr<UserScript>& script) {
+                       return DoesContentScriptMatchNavigation(*script,
+                                                               navigation);
+                     });
+}
+
+void MarkInitiatorsAsRequiringSeparateURLLoaderFactory(
+    content::RenderFrameHost* frame,
+    std::vector<url::Origin> request_initiators,
+    bool push_to_renderer_now) {
+  DCHECK(!request_initiators.empty());
+  if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
+    frame->MarkInitiatorsAsRequiringSeparateURLLoaderFactory(
+        std::move(request_initiators), push_to_renderer_now);
+  } else {
+    // TODO(lukasza): In non-NetworkService implementation of CORB, make an
+    // exception only for specific extensions (e.g. based on process id,
+    // similarly to how r585124 does it for plugins).  Doing so will likely
+    // interfere with Extensions.CrossOriginFetchFromContentScript2 Rappor
+    // metric, so this needs to wait until this metric is not needed anymore.
+  }
+}
+
+}  // namespace
+
+// static
+void URLLoaderFactoryManager::ReadyToCommitNavigation(
+    content::NavigationHandle* navigation) {
+  content::RenderFrameHost* frame = navigation->GetRenderFrameHost();
+
+  std::vector<url::Origin> initiators_requiring_separate_factory;
+  const ExtensionRegistry* registry =
+      ExtensionRegistry::Get(frame->GetProcess()->GetBrowserContext());
+  DCHECK(registry);  // ReadyToCommitNavigation shouldn't run during shutdown.
+  for (const auto& it : registry->enabled_extensions()) {
+    const Extension& extension = *it;
+    if (!DoContentScriptsMatchNavigation(extension, navigation))
+      continue;
+
+    if (!IsSpecialURLLoaderFactoryRequired(extension,
+                                           FactoryUser::kContentScript))
+      continue;
+
+    initiators_requiring_separate_factory.push_back(
+        url::Origin::Create(extension.url()));
+  }
+
+  if (!initiators_requiring_separate_factory.empty()) {
+    // At ReadyToCommitNavigation time there is no need to trigger an explicit
+    // push of URLLoaderFactoryBundle to the renderer - it is sufficient if the
+    // factories are pushed during the commit.
+    constexpr bool kPushToRendererNow = false;
+
+    MarkInitiatorsAsRequiringSeparateURLLoaderFactory(
+        frame, std::move(initiators_requiring_separate_factory),
+        kPushToRendererNow);
+  }
+}
+
+// static
+void URLLoaderFactoryManager::WillExecuteCode(content::RenderFrameHost* frame,
+                                              const HostID& host_id) {
+  if (host_id.type() != HostID::EXTENSIONS)
+    return;
+
+  const ExtensionRegistry* registry =
+      ExtensionRegistry::Get(frame->GetProcess()->GetBrowserContext());
+  DCHECK(registry);  // WillExecuteCode shouldn't happen during shutdown.
+  const Extension* extension =
+      registry->enabled_extensions().GetByID(host_id.id());
+  DCHECK(extension);  // Guaranteed by the caller - see the doc comment.
+
+  if (!IsSpecialURLLoaderFactoryRequired(*extension,
+                                         FactoryUser::kContentScript))
+    return;
+
+  // When WillExecuteCode runs, the frame already received the initial
+  // URLLoaderFactoryBundle - therefore we need to request a separate push
+  // below.  This doesn't race with the ExtensionMsg_ExecuteCode message,
+  // because the URLLoaderFactoryBundle is sent to the renderer over
+  // content.mojom.FrameNavigationControl interface which is associated with the
+  // legacy IPC pipe (raciness will be introduced if that ever changes).
+  constexpr bool kPushToRendererNow = true;
+
+  MarkInitiatorsAsRequiringSeparateURLLoaderFactory(
+      frame, {url::Origin::Create(extension->url())}, kPushToRendererNow);
+}
+
+// static
+network::mojom::URLLoaderFactoryPtrInfo URLLoaderFactoryManager::CreateFactory(
+    content::RenderProcessHost* process,
+    network::mojom::NetworkContext* network_context,
+    const url::Origin& initiator_origin) {
+  content::BrowserContext* browser_context = process->GetBrowserContext();
+  const ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context);
+  DCHECK(registry);  // CreateFactory shouldn't happen during shutdown.
+
+  // Opaque origins normally don't inherit security properties of their
+  // precursor origins, but here opaque origins (e.g. think data: URIs) created
+  // by an extension should inherit CORS/CORB treatment of the extension.
+  url::SchemeHostPort precursor_origin =
+      initiator_origin.GetTupleOrPrecursorTupleIfOpaque();
+
+  // Don't create a factory for something that is not an extension.
+  if (precursor_origin.scheme() != kExtensionScheme)
+    return network::mojom::URLLoaderFactoryPtrInfo();
+
+  // Find the |extension| associated with |initiator_origin|.
+  const Extension* extension =
+      registry->enabled_extensions().GetByID(precursor_origin.host());
+  if (!extension) {
+    // This may happen if an extension gets disabled between the time
+    // RenderFrameHost::MarkInitiatorAsRequiringSeparateURLLoaderFactory is
+    // called and the time
+    // ContentBrowserClient::CreateURLLoaderFactory is called.
+    return network::mojom::URLLoaderFactoryPtrInfo();
+  }
+
+  // Figure out if the factory is needed for content scripts VS extension
+  // renderer.
+  FactoryUser factory_user = FactoryUser::kContentScript;
+  ProcessMap* process_map = ProcessMap::Get(browser_context);
+  if (process_map->Contains(extension->id(), process->GetID()))
+    factory_user = FactoryUser::kExtensionProcess;
+
+  // Create the factory (but only if really needed).
+  if (!IsSpecialURLLoaderFactoryRequired(*extension, factory_user))
+    return network::mojom::URLLoaderFactoryPtrInfo();
+  return CreateURLLoaderFactory(process, network_context, *extension);
+}
+
+}  // namespace extensions
diff --git a/extensions/browser/url_loader_factory_manager.h b/extensions/browser/url_loader_factory_manager.h
new file mode 100644
index 0000000..8f33f07
--- /dev/null
+++ b/extensions/browser/url_loader_factory_manager.h
@@ -0,0 +1,72 @@
+// 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 EXTENSIONS_BROWSER_URL_LOADER_FACTORY_MANAGER_H_
+#define EXTENSIONS_BROWSER_URL_LOADER_FACTORY_MANAGER_H_
+
+#include "base/macros.h"
+#include "content/public/browser/navigation_handle.h"
+#include "extensions/common/host_id.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+
+namespace content {
+class RenderFrameHost;
+class RenderProcessHost;
+}  // namespace content
+
+namespace url {
+class Origin;
+}  // namespace url
+
+namespace extensions {
+
+// This class manages URLLoaderFactory objects that handle network requests that
+// require extension-specific permissions (related to relaxed CORB and CORS).
+//
+// See also https://crbug.com/846346 for motivation for having separate
+// URLLoaderFactory objects for content scripts.
+class URLLoaderFactoryManager {
+ public:
+  // Only static methods.
+  URLLoaderFactoryManager() = delete;
+
+  // To be called before a navigation commits (to ensure that the renderer gets
+  // the special URLLoaderFactory before injecting content scripts declared in
+  // an extension manifest).
+  //
+  // This method will inspect all enabled extensions and ask RenderFrameHost to
+  // create separate URLLoaderFactory objects for the extensions that declare in
+  // their manifest desire to inject content scripts into the target of the
+  // |navigation|.
+  static void ReadyToCommitNavigation(content::NavigationHandle* navigation);
+
+  // To be called before ExtensionMsg_ExecuteCode is sent to a renderer process
+  // (to ensure that the renderer gets the special URLLoaderFactory before
+  // injecting content script requested via chrome.tabs.executeScript).
+  //
+  // This method may ask RenderFrameHost to create a separate URLLoaderFactory
+  // object for extension identified by |host_id|.  The caller needs to ensure
+  // that if |host_id.type() == HostID::EXTENSIONS|, then the extension with the
+  // given id exists and is enabled.
+  static void WillExecuteCode(content::RenderFrameHost* frame,
+                              const HostID& host_id);
+
+  // Creates a URLLoaderFactory that should be used for requests initiated from
+  // |process| by |initiator_origin|.  Returns a "null" InterfacePtrInfo if the
+  // default, extensions-agnostic URLLoaderFactory should be used (if either
+  // |initiator_origin| is not associated with an extension, or the extension
+  // doesn't need a special URLLoaderFactory).
+  static network::mojom::URLLoaderFactoryPtrInfo CreateFactory(
+      content::RenderProcessHost* process,
+      network::mojom::NetworkContext* network_context,
+      const url::Origin& initiator_origin);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(URLLoaderFactoryManager);
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_URL_LOADER_FACTORY_MANAGER_H_
diff --git a/extensions/common/user_script.cc b/extensions/common/user_script.cc
index f9276b90..14349e6 100644
--- a/extensions/common/user_script.cc
+++ b/extensions/common/user_script.cc
@@ -7,6 +7,9 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <memory>
+#include <utility>
+
 #include "base/atomic_sequence_num.h"
 #include "base/command_line.h"
 #include "base/pickle.h"
@@ -47,10 +50,9 @@
 // static
 const char UserScript::kFileExtension[] = ".user.js";
 
-
 // static
 int UserScript::GenerateUserScriptID() {
-   return g_user_script_id_generator.GetNext();
+  return g_user_script_id_generator.GetNext();
 }
 
 bool UserScript::IsURLUserScript(const GURL& url,
@@ -168,6 +170,14 @@
   return true;
 }
 
+bool UserScript::MatchesDocument(const GURL& effective_document_url,
+                                 bool is_subframe) const {
+  if (is_subframe && !match_all_frames())
+    return false;
+
+  return MatchesURL(effective_document_url);
+}
+
 void UserScript::File::Pickle(base::Pickle* pickle) const {
   pickle->WriteString(url_.spec());
   // Do not write path. It's not needed in the renderer.
diff --git a/extensions/common/user_script.h b/extensions/common/user_script.h
index 0573eb662..6d4d25d 100644
--- a/extensions/common/user_script.h
+++ b/extensions/common/user_script.h
@@ -5,6 +5,7 @@
 #ifndef EXTENSIONS_COMMON_USER_SCRIPT_H_
 #define EXTENSIONS_COMMON_USER_SCRIPT_H_
 
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -229,6 +230,13 @@
   // otherwise.
   bool MatchesURL(const GURL& url) const;
 
+  // Returns true if the script should be applied to the given
+  // |effective_document_url| (calculated by the caller based on
+  // match_about_blank()| while also taking into account whether the document's
+  // frame |is_subframe| and what the |top_level_origin| is.
+  bool MatchesDocument(const GURL& effective_document_url,
+                       bool is_subframe) const;
+
   // Serializes the UserScript into a pickle. The content of the scripts and
   // paths to UserScript::Files will not be serialized!
   void Pickle(base::Pickle* pickle) const;
@@ -327,7 +335,7 @@
 // Information we need while removing scripts from a UserScriptLoader.
 struct UserScriptIDPair {
   UserScriptIDPair(int id, const HostID& host_id);
-  UserScriptIDPair(int id);
+  explicit UserScriptIDPair(int id);
 
   int id;
   HostID host_id;
diff --git a/extensions/renderer/module_system.cc b/extensions/renderer/module_system.cc
index 0b0ed47e..73d390c5eb 100644
--- a/extensions/renderer/module_system.cc
+++ b/extensions/renderer/module_system.cc
@@ -122,7 +122,7 @@
   //
   // Prefer to use Mojo from C++ if possible rather than adding to this list.
   static const char* const kApisRequiringMojo[] = {
-      "mimeHandlerPrivate", "mojoPrivate",
+      "mediaPerceptionPrivate", "mimeHandlerPrivate", "mojoPrivate",
   };
 
   for (const auto* api : kApisRequiringMojo) {
diff --git a/extensions/renderer/user_script_set.cc b/extensions/renderer/user_script_set.cc
index 35657b8..40cf7f9 100644
--- a/extensions/renderer/user_script_set.cc
+++ b/extensions/renderer/user_script_set.cc
@@ -212,13 +212,11 @@
     injection_host.reset(new WebUIInjectionHost(host_id));
   }
 
-  if (web_frame->Parent() && !script->match_all_frames())
-    return injection;  // Only match subframes if the script declared it.
-
   GURL effective_document_url = ScriptContext::GetEffectiveDocumentURL(
       web_frame, document_url, script->match_about_blank());
 
-  if (!script->MatchesURL(effective_document_url))
+  bool is_subframe = web_frame->Parent();
+  if (!script->MatchesDocument(effective_document_url, is_subframe))
     return injection;
 
   std::unique_ptr<ScriptInjector> injector(
diff --git a/extensions/shell/browser/shell_content_browser_client.cc b/extensions/shell/browser/shell_content_browser_client.cc
index 4bf301e..a864ad7 100644
--- a/extensions/shell/browser/shell_content_browser_client.cc
+++ b/extensions/shell/browser/shell_content_browser_client.cc
@@ -38,6 +38,7 @@
 #include "extensions/browser/info_map.h"
 #include "extensions/browser/io_thread_extension_message_filter.h"
 #include "extensions/browser/process_map.h"
+#include "extensions/browser/url_loader_factory_manager.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/switches.h"
@@ -312,6 +313,15 @@
   return false;
 }
 
+network::mojom::URLLoaderFactoryPtrInfo
+ShellContentBrowserClient::CreateURLLoaderFactoryForNetworkRequests(
+    content::RenderProcessHost* process,
+    network::mojom::NetworkContext* network_context,
+    const url::Origin& request_initiator) {
+  return URLLoaderFactoryManager::CreateFactory(process, network_context,
+                                                request_initiator);
+}
+
 ShellBrowserMainParts* ShellContentBrowserClient::CreateShellBrowserMainParts(
     const content::MainFunctionParams& parameters,
     ShellBrowserMainDelegate* browser_main_delegate) {
diff --git a/extensions/shell/browser/shell_content_browser_client.h b/extensions/shell/browser/shell_content_browser_client.h
index 8a7b6205..03718221 100644
--- a/extensions/shell/browser/shell_content_browser_client.h
+++ b/extensions/shell/browser/shell_content_browser_client.h
@@ -88,6 +88,11 @@
       bool is_main_frame,
       ui::PageTransition page_transition,
       bool has_user_gesture) override;
+  network::mojom::URLLoaderFactoryPtrInfo
+  CreateURLLoaderFactoryForNetworkRequests(
+      content::RenderProcessHost* process,
+      network::mojom::NetworkContext* network_context,
+      const url::Origin& request_initiator) override;
 
  protected:
   // Subclasses may wish to provide their own ShellBrowserMainParts.
diff --git a/ios/chrome/browser/signin/ios_chrome_signin_client.mm b/ios/chrome/browser/signin/ios_chrome_signin_client.mm
index a4bd985..8038830 100644
--- a/ios/chrome/browser/signin/ios_chrome_signin_client.mm
+++ b/ios/chrome/browser/signin/ios_chrome_signin_client.mm
@@ -28,8 +28,8 @@
     SigninErrorController* signin_error_controller,
     scoped_refptr<content_settings::CookieSettings> cookie_settings,
     scoped_refptr<HostContentSettingsMap> host_content_settings_map)
-    : network_callback_helper_(
-          std::make_unique<WaitForNetworkCallbackHelper>()),
+    : network_callback_helper_(std::make_unique<WaitForNetworkCallbackHelper>(
+          GetApplicationContext()->GetNetworkConnectionTracker())),
       browser_state_(browser_state),
       signin_error_controller_(signin_error_controller),
       cookie_settings_(cookie_settings),
diff --git a/ios/web/public/web_task_traits.h b/ios/web/public/web_task_traits.h
index 88dafe7..0529508 100644
--- a/ios/web/public/web_task_traits.h
+++ b/ios/web/public/web_task_traits.h
@@ -40,13 +40,20 @@
 // Posting to a WebThread must only be done after it was initialized (ref.
 // WebMainLoop::CreateThreads() phase).
 class WebTaskTraitsExtension {
+  using WebThreadIDFilter =
+      base::trait_helpers::RequiredEnumTraitFilter<WebThread::ID>;
+  using NonNestableFilter =
+      base::trait_helpers::BooleanTraitFilter<NonNestable>;
+
  public:
   static constexpr uint8_t kExtensionId =
       base::TaskTraitsExtensionStorage::kFirstEmbedderExtensionId;
 
-  struct ValidTrait {
-    ValidTrait(WebThread::ID) {}
-    ValidTrait(NonNestable) {}
+  struct ValidTrait : public base::TaskTraits::ValidTrait {
+    using base::TaskTraits::ValidTrait::ValidTrait;
+
+    ValidTrait(WebThread::ID);
+    ValidTrait(NonNestable);
   };
 
   template <
@@ -54,11 +61,9 @@
       class CheckArgumentsAreValid = std::enable_if_t<
           base::trait_helpers::AreValidTraits<ValidTrait, ArgTypes...>::value>>
   constexpr WebTaskTraitsExtension(ArgTypes... args)
-      : web_thread_(base::trait_helpers::GetValueFromArgList(
-            base::trait_helpers::RequiredEnumArgGetter<WebThread::ID>(),
+      : web_thread_(base::trait_helpers::GetTraitFromArgList<WebThreadIDFilter>(
             args...)),
-        nestable_(!base::trait_helpers::GetValueFromArgList(
-            base::trait_helpers::BooleanArgGetter<NonNestable>(),
+        nestable_(!base::trait_helpers::GetTraitFromArgList<NonNestableFilter>(
             args...)) {}
 
   constexpr base::TaskTraitsExtensionStorage Serialize() const {
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 8e0f17b..859ff648 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -1994,6 +1994,10 @@
   } else if (!_currentURLLoadWasTrigerred) {
     [self ensureContainerViewCreated];
 
+    // This method reloads last committed item, so make than item also pending.
+    self.sessionController.pendingItemIndex =
+        self.sessionController.lastCommittedItemIndex;
+
     // TODO(crbug.com/796608): end the practice of calling |loadCurrentURL|
     // when it is possible there is no current URL. If the call performs
     // necessary initialization, break that out.
diff --git a/ios/web/web_state/web_state_observer_inttest.mm b/ios/web/web_state/web_state_observer_inttest.mm
index 0f6aea5..31690cca 100644
--- a/ios/web/web_state/web_state_observer_inttest.mm
+++ b/ios/web/web_state/web_state_observer_inttest.mm
@@ -499,14 +499,25 @@
   EXPECT_FALSE((*context)->IsDownload());
   EXPECT_FALSE((*context)->IsPost());
   EXPECT_FALSE((*context)->GetError());
-  // This should be false. Navigation is determined as renderer initiated
-  // because there is no pending item during the restoration (crbug.com/888021).
-  EXPECT_TRUE((*context)->IsRendererInitiated());
+  if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
+    // This should be false. Navigation is determined as renderer initiated
+    // because there is no pending item during the restoration
+    // (crbug.com/888021).
+    EXPECT_TRUE((*context)->IsRendererInitiated());
+  } else {
+    EXPECT_FALSE((*context)->IsRendererInitiated());
+  }
   ASSERT_FALSE((*context)->GetResponseHeaders());
   ASSERT_TRUE(web_state->IsLoading());
-  // Pending item is null during restoration (crbug.com/888021).
+
   NavigationManager* navigation_manager = web_state->GetNavigationManager();
-  ASSERT_FALSE(navigation_manager->GetPendingItem());
+  if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
+    // Pending item is null during restoration (crbug.com/888021).
+    ASSERT_FALSE(navigation_manager->GetPendingItem());
+  } else {
+    ASSERT_TRUE(navigation_manager->GetPendingItem());
+    EXPECT_EQ(url, navigation_manager->GetPendingItem()->GetURL());
+  }
 }
 
 // Verifies correctness of |NavigationContext| (|arg1|) for restoration
@@ -534,9 +545,14 @@
   EXPECT_FALSE((*context)->IsDownload());
   EXPECT_FALSE((*context)->IsPost());
   EXPECT_FALSE((*context)->GetError());
-  // This should be false. Navigation is determined as renderer initiated
-  // because there is no pending item during the restoration (crbug.com/888021).
-  EXPECT_TRUE((*context)->IsRendererInitiated());
+  if (GetWebClient()->IsSlimNavigationManagerEnabled()) {
+    // This should be false. Navigation is determined as renderer initiated
+    // because there is no pending item during the restoration
+    // (crbug.com/888021).
+    EXPECT_TRUE((*context)->IsRendererInitiated());
+  } else {
+    EXPECT_FALSE((*context)->IsRendererInitiated());
+  }
   ASSERT_TRUE((*context)->GetResponseHeaders());
   std::string actual_mime_type;
   (*context)->GetResponseHeaders()->GetMimeType(&actual_mime_type);
diff --git a/ios/web/web_state/web_state_unittest.mm b/ios/web/web_state/web_state_unittest.mm
index b161ad81..9940592e 100644
--- a/ios/web/web_state/web_state_unittest.mm
+++ b/ios/web/web_state/web_state_unittest.mm
@@ -60,6 +60,8 @@
 }
 }  // namespace
 
+using wk_navigation_util::IsWKInternalUrl;
+
 // WebStateTest is parameterized on this enum to test both the legacy
 // implementation of navigation manager and the experimental implementation.
 enum NavigationManagerChoice {
@@ -367,16 +369,13 @@
       EXPECT_TRUE(last_committed_item);
       EXPECT_TRUE(last_committed_item &&
                   last_committed_item->GetURL() == "http://www.0.com/");
-      EXPECT_FALSE(navigation_manager->GetPendingItem());
       EXPECT_EQ(0, navigation_manager->GetLastCommittedItemIndex());
-      EXPECT_EQ(-1, navigation_manager->GetPendingItemIndex());
       EXPECT_TRUE(navigation_manager->GetBackwardItems().empty());
       EXPECT_EQ(std::max(navigation_manager->GetItemCount() - 1, 0),
                 static_cast<int>(navigation_manager->GetForwardItems().size()));
     }
     // TODO(crbug.com/877671): Ensure that the following API work correctly:
     //  - WebState::GetTitle
-    //  - WebState::IsLoading
     //  - WebState::GetLoadingProgress
     EXPECT_FALSE(web_state_ptr->IsCrashed());
     EXPECT_FALSE(web_state_ptr->IsEvicted());
@@ -386,6 +385,7 @@
     EXPECT_TRUE(visible_item && visible_item->GetURL() == "http://www.0.com/");
     EXPECT_FALSE(navigation_manager->CanGoBack());
     EXPECT_FALSE(navigation_manager->GetTransientItem());
+    EXPECT_FALSE(IsWKInternalUrl(web_state_ptr->GetVisibleURL()));
 
     return restored;
   }));
@@ -397,6 +397,13 @@
   if (web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
     histogram_tester_.ExpectTotalCount(kRestoreNavigationTime, 1);
   }
+
+  // Now wait until the last committed item is fully loaded.
+  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
+    EXPECT_FALSE(IsWKInternalUrl(web_state_ptr->GetVisibleURL()));
+
+    return !navigation_manager->GetPendingItem() && !web_state_ptr->IsLoading();
+  }));
 }
 
 // Tests that if a saved session is provided when creating a new WebState, it is
diff --git a/ios/web_view/internal/signin/ios_web_view_signin_client.mm b/ios/web_view/internal/signin/ios_web_view_signin_client.mm
index 249d151..c878768f 100644
--- a/ios/web_view/internal/signin/ios_web_view_signin_client.mm
+++ b/ios/web_view/internal/signin/ios_web_view_signin_client.mm
@@ -7,6 +7,7 @@
 #include "components/signin/core/browser/cookie_settings_util.h"
 #include "components/signin/core/browser/device_id_helper.h"
 #include "google_apis/gaia/gaia_auth_fetcher.h"
+#include "ios/web_view/internal/app/application_context.h"
 #import "ios/web_view/internal/sync/cwv_sync_controller_internal.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
@@ -21,8 +22,9 @@
     SigninErrorController* signin_error_controller,
     scoped_refptr<content_settings::CookieSettings> cookie_settings,
     scoped_refptr<HostContentSettingsMap> host_content_settings_map)
-    : network_callback_helper_(
-          std::make_unique<WaitForNetworkCallbackHelper>()),
+    : network_callback_helper_(std::make_unique<WaitForNetworkCallbackHelper>(
+          ios_web_view::ApplicationContext::GetInstance()
+              ->GetNetworkConnectionTracker())),
       pref_service_(pref_service),
       url_loader_factory_(url_loader_factory),
       cookie_manager_(cookie_manager),
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 2a9b06b..ef3d2e1 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -284,6 +284,14 @@
 const base::Feature kD3D11VP9Decoder{"D3D11VP9Decoder",
                                      base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Falls back to other decoders after audio/video decode error happens. The
+// implementation may choose different strategies on when to fallback. See
+// DecoderStream for details. When disabled, playback will fail immediately
+// after a decode error happens. This can be useful in debugging and testing
+// because the behavior is simpler and more predictable.
+const base::Feature kFallbackAfterDecodeError{"FallbackAfterDecodeError",
+                                              base::FEATURE_ENABLED_BY_DEFAULT};
+
 // Manage and report MSE buffered ranges by PTS intervals, not DTS intervals.
 const base::Feature kMseBufferByPts{"MseBufferByPts",
                                     base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index daadde72..d955557d 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -107,6 +107,7 @@
 MEDIA_EXPORT extern const base::Feature kD3D11VP9Decoder;
 MEDIA_EXPORT extern const base::Feature kD3D11VideoDecoder;
 MEDIA_EXPORT extern const base::Feature kExternalClearKeyForTesting;
+MEDIA_EXPORT extern const base::Feature kFallbackAfterDecodeError;
 MEDIA_EXPORT extern const base::Feature kHardwareSecureDecryption;
 MEDIA_EXPORT extern const base::Feature kLimitParallelMediaPreloading;
 MEDIA_EXPORT extern const base::Feature kLowDelayVideoRenderingOnLiveStream;
diff --git a/media/filters/decoder_stream.cc b/media/filters/decoder_stream.cc
index 09790bb..062ad30 100644
--- a/media/filters/decoder_stream.cc
+++ b/media/filters/decoder_stream.cc
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
+#include "base/feature_list.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/single_thread_task_runner.h"
@@ -17,6 +18,7 @@
 #include "media/base/decoder_buffer.h"
 #include "media/base/limits.h"
 #include "media/base/media_log.h"
+#include "media/base/media_switches.h"
 #include "media/base/timestamp_constants.h"
 #include "media/base/video_decoder.h"
 #include "media/base/video_frame.h"
@@ -519,7 +521,8 @@
 
   switch (status) {
     case DecodeStatus::DECODE_ERROR:
-      if (!decoder_produced_a_frame_) {
+      if (!decoder_produced_a_frame_ &&
+          base::FeatureList::IsEnabled(kFallbackAfterDecodeError)) {
         pending_decode_requests_ = 0;
 
         // Prevent all pending decode requests and outputs from those requests
diff --git a/media/media_options.gni b/media/media_options.gni
index 435a5c1..46eaa581 100644
--- a/media/media_options.gni
+++ b/media/media_options.gni
@@ -186,12 +186,20 @@
     _default_mojo_media_host = "browser"
   } else if (is_android) {
     # Both chrome for Android and cast for Android belongs to this case
-    _default_mojo_media_services = [
-      "cdm",
-      "audio_decoder",
-      "video_decoder",
-    ]
-    _default_mojo_media_host = "gpu"
+    if (is_cast_audio_only) {
+      _default_mojo_media_services = [
+        "cdm",
+        "audio_decoder",
+      ]
+      _default_mojo_media_host = "browser"
+    } else {
+      _default_mojo_media_services = [
+        "cdm",
+        "audio_decoder",
+        "video_decoder",
+      ]
+      _default_mojo_media_host = "gpu"
+    }
   } else if (is_chromeos || is_mac || is_win) {
     _default_mojo_media_services = [ "video_decoder" ]
     _default_mojo_media_host = "gpu"
diff --git a/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
index f653481..6579bb0 100644
--- a/mojo/public/tools/bindings/chromium_bindings_configuration.gni
+++ b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
@@ -5,7 +5,6 @@
 _typemap_imports = [
   "//ash/public/interfaces/typemaps.gni",
   "//chrome/chrome_cleaner/interfaces/typemaps/typemaps.gni",
-  "//chrome/common/extensions/typemaps.gni",
   "//chrome/common/importer/typemaps.gni",
   "//chrome/common/media_router/mojo/typemaps.gni",
   "//chrome/typemaps.gni",
diff --git a/remoting/host/disconnect_window_win.cc b/remoting/host/disconnect_window_win.cc
index 1cd366e..71e2b7b 100644
--- a/remoting/host/disconnect_window_win.cc
+++ b/remoting/host/disconnect_window_win.cc
@@ -5,6 +5,7 @@
 #include <stddef.h>
 #include <windows.h>
 
+#include <cstdlib>
 #include <memory>
 
 #include "base/compiler_specific.h"
@@ -122,6 +123,8 @@
   bool has_hotkey_ = false;
   base::win::ScopedGDIObject<HPEN> border_pen_;
 
+  webrtc::DesktopVector mouse_position_;
+
   base::WeakPtrFactory<DisconnectWindowWin> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(DisconnectWindowWin);
@@ -380,7 +383,16 @@
 
 void DisconnectWindowWin::OnLocalMouseEvent(
     const webrtc::DesktopVector& position) {
-  ShowDialog();
+  // Don't show the dialog if the position changes by ~1px in any direction.
+  // This will prevent the dialog from being reshown due to small movements
+  // caused by hardware/software issues which cause cursor drift or small
+  // vibrations in the environment around the remote host.
+  if (std::abs(position.x() - mouse_position_.x()) > 1 ||
+      std::abs(position.y() - mouse_position_.y()) > 1) {
+    ShowDialog();
+  }
+
+  mouse_position_ = position;
 }
 
 void DisconnectWindowWin::OnLocalKeyboardEvent() {
diff --git a/remoting/protocol/webrtc_frame_scheduler_simple.cc b/remoting/protocol/webrtc_frame_scheduler_simple.cc
index 068b50d..94ab605 100644
--- a/remoting/protocol/webrtc_frame_scheduler_simple.cc
+++ b/remoting/protocol/webrtc_frame_scheduler_simple.cc
@@ -191,9 +191,11 @@
   if (updated_area - updated_region_area_.Max() > kBigFrameThresholdPixels) {
     int expected_frame_size =
         updated_area * kEstimatedBytesPerMegapixel / kPixelsPerMegapixel;
-    base::TimeDelta expected_send_delay = base::TimeDelta::FromMicroseconds(
-        base::Time::kMicrosecondsPerSecond * expected_frame_size /
-        pacing_bucket_.rate());
+    base::TimeDelta expected_send_delay =
+        pacing_bucket_.rate() ? base::TimeDelta::FromMicroseconds(
+                                    base::Time::kMicrosecondsPerSecond *
+                                    expected_frame_size / pacing_bucket_.rate())
+                              : base::TimeDelta::Max();
     if (expected_send_delay > kTargetFrameInterval) {
       params_out->vpx_min_quantizer = kMaxQuantizer;
     }
diff --git a/services/media_session/BUILD.gn b/services/media_session/BUILD.gn
index c2d97c5..6995402 100644
--- a/services/media_session/BUILD.gn
+++ b/services/media_session/BUILD.gn
@@ -12,12 +12,15 @@
 
 source_set("lib") {
   sources = [
+    "audio_focus_manager.cc",
+    "audio_focus_manager.h",
     "media_session_service.cc",
     "media_session_service.h",
   ]
 
   deps = [
     "//mojo/public/cpp/bindings",
+    "//services/media_session/public/mojom",
   ]
 
   public_deps = [
@@ -34,6 +37,7 @@
 source_set("tests") {
   testonly = true
   sources = [
+    "audio_focus_manager_unittest.cc",
     "media_session_service_unittest.cc",
   ]
 
@@ -41,6 +45,8 @@
     ":lib",
     "//base",
     "//base/test:test_support",
+    "//services/media_session/public/cpp/test:test_support",
+    "//services/media_session/public/mojom",
     "//services/service_manager/public/cpp/test:test_support",
     "//testing/gtest",
   ]
diff --git a/services/media_session/audio_focus_manager.cc b/services/media_session/audio_focus_manager.cc
new file mode 100644
index 0000000..c9a8b63f
--- /dev/null
+++ b/services/media_session/audio_focus_manager.cc
@@ -0,0 +1,232 @@
+// 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 "services/media_session/audio_focus_manager.h"
+
+#include "base/atomic_sequence_num.h"
+#include "base/memory/ptr_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "services/media_session/public/mojom/audio_focus.mojom.h"
+
+namespace media_session {
+
+namespace {
+
+// Generate a unique audio focus request ID for the audio focus request. The IDs
+// are only handed out by the audio focus manager.
+int GenerateAudioFocusRequestId() {
+  static base::AtomicSequenceNumber request_id;
+  return request_id.GetNext();
+}
+
+}  // namespace
+
+// static
+AudioFocusManager* AudioFocusManager::GetInstance() {
+  return base::Singleton<AudioFocusManager>::get();
+}
+
+AudioFocusManager::RequestResponse AudioFocusManager::RequestAudioFocus(
+    mojom::MediaSessionPtr media_session,
+    mojom::MediaSessionInfoPtr session_info,
+    mojom::AudioFocusType type,
+    base::Optional<RequestId> previous_id) {
+  DCHECK(!session_info.is_null());
+  DCHECK(media_session.is_bound());
+
+  if (!audio_focus_stack_.empty() &&
+      previous_id == audio_focus_stack_.back()->id() &&
+      audio_focus_stack_.back()->audio_focus_type() == type &&
+      audio_focus_stack_.back()->IsActive()) {
+    // Early returning if |media_session| is already on top (has focus) and is
+    // active.
+    return AudioFocusManager::RequestResponse(previous_id.value(), true);
+  }
+
+  // If we have a previous ID then we should remove it from the stack.
+  if (previous_id.has_value())
+    RemoveFocusEntryIfPresent(previous_id.value());
+
+  if (type == mojom::AudioFocusType::kGainTransientMayDuck) {
+    for (auto& old_session : audio_focus_stack_)
+      old_session->session()->StartDucking();
+  } else {
+    for (auto& old_session : audio_focus_stack_) {
+      // If the session has the force duck bit set then we should duck the
+      // session instead of suspending it.
+      if (old_session->info()->force_duck) {
+        old_session->session()->StartDucking();
+      } else {
+        old_session->session()->Suspend(
+            mojom::MediaSession::SuspendType::kSystem);
+      }
+    }
+  }
+
+  audio_focus_stack_.push_back(std::make_unique<AudioFocusManager::StackRow>(
+      std::move(media_session), std::move(session_info), type,
+      previous_id ? *previous_id : GenerateAudioFocusRequestId()));
+
+  AudioFocusManager::StackRow* row = audio_focus_stack_.back().get();
+  row->session()->StopDucking();
+
+  // Notify observers that we were gained audio focus.
+  observers_.ForAllPtrs([&row, type](mojom::AudioFocusObserver* observer) {
+    observer->OnFocusGained(row->info().Clone(), type);
+  });
+
+  // We always grant the audio focus request but this may not always be the case
+  // in the future.
+  return AudioFocusManager::RequestResponse(row->id(), true);
+}
+
+void AudioFocusManager::AbandonAudioFocus(RequestId id) {
+  if (audio_focus_stack_.empty())
+    return;
+
+  if (audio_focus_stack_.back()->id() != id) {
+    RemoveFocusEntryIfPresent(id);
+    return;
+  }
+
+  auto row = std::move(audio_focus_stack_.back());
+  audio_focus_stack_.pop_back();
+
+  if (audio_focus_stack_.empty()) {
+    // Notify observers that we lost audio focus.
+    observers_.ForAllPtrs([&row](mojom::AudioFocusObserver* observer) {
+      observer->OnFocusLost(row->info().Clone());
+    });
+    return;
+  }
+
+  // Allow the top-most MediaSession having force duck to unduck even if
+  // it is not active.
+  for (auto iter = audio_focus_stack_.rbegin();
+       iter != audio_focus_stack_.rend(); ++iter) {
+    if (!(*iter)->info()->force_duck)
+      continue;
+
+    auto duck_row = std::move(*iter);
+    duck_row->session()->StopDucking();
+    audio_focus_stack_.erase(std::next(iter).base());
+    audio_focus_stack_.push_back(std::move(duck_row));
+    return;
+  }
+
+  // Only try to unduck the new MediaSession on top. The session might be
+  // still inactive but it will not be resumed (so it doesn't surprise the
+  // user).
+  audio_focus_stack_.back()->session()->StopDucking();
+
+  // Notify observers that we lost audio focus.
+  observers_.ForAllPtrs([&row](mojom::AudioFocusObserver* observer) {
+    observer->OnFocusLost(row->info().Clone());
+  });
+}
+
+mojo::InterfacePtrSetElementId AudioFocusManager::AddObserver(
+    mojom::AudioFocusObserverPtr observer) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  return observers_.AddPtr(std::move(observer));
+}
+
+mojom::AudioFocusType AudioFocusManager::GetFocusTypeForSession(RequestId id) {
+  for (auto& row : audio_focus_stack_) {
+    if (row->id() == id)
+      return row->audio_focus_type();
+  }
+
+  NOTREACHED();
+  return mojom::AudioFocusType::kGain;
+}
+
+void AudioFocusManager::RemoveObserver(mojo::InterfacePtrSetElementId id) {
+  observers_.RemovePtr(id);
+}
+
+void AudioFocusManager::ResetForTesting() {
+  audio_focus_stack_.clear();
+  observers_.CloseAll();
+}
+
+void AudioFocusManager::FlushForTesting() {
+  observers_.FlushForTesting();
+
+  for (auto& session : audio_focus_stack_)
+    session->FlushForTesting();
+}
+
+AudioFocusManager::AudioFocusManager() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+AudioFocusManager::~AudioFocusManager() = default;
+
+void AudioFocusManager::RemoveFocusEntryIfPresent(
+    AudioFocusManager::RequestId id) {
+  for (auto iter = audio_focus_stack_.begin(); iter != audio_focus_stack_.end();
+       ++iter) {
+    if ((*iter)->id() == id) {
+      audio_focus_stack_.erase(iter);
+      break;
+    }
+  }
+}
+
+AudioFocusManager::StackRow::StackRow(mojom::MediaSessionPtr session,
+                                      mojom::MediaSessionInfoPtr current_info,
+                                      mojom::AudioFocusType audio_focus_type,
+                                      AudioFocusManager::RequestId id)
+    : id_(id),
+      session_(std::move(session)),
+      current_info_(std::move(current_info)),
+      audio_focus_type_(audio_focus_type),
+      binding_(this) {
+  mojom::MediaSessionObserverPtr observer;
+  binding_.Bind(mojo::MakeRequest(&observer));
+
+  // Listen for mojo errors.
+  binding_.set_connection_error_handler(base::BindOnce(
+      &AudioFocusManager::StackRow::OnConnectionError, base::Unretained(this)));
+  session_.set_connection_error_handler(base::BindOnce(
+      &AudioFocusManager::StackRow::OnConnectionError, base::Unretained(this)));
+
+  // Listen to info changes on the MediaSession.
+  session_->AddObserver(std::move(observer));
+};
+
+AudioFocusManager::StackRow::~StackRow() = default;
+
+void AudioFocusManager::StackRow::MediaSessionInfoChanged(
+    mojom::MediaSessionInfoPtr info) {
+  current_info_ = std::move(info);
+}
+
+mojom::MediaSession* AudioFocusManager::StackRow::session() {
+  return session_.get();
+}
+
+const mojom::MediaSessionInfoPtr& AudioFocusManager::StackRow::info() const {
+  return current_info_;
+}
+
+bool AudioFocusManager::StackRow::IsActive() const {
+  return current_info_->state == mojom::MediaSessionInfo::SessionState::kActive;
+}
+
+void AudioFocusManager::StackRow::FlushForTesting() {
+  session_.FlushForTesting();
+  binding_.FlushForTesting();
+}
+
+void AudioFocusManager::StackRow::OnConnectionError() {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&AudioFocusManager::AbandonAudioFocus,
+                     base::Unretained(AudioFocusManager::GetInstance()), id()));
+}
+
+}  // namespace media_session
diff --git a/services/media_session/audio_focus_manager.h b/services/media_session/audio_focus_manager.h
new file mode 100644
index 0000000..048d0b64
--- /dev/null
+++ b/services/media_session/audio_focus_manager.h
@@ -0,0 +1,122 @@
+// 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 SERVICES_MEDIA_SESSION_AUDIO_FOCUS_MANAGER_H_
+#define SERVICES_MEDIA_SESSION_AUDIO_FOCUS_MANAGER_H_
+
+#include <list>
+#include <unordered_map>
+
+#include "base/memory/singleton.h"
+#include "base/threading/thread_checker.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/bindings/interface_ptr_set.h"
+#include "services/media_session/public/mojom/audio_focus.mojom.h"
+
+namespace media_session {
+
+class AudioFocusManager {
+ public:
+  using RequestId = int;
+  using RequestResponse = std::pair<RequestId, bool>;
+
+  // Returns Chromium's internal AudioFocusManager.
+  static AudioFocusManager* GetInstance();
+
+  // Requests audio focus with |type| for the |media_session| with
+  // |session_info|. The function will return a |RequestResponse| which contains
+  // a |RequestId| and a boolean as to whether the request was successful. If a
+  // session wishes to request a different focus type it should provide its
+  // previous request id as |previous_id|.
+  RequestResponse RequestAudioFocus(mojom::MediaSessionPtr media_session,
+                                    mojom::MediaSessionInfoPtr session_info,
+                                    mojom::AudioFocusType type,
+                                    base::Optional<RequestId> previous_id);
+
+  // Abandons audio focus for a request with |id|. The next request on top of
+  // the stack will be granted audio focus.
+  void AbandonAudioFocus(RequestId id);
+
+  // Returns the current audio focus type for a request with |id|.
+  mojom::AudioFocusType GetFocusTypeForSession(RequestId id);
+
+  // Adds/removes audio focus observers.
+  mojo::InterfacePtrSetElementId AddObserver(mojom::AudioFocusObserverPtr);
+  void RemoveObserver(mojo::InterfacePtrSetElementId);
+
+ private:
+  friend struct base::DefaultSingletonTraits<AudioFocusManager>;
+  friend class AudioFocusManagerTest;
+
+  // Media internals UI needs access to internal state.
+  friend class MediaInternalsAudioFocusTest;
+  friend class MediaInternals;
+
+  // Flush for testing will flush any pending messages to the observers.
+  void FlushForTesting();
+
+  // Reset for testing will clear any built up internal state.
+  void ResetForTesting();
+
+  AudioFocusManager();
+  ~AudioFocusManager();
+
+  void RemoveFocusEntryIfPresent(RequestId id);
+
+  // Weak reference of managed observers. Observers are expected to remove
+  // themselves before being destroyed.
+  mojo::InterfacePtrSet<mojom::AudioFocusObserver> observers_;
+
+  // StackRow is a MediaSessionObserver and holds a cached copy of the latest
+  // MediaSessionInfo associated with the MediaSession. By keeping the info
+  // cached and readily available we can make audio focus decisions quickly
+  // without waiting on MediaSessions.
+  class StackRow : public mojom::MediaSessionObserver {
+   public:
+    StackRow(mojom::MediaSessionPtr session,
+             mojom::MediaSessionInfoPtr current_info,
+             mojom::AudioFocusType audio_focus_type,
+             RequestId id);
+    ~StackRow() override;
+
+    // mojom::MediaSessionObserver.
+    void MediaSessionInfoChanged(mojom::MediaSessionInfoPtr info) override;
+
+    mojom::MediaSession* session();
+    const mojom::MediaSessionInfoPtr& info() const;
+    mojom::AudioFocusType audio_focus_type() const { return audio_focus_type_; }
+
+    bool IsActive() const;
+    int id() { return id_; }
+
+    // Flush any pending mojo messages for testing.
+    void FlushForTesting();
+
+   private:
+    void OnConnectionError();
+
+    int id_;
+    mojom::MediaSessionPtr session_;
+    mojom::MediaSessionInfoPtr current_info_;
+    mojom::AudioFocusType audio_focus_type_;
+    mojo::Binding<mojom::MediaSessionObserver> binding_;
+
+    DISALLOW_COPY_AND_ASSIGN(StackRow);
+  };
+
+  // A stack of Mojo interface pointers and their requested audio focus type.
+  // A MediaSession must abandon audio focus before its destruction.
+  std::list<std::unique_ptr<StackRow>> audio_focus_stack_;
+
+  // Adding observers should happen on the same thread that the service is
+  // running on.
+  THREAD_CHECKER(thread_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(AudioFocusManager);
+};
+
+}  // namespace media_session
+
+#endif  // SERVICES_MEDIA_SESSION_AUDIO_FOCUS_MANAGER_H_
diff --git a/services/media_session/audio_focus_manager_unittest.cc b/services/media_session/audio_focus_manager_unittest.cc
new file mode 100644
index 0000000..98edc53
--- /dev/null
+++ b/services/media_session/audio_focus_manager_unittest.cc
@@ -0,0 +1,573 @@
+// 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 "services/media_session/audio_focus_manager.h"
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "services/media_session/public/cpp/test/audio_focus_test_util.h"
+#include "services/media_session/public/mojom/audio_focus.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media_session {
+
+namespace {
+
+static const base::Optional<AudioFocusManager::RequestId> kNoRequestId;
+
+static const AudioFocusManager::RequestId kNoFocusedSession = -1;
+
+class MockMediaSession : public mojom::MediaSession {
+ public:
+  MockMediaSession() = default;
+  explicit MockMediaSession(bool force_duck) : force_duck_(force_duck) {}
+
+  ~MockMediaSession() override {}
+
+  void Suspend(SuspendType suspend_type) override {
+    DCHECK_EQ(SuspendType::kSystem, suspend_type);
+    SetState(mojom::MediaSessionInfo::SessionState::kSuspended);
+  }
+
+  void StartDucking() override {
+    is_ducking_ = true;
+    NotifyObservers();
+  }
+
+  void StopDucking() override {
+    is_ducking_ = false;
+    NotifyObservers();
+  }
+
+  void GetMediaSessionInfo(GetMediaSessionInfoCallback callback) override {
+    std::move(callback).Run(GetSessionInfoSync());
+  }
+
+  void AddObserver(mojom::MediaSessionObserverPtr observer) override {
+    observers_.AddPtr(std::move(observer));
+  }
+
+  void GetDebugInfo(GetDebugInfoCallback callback) override {}
+
+  void BindToMojoRequest(mojo::InterfaceRequest<mojom::MediaSession> request) {
+    bindings_.AddBinding(this, std::move(request));
+  }
+
+  void SetState(mojom::MediaSessionInfo::SessionState state) {
+    state_ = state;
+    NotifyObservers();
+  }
+
+  bool has_observers() const { return !observers_.empty(); }
+
+  void CloseAllObservers() { observers_.CloseAll(); }
+
+ private:
+  mojom::MediaSessionInfoPtr GetSessionInfoSync() {
+    mojom::MediaSessionInfoPtr info(mojom::MediaSessionInfo::New());
+    info->force_duck = force_duck_;
+    info->state = state_;
+    if (is_ducking_)
+      info->state = mojom::MediaSessionInfo::SessionState::kDucking;
+    return info;
+  }
+
+  void NotifyObservers() {
+    observers_.ForAllPtrs([this](mojom::MediaSessionObserver* observer) {
+      observer->MediaSessionInfoChanged(GetSessionInfoSync());
+    });
+
+    // This will flush all pending async messages on the observers.
+    observers_.FlushForTesting();
+  }
+
+  const bool force_duck_ = false;
+  bool is_ducking_ = false;
+  mojom::MediaSessionInfo::SessionState state_ =
+      mojom::MediaSessionInfo::SessionState::kInactive;
+
+  mojo::InterfacePtrSet<mojom::MediaSessionObserver> observers_;
+  mojo::BindingSet<mojom::MediaSession> bindings_;
+};
+
+}  // anonymous namespace
+
+class AudioFocusManagerTest : public testing::Test {
+ public:
+  AudioFocusManagerTest() = default;
+
+  void SetUp() override {
+    // AudioFocusManager is a singleton so we should make sure we reset any
+    // state in between tests.
+    AudioFocusManager::GetInstance()->ResetForTesting();
+  }
+
+  void TearDown() override {
+    // Run pending tasks.
+    base::RunLoop().RunUntilIdle();
+  }
+
+  AudioFocusManager::RequestId GetAudioFocusedSession() const {
+    const AudioFocusManager* manager = AudioFocusManager::GetInstance();
+    const auto& audio_focus_stack = manager->audio_focus_stack_;
+
+    for (auto iter = audio_focus_stack.rbegin();
+         iter != audio_focus_stack.rend(); ++iter) {
+      if ((*iter)->audio_focus_type() == mojom::AudioFocusType::kGain)
+        return (*iter)->id();
+    }
+    return kNoFocusedSession;
+  }
+
+  int GetTransientMaybeDuckCount() const {
+    const AudioFocusManager* manager = AudioFocusManager::GetInstance();
+    const auto& audio_focus_stack = manager->audio_focus_stack_;
+    int count = 0;
+
+    for (auto iter = audio_focus_stack.rbegin();
+         iter != audio_focus_stack.rend(); ++iter) {
+      if ((*iter)->audio_focus_type() ==
+          mojom::AudioFocusType::kGainTransientMayDuck)
+        ++count;
+      else
+        break;
+    }
+
+    return count;
+  }
+
+  void AbandonAudioFocus(AudioFocusManager::RequestId id) {
+    AudioFocusManager::GetInstance()->AbandonAudioFocus(id);
+    FlushForTesting();
+  }
+
+  AudioFocusManager::RequestId RequestAudioFocus(
+      MockMediaSession* session,
+      mojom::AudioFocusType audio_focus_type) {
+    return RequestAudioFocus(session, audio_focus_type, kNoRequestId);
+  }
+
+  AudioFocusManager::RequestId RequestAudioFocus(
+      MockMediaSession* session,
+      mojom::AudioFocusType audio_focus_type,
+      base::Optional<AudioFocusManager::RequestId> previous_id) {
+    mojom::MediaSessionPtr media_session;
+    session->BindToMojoRequest(mojo::MakeRequest(&media_session));
+
+    AudioFocusManager::RequestResponse response =
+        AudioFocusManager::GetInstance()->RequestAudioFocus(
+            std::move(media_session), test::GetMediaSessionInfoSync(session),
+            audio_focus_type, previous_id);
+
+    // If the audio focus was granted then we should set the session state to
+    // active.
+    if (response.second)
+      session->SetState(mojom::MediaSessionInfo::SessionState::kActive);
+
+    FlushForTesting();
+    return response.first;
+  }
+
+  mojom::MediaSessionInfo::SessionState GetState(MockMediaSession* session) {
+    return test::GetMediaSessionInfoSync(session)->state;
+  }
+
+  std::unique_ptr<test::TestAudioFocusObserver> CreateObserver() {
+    std::unique_ptr<test::TestAudioFocusObserver> observer =
+        std::make_unique<test::TestAudioFocusObserver>();
+
+    mojom::AudioFocusObserverPtr observer_ptr;
+    observer->BindToMojoRequest(mojo::MakeRequest(&observer_ptr));
+    AudioFocusManager::GetInstance()->AddObserver(std::move(observer_ptr));
+
+    return observer;
+  }
+
+ private:
+  void FlushForTesting() {
+    AudioFocusManager::GetInstance()->FlushForTesting();
+  }
+
+  base::test::ScopedTaskEnvironment task_environment_;
+
+  DISALLOW_COPY_AND_ASSIGN(AudioFocusManagerTest);
+};
+
+TEST_F(AudioFocusManagerTest, InstanceAvailableAndSame) {
+  AudioFocusManager* audio_focus_manager = AudioFocusManager::GetInstance();
+  EXPECT_TRUE(!!audio_focus_manager);
+  EXPECT_EQ(audio_focus_manager, AudioFocusManager::GetInstance());
+}
+
+TEST_F(AudioFocusManagerTest, AddObserverOnRequest) {
+  MockMediaSession media_session_1;
+  EXPECT_FALSE(media_session_1.has_observers());
+
+  RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain,
+                    kNoRequestId);
+  EXPECT_TRUE(media_session_1.has_observers());
+}
+
+TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_ReplaceFocusedEntry) {
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
+  MockMediaSession media_session_3;
+
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kInactive,
+            GetState(&media_session_1));
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kInactive,
+            GetState(&media_session_2));
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kInactive,
+            GetState(&media_session_3));
+
+  AudioFocusManager::RequestId request_id_1 =
+      RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
+  EXPECT_EQ(request_id_1, GetAudioFocusedSession());
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
+            GetState(&media_session_1));
+
+  AudioFocusManager::RequestId request_id_2 =
+      RequestAudioFocus(&media_session_2, mojom::AudioFocusType::kGain);
+  EXPECT_EQ(request_id_2, GetAudioFocusedSession());
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kSuspended,
+            GetState(&media_session_1));
+
+  AudioFocusManager::RequestId request_id_3 =
+      RequestAudioFocus(&media_session_3, mojom::AudioFocusType::kGain);
+  EXPECT_EQ(request_id_3, GetAudioFocusedSession());
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kSuspended,
+            GetState(&media_session_2));
+}
+
+TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_Duplicate) {
+  MockMediaSession media_session;
+
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+
+  AudioFocusManager::RequestId request_id =
+      RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
+  EXPECT_EQ(request_id, GetAudioFocusedSession());
+
+  RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain, request_id);
+  EXPECT_EQ(request_id, GetAudioFocusedSession());
+}
+
+TEST_F(AudioFocusManagerTest, RequestAudioFocusGain_FromTransient) {
+  MockMediaSession media_session;
+
+  AudioFocusManager::RequestId request_id = RequestAudioFocus(
+      &media_session, mojom::AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
+
+  RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain, request_id);
+  EXPECT_EQ(request_id, GetAudioFocusedSession());
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
+}
+
+TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGain) {
+  MockMediaSession media_session;
+
+  AudioFocusManager::RequestId request_id =
+      RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
+  EXPECT_EQ(request_id, GetAudioFocusedSession());
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
+
+  RequestAudioFocus(&media_session,
+                    mojom::AudioFocusType::kGainTransientMayDuck, request_id);
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
+  EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session));
+}
+
+TEST_F(AudioFocusManagerTest, RequestAudioFocusTransient_FromGainWhileDucking) {
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
+
+  AudioFocusManager::RequestId request_id =
+      RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
+            GetState(&media_session_1));
+
+  RequestAudioFocus(&media_session_2,
+                    mojom::AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+
+  RequestAudioFocus(&media_session_1,
+                    mojom::AudioFocusType::kGainTransientMayDuck, request_id);
+  EXPECT_EQ(2, GetTransientMaybeDuckCount());
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
+            GetState(&media_session_1));
+}
+
+TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesFocusedEntry) {
+  MockMediaSession media_session;
+
+  AudioFocusManager::RequestId request_id =
+      RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
+  EXPECT_EQ(request_id, GetAudioFocusedSession());
+
+  AbandonAudioFocus(request_id);
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+}
+
+TEST_F(AudioFocusManagerTest, AbandonAudioFocus_NoAssociatedEntry) {
+  AbandonAudioFocus(kNoFocusedSession);
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+}
+
+TEST_F(AudioFocusManagerTest, AbandonAudioFocus_RemovesTransientEntry) {
+  MockMediaSession media_session;
+
+  AudioFocusManager::RequestId request_id = RequestAudioFocus(
+      &media_session, mojom::AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
+
+  {
+    std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
+    AbandonAudioFocus(request_id);
+
+    EXPECT_EQ(0, GetTransientMaybeDuckCount());
+    EXPECT_TRUE(observer->focus_lost_session_.Equals(
+        test::GetMediaSessionInfoSync(&media_session)));
+  }
+}
+
+TEST_F(AudioFocusManagerTest, AbandonAudioFocus_WhileDuckingThenResume) {
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
+
+  AudioFocusManager::RequestId request_id_1 =
+      RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
+  EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+
+  AudioFocusManager::RequestId request_id_2 = RequestAudioFocus(
+      &media_session_2, mojom::AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+
+  AbandonAudioFocus(request_id_1);
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
+
+  AbandonAudioFocus(request_id_2);
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
+
+  RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
+  EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+}
+
+TEST_F(AudioFocusManagerTest, AbandonAudioFocus_StopsDucking) {
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
+
+  RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
+  EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+
+  AudioFocusManager::RequestId request_id_2 = RequestAudioFocus(
+      &media_session_2, mojom::AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(1, GetTransientMaybeDuckCount());
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+
+  AbandonAudioFocus(request_id_2);
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
+  EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+}
+
+TEST_F(AudioFocusManagerTest, DuckWhilePlaying) {
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
+
+  RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
+  EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+
+  RequestAudioFocus(&media_session_2,
+                    mojom::AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+}
+
+TEST_F(AudioFocusManagerTest, GainSuspendsTransient) {
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
+
+  RequestAudioFocus(&media_session_2,
+                    mojom::AudioFocusType::kGainTransientMayDuck);
+
+  RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kSuspended,
+            GetState(&media_session_2));
+}
+
+TEST_F(AudioFocusManagerTest, DuckWithMultipleTransients) {
+  MockMediaSession media_session_1;
+  MockMediaSession media_session_2;
+  MockMediaSession media_session_3;
+
+  RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
+  EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+
+  AudioFocusManager::RequestId request_id_2 = RequestAudioFocus(
+      &media_session_2, mojom::AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+
+  AudioFocusManager::RequestId request_id_3 = RequestAudioFocus(
+      &media_session_3, mojom::AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+
+  AbandonAudioFocus(request_id_2);
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+
+  AbandonAudioFocus(request_id_3);
+  EXPECT_NE(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+}
+
+TEST_F(AudioFocusManagerTest, MediaSessionDestroyed_ReleasesFocus) {
+  {
+    MockMediaSession media_session;
+
+    AudioFocusManager::RequestId request_id =
+        RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
+    EXPECT_EQ(request_id, GetAudioFocusedSession());
+  }
+
+  // If the media session is destroyed without abandoning audio focus we do not
+  // know until we next interact with the manager.
+  MockMediaSession media_session;
+  RequestAudioFocus(&media_session,
+                    mojom::AudioFocusType::kGainTransientMayDuck);
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+}
+
+TEST_F(AudioFocusManagerTest, MediaSessionDestroyed_ReleasesTransients) {
+  {
+    MockMediaSession media_session;
+    RequestAudioFocus(&media_session,
+                      mojom::AudioFocusType::kGainTransientMayDuck);
+    EXPECT_EQ(1, GetTransientMaybeDuckCount());
+  }
+
+  // If the media session is destroyed without abandoning audio focus we do not
+  // know until we next interact with the manager.
+  MockMediaSession media_session;
+  RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
+  EXPECT_EQ(0, GetTransientMaybeDuckCount());
+}
+
+TEST_F(AudioFocusManagerTest, GainDucksForceDuck) {
+  MockMediaSession media_session_1(true /* force_duck */);
+  MockMediaSession media_session_2;
+
+  RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
+
+  AudioFocusManager::RequestId request_id_2 =
+      RequestAudioFocus(&media_session_2, mojom::AudioFocusType::kGain);
+
+  EXPECT_EQ(request_id_2, GetAudioFocusedSession());
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+}
+
+TEST_F(AudioFocusManagerTest,
+       AbandoningGainFocusRevokesTopMostForceDuckSession) {
+  MockMediaSession media_session_1(true /* force_duck */);
+  MockMediaSession media_session_2;
+  MockMediaSession media_session_3;
+
+  AudioFocusManager::RequestId request_id_1 =
+      RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
+  RequestAudioFocus(&media_session_2, mojom::AudioFocusType::kGain);
+
+  AudioFocusManager::RequestId request_id_3 =
+      RequestAudioFocus(&media_session_3, mojom::AudioFocusType::kGain);
+  EXPECT_EQ(request_id_3, GetAudioFocusedSession());
+
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kSuspended,
+            GetState(&media_session_2));
+  EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kDucking,
+            GetState(&media_session_1));
+
+  AbandonAudioFocus(request_id_3);
+  EXPECT_EQ(request_id_1, GetAudioFocusedSession());
+}
+
+TEST_F(AudioFocusManagerTest, AudioFocusObserver_AbandonNoop) {
+  std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
+  AbandonAudioFocus(kNoFocusedSession);
+
+  EXPECT_EQ(kNoFocusedSession, GetAudioFocusedSession());
+  EXPECT_TRUE(observer->focus_lost_session_.is_null());
+}
+
+TEST_F(AudioFocusManagerTest, AudioFocusObserver_RequestNoop) {
+  MockMediaSession media_session;
+  AudioFocusManager::RequestId request_id;
+
+  {
+    std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
+    request_id =
+        RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain);
+
+    EXPECT_EQ(request_id, GetAudioFocusedSession());
+    EXPECT_EQ(mojom::AudioFocusType::kGain, observer->focus_gained_type());
+    EXPECT_FALSE(observer->focus_gained_session_.is_null());
+  }
+
+  {
+    std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
+    RequestAudioFocus(&media_session, mojom::AudioFocusType::kGain, request_id);
+
+    EXPECT_EQ(request_id, GetAudioFocusedSession());
+    EXPECT_TRUE(observer->focus_gained_session_.is_null());
+  }
+}
+
+TEST_F(AudioFocusManagerTest, AudioFocusObserver_TransientMayDuck) {
+  MockMediaSession media_session;
+  AudioFocusManager::RequestId request_id;
+
+  {
+    std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
+    request_id = RequestAudioFocus(
+        &media_session, mojom::AudioFocusType::kGainTransientMayDuck);
+
+    EXPECT_EQ(1, GetTransientMaybeDuckCount());
+    EXPECT_EQ(mojom::AudioFocusType::kGainTransientMayDuck,
+              observer->focus_gained_type());
+    EXPECT_FALSE(observer->focus_gained_session_.is_null());
+  }
+
+  {
+    std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
+    AbandonAudioFocus(request_id);
+
+    EXPECT_EQ(0, GetTransientMaybeDuckCount());
+    EXPECT_TRUE(observer->focus_lost_session_.Equals(
+        test::GetMediaSessionInfoSync(&media_session)));
+  }
+}
+
+}  // namespace media_session
diff --git a/services/media_session/public/cpp/test/BUILD.gn b/services/media_session/public/cpp/test/BUILD.gn
new file mode 100644
index 0000000..2b515f0
--- /dev/null
+++ b/services/media_session/public/cpp/test/BUILD.gn
@@ -0,0 +1,19 @@
+# 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.
+
+component("test_support") {
+  output_name = "media_session_test_support_cpp"
+
+  sources = [
+    "audio_focus_test_util.cc",
+    "audio_focus_test_util.h",
+  ]
+
+  deps = [
+    "//base",
+    "//services/media_session/public/mojom",
+  ]
+
+  defines = [ "IS_MEDIA_SESSION_TEST_SUPPORT_CPP_IMPL" ]
+}
diff --git a/services/media_session/public/cpp/test/audio_focus_test_util.cc b/services/media_session/public/cpp/test/audio_focus_test_util.cc
new file mode 100644
index 0000000..2361d67
--- /dev/null
+++ b/services/media_session/public/cpp/test/audio_focus_test_util.cc
@@ -0,0 +1,76 @@
+// 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 "services/media_session/public/cpp/test/audio_focus_test_util.h"
+
+namespace media_session {
+namespace test {
+
+namespace {
+
+void ReceivedSessionInfo(media_session::mojom::MediaSessionInfoPtr* info_out,
+                         base::RepeatingClosure callback,
+                         media_session::mojom::MediaSessionInfoPtr result) {
+  *info_out = std::move(result);
+  std::move(callback).Run();
+}
+
+}  // namespace
+
+TestAudioFocusObserver::TestAudioFocusObserver() : binding_(this) {}
+
+TestAudioFocusObserver::~TestAudioFocusObserver() = default;
+
+void TestAudioFocusObserver::OnFocusGained(
+    media_session::mojom::MediaSessionInfoPtr session,
+    media_session::mojom::AudioFocusType type) {
+  focus_gained_type_ = type;
+  focus_gained_session_ = std::move(session);
+
+  if (wait_for_gained_)
+    run_loop_.Quit();
+}
+
+void TestAudioFocusObserver::OnFocusLost(
+    media_session::mojom::MediaSessionInfoPtr session) {
+  focus_lost_session_ = std::move(session);
+
+  if (wait_for_lost_)
+    run_loop_.Quit();
+}
+
+void TestAudioFocusObserver::WaitForGainedEvent() {
+  if (!focus_gained_session_.is_null())
+    return;
+
+  wait_for_gained_ = true;
+  run_loop_.Run();
+}
+
+void TestAudioFocusObserver::WaitForLostEvent() {
+  if (!focus_lost_session_.is_null())
+    return;
+
+  wait_for_lost_ = true;
+  run_loop_.Run();
+}
+
+void TestAudioFocusObserver::BindToMojoRequest(
+    media_session::mojom::AudioFocusObserverRequest request) {
+  binding_.Bind(std::move(request));
+}
+
+media_session::mojom::MediaSessionInfoPtr GetMediaSessionInfoSync(
+    media_session::mojom::MediaSession* session) {
+  media_session::mojom::MediaSessionInfoPtr session_info;
+  base::RunLoop run_loop;
+
+  session->GetMediaSessionInfo(base::BindOnce(
+      &ReceivedSessionInfo, &session_info, run_loop.QuitClosure()));
+
+  return session_info;
+}
+
+}  // namespace test
+}  // namespace media_session
diff --git a/services/media_session/public/cpp/test/audio_focus_test_util.h b/services/media_session/public/cpp/test/audio_focus_test_util.h
new file mode 100644
index 0000000..01d120f
--- /dev/null
+++ b/services/media_session/public/cpp/test/audio_focus_test_util.h
@@ -0,0 +1,61 @@
+// 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 SERVICES_MEDIA_SESSION_PUBLIC_CPP_TEST_AUDIO_FOCUS_TEST_UTIL_H_
+#define SERVICES_MEDIA_SESSION_PUBLIC_CPP_TEST_AUDIO_FOCUS_TEST_UTIL_H_
+
+#include "base/component_export.h"
+#include "base/optional.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "services/media_session/public/mojom/audio_focus.mojom.h"
+
+namespace media_session {
+namespace test {
+
+class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) TestAudioFocusObserver
+    : public mojom::AudioFocusObserver {
+ public:
+  TestAudioFocusObserver();
+  ~TestAudioFocusObserver() override;
+
+  void OnFocusGained(media_session::mojom::MediaSessionInfoPtr,
+                     media_session::mojom::AudioFocusType) override;
+
+  void OnFocusLost(media_session::mojom::MediaSessionInfoPtr) override;
+
+  void WaitForGainedEvent();
+  void WaitForLostEvent();
+
+  media_session::mojom::AudioFocusType focus_gained_type() const {
+    DCHECK(!focus_gained_session_.is_null());
+    return focus_gained_type_;
+  }
+
+  void BindToMojoRequest(media_session::mojom::AudioFocusObserverRequest);
+
+  // These store the values we received.
+  media_session::mojom::MediaSessionInfoPtr focus_gained_session_;
+  media_session::mojom::MediaSessionInfoPtr focus_lost_session_;
+
+ private:
+  mojo::Binding<mojom::AudioFocusObserver> binding_;
+  media_session::mojom::AudioFocusType focus_gained_type_;
+
+  // If either of these are true we will quit the run loop if we observe a gain
+  // or lost event.
+  bool wait_for_gained_ = false;
+  bool wait_for_lost_ = false;
+
+  base::RunLoop run_loop_;
+};
+
+COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP)
+media_session::mojom::MediaSessionInfoPtr GetMediaSessionInfoSync(
+    media_session::mojom::MediaSession*);
+
+}  // namespace test
+}  // namespace media_session
+
+#endif  // SERVICES_MEDIA_SESSION_PUBLIC_CPP_TEST_AUDIO_FOCUS_TEST_UTIL_H_
diff --git a/services/media_session/public/mojom/audio_focus.mojom b/services/media_session/public/mojom/audio_focus.mojom
index d6d88c72..5bf2ce5 100644
--- a/services/media_session/public/mojom/audio_focus.mojom
+++ b/services/media_session/public/mojom/audio_focus.mojom
@@ -21,8 +21,8 @@
 // The observer for audio focus events.
 interface AudioFocusObserver {
   // The given |session| gained audio focus with the specified |type|.
-  OnFocusGained(MediaSession session, AudioFocusType type);
+  OnFocusGained(MediaSessionInfo session, AudioFocusType type);
 
   // The given |session| lost audio focus.
-  OnFocusLost(MediaSession session);
+  OnFocusLost(MediaSessionInfo session);
 };
diff --git a/services/media_session/public/mojom/media_session.mojom b/services/media_session/public/mojom/media_session.mojom
index 5624599d..9a156c8 100644
--- a/services/media_session/public/mojom/media_session.mojom
+++ b/services/media_session/public/mojom/media_session.mojom
@@ -4,8 +4,79 @@
 
 module media_session.mojom;
 
+// Contains state information about a MediaSession.
+struct MediaSessionInfo {
+  enum SessionState {
+    // The MediaSession is currently playing media.
+    kActive,
+
+    // The MediaSession is currently playing at a reduced volume (ducking).
+    kDucking,
+
+    // The MediaSession is currently paused.
+    kSuspended,
+
+    // The MediaSession is not currently playing media.
+    kInactive,
+  };
+
+  // The current state of the MediaSession.
+  SessionState state;
+
+  // If true then we will always duck this MediaSession instead of suspending.
+  bool force_duck;
+};
+
+// Contains debugging information about a MediaSession. This will be displayed
+// on the Media Internals WebUI.
+struct MediaSessionDebugInfo {
+  // A unique name for the MediaSession.
+  string name;
+
+  // The owner of the MediaSession.
+  string owner;
+
+  // State information stored in a string e.g. Ducked.
+  string state;
+};
+
+// The observer for observing media session events.
+interface MediaSessionObserver {
+  // The info associated with the session changed.
+  MediaSessionInfoChanged(MediaSessionInfo info);
+};
+
 // A MediaSession manages the media session and audio focus for a given
 // WebContents or ARC app.
 // TODO(https://crbug.com/875004): migrate media session from content/public
 // to mojo.
-interface MediaSession {};
+interface MediaSession {
+  enum SuspendType {
+    // Suspended by the system because a transient sound needs to be played.
+    kSystem,
+    // Suspended by the UI.
+    kUI,
+    // Suspended by the page via script or user interaction.
+    kContent,
+  };
+
+  // Returns information about the MediaSession.
+  GetMediaSessionInfo() => (MediaSessionInfo info);
+
+  // Returns debug information about the MediaSession.
+  GetDebugInfo() => (MediaSessionDebugInfo info);
+
+  // Let the media session start ducking such that the volume multiplier
+  // is reduced.
+  StartDucking();
+
+  // Let the media session stop ducking such that the volume multiplier is
+  // recovered.
+  StopDucking();
+
+  // Suspend the media session.
+  // |type| represents the origin of the request.
+  Suspend(SuspendType suspend_type);
+
+  AddObserver(MediaSessionObserver observer);
+};
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
index 49e56c59..818a624 100644
--- a/services/network/network_context_unittest.cc
+++ b/services/network/network_context_unittest.cc
@@ -3760,6 +3760,72 @@
             "Failed: FindProxyForURL(url=http://server.bad.dns/)");
 }
 
+// Test ensures that ProxyServer data is populated correctly across Mojo calls.
+// Basically it performs a set of URLLoader network requests, whose requests
+// configure proxies. Then it checks whether the expected proxy scheme is
+// respected.
+TEST_F(NetworkContextTest, EnsureProperProxyServerIsUsed) {
+  net::test_server::EmbeddedTestServer test_server;
+  test_server.AddDefaultHandlers(
+      base::FilePath(FILE_PATH_LITERAL("services/test/data")));
+  ASSERT_TRUE(test_server.Start());
+
+  struct ProxyConfigSet {
+    net::ProxyConfig proxy_config;
+    GURL url;
+    net::ProxyServer::Scheme expected_proxy_config_scheme;
+  } proxy_config_set[2];
+
+  proxy_config_set[0].proxy_config.proxy_rules().ParseFromString(
+      base::StringPrintf("http=%s",
+                         test_server.host_port_pair().ToString().c_str()));
+  // The domain here is irrelevant, and it is the path that matters.
+  proxy_config_set[0].url = GURL("http://does.not.matter/echo");
+  proxy_config_set[0].expected_proxy_config_scheme =
+      net::ProxyServer::SCHEME_HTTP;
+
+  proxy_config_set[1].proxy_config.proxy_rules().ParseFromString(
+      "http=direct://");
+  proxy_config_set[1].url = test_server.GetURL("/echo");
+  proxy_config_set[1].expected_proxy_config_scheme =
+      net::ProxyServer::SCHEME_DIRECT;
+
+  for (const auto& proxy_data : proxy_config_set) {
+    mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+    context_params->initial_proxy_config = net::ProxyConfigWithAnnotation(
+        proxy_data.proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS);
+    mojom::ProxyConfigClientPtr config_client;
+    context_params->proxy_config_client_request =
+        mojo::MakeRequest(&config_client);
+    std::unique_ptr<NetworkContext> network_context =
+        CreateContextWithParams(std::move(context_params));
+
+    mojom::URLLoaderFactoryPtr loader_factory;
+    mojom::URLLoaderFactoryParamsPtr params =
+        mojom::URLLoaderFactoryParams::New();
+    params->process_id = 0;
+    network_context->CreateURLLoaderFactory(mojo::MakeRequest(&loader_factory),
+                                            std::move(params));
+
+    ResourceRequest request;
+    // The domain here is irrelevant, and it is the path that matters.
+    request.url = proxy_data.url;  // test_server.GetURL("/echo");
+
+    mojom::URLLoaderPtr loader;
+    TestURLLoaderClient client;
+    loader_factory->CreateLoaderAndStart(
+        mojo::MakeRequest(&loader), 0 /* routing_id */, 0 /* request_id */,
+        0 /* options */, request, client.CreateInterfacePtr(),
+        net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+
+    client.RunUntilComplete();
+
+    EXPECT_TRUE(client.has_received_completion());
+    EXPECT_EQ(client.response_head().proxy_server.scheme(),
+              proxy_data.expected_proxy_config_scheme);
+  }
+}
+
 }  // namespace
 
 }  // namespace network
diff --git a/services/network/public/cpp/net_ipc_param_traits.cc b/services/network/public/cpp/net_ipc_param_traits.cc
index 79912a5..445809f0 100644
--- a/services/network/public/cpp/net_ipc_param_traits.cc
+++ b/services/network/public/cpp/net_ipc_param_traits.cc
@@ -222,6 +222,47 @@
   l->append("<HttpResponseHeaders>");
 }
 
+void ParamTraits<net::ProxyServer>::Write(base::Pickle* m,
+                                          const param_type& p) {
+  net::ProxyServer::Scheme scheme = p.scheme();
+  WriteParam(m, scheme);
+  // When scheme is either 'direct' or 'invalid' |host_port_pair|
+  // should not be called, as per the method implementation body.
+  if (scheme != net::ProxyServer::SCHEME_DIRECT &&
+      scheme != net::ProxyServer::SCHEME_INVALID) {
+    WriteParam(m, p.host_port_pair());
+  }
+  WriteParam(m, p.is_trusted_proxy());
+}
+
+bool ParamTraits<net::ProxyServer>::Read(const base::Pickle* m,
+                                         base::PickleIterator* iter,
+                                         param_type* r) {
+  net::ProxyServer::Scheme scheme;
+  bool is_trusted_proxy = false;
+  if (!ReadParam(m, iter, &scheme))
+    return false;
+
+  // When scheme is either 'direct' or 'invalid' |host_port_pair|
+  // should not be called, as per the method implementation body.
+  net::HostPortPair host_port_pair;
+  if (scheme != net::ProxyServer::SCHEME_DIRECT &&
+      scheme != net::ProxyServer::SCHEME_INVALID &&
+      !ReadParam(m, iter, &host_port_pair)) {
+    return false;
+  }
+
+  if (!ReadParam(m, iter, &is_trusted_proxy))
+    return false;
+
+  *r = net::ProxyServer(scheme, host_port_pair, is_trusted_proxy);
+  return true;
+}
+
+void ParamTraits<net::ProxyServer>::Log(const param_type& p, std::string* l) {
+  l->append("<ProxyServer>");
+}
+
 void ParamTraits<net::OCSPVerifyResult>::Write(base::Pickle* m,
                                                const param_type& p) {
   WriteParam(m, p.response_status);
diff --git a/services/network/public/cpp/net_ipc_param_traits.h b/services/network/public/cpp/net_ipc_param_traits.h
index 26b492c..9ae1082f 100644
--- a/services/network/public/cpp/net_ipc_param_traits.h
+++ b/services/network/public/cpp/net_ipc_param_traits.h
@@ -13,6 +13,7 @@
 #include "ipc/param_traits_macros.h"
 #include "net/base/auth.h"
 #include "net/base/host_port_pair.h"
+#include "net/base/proxy_server.h"
 #include "net/base/request_priority.h"
 #include "net/cert/cert_verify_result.h"
 #include "net/cert/ct_policy_status.h"
@@ -113,6 +114,16 @@
 };
 
 template <>
+struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ParamTraits<net::ProxyServer> {
+  typedef net::ProxyServer param_type;
+  static void Write(base::Pickle* m, const param_type& p);
+  static bool Read(const base::Pickle* m,
+                   base::PickleIterator* iter,
+                   param_type* r);
+  static void Log(const param_type& p, std::string* l);
+};
+
+template <>
 struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ParamTraits<net::OCSPVerifyResult> {
   typedef net::OCSPVerifyResult param_type;
   static void Write(base::Pickle* m, const param_type& p);
@@ -203,6 +214,9 @@
 IPC_ENUM_TRAITS_MAX_VALUE(
     net::ct::CTPolicyCompliance,
     net::ct::CTPolicyCompliance::CT_POLICY_COMPLIANCE_DETAILS_NOT_AVAILABLE)
+
+IPC_ENUM_TRAITS(net::ProxyServer::Scheme);  // BitMask.
+
 IPC_ENUM_TRAITS_MAX_VALUE(net::OCSPVerifyResult::ResponseStatus,
                           net::OCSPVerifyResult::PARSE_RESPONSE_DATA_ERROR)
 IPC_ENUM_TRAITS_MAX_VALUE(net::OCSPRevocationStatus,
diff --git a/services/network/public/cpp/network_ipc_param_traits.h b/services/network/public/cpp/network_ipc_param_traits.h
index 08b17f2..dcf4964 100644
--- a/services/network/public/cpp/network_ipc_param_traits.h
+++ b/services/network/public/cpp/network_ipc_param_traits.h
@@ -13,6 +13,7 @@
 #include "ipc/param_traits_macros.h"
 #include "net/base/auth.h"
 #include "net/base/host_port_pair.h"
+#include "net/base/proxy_server.h"
 #include "net/base/request_priority.h"
 #include "net/cert/cert_verify_result.h"
 #include "net/cert/ct_policy_status.h"
@@ -193,6 +194,7 @@
   IPC_STRUCT_TRAITS_MEMBER(alpn_negotiated_protocol)
   IPC_STRUCT_TRAITS_MEMBER(socket_address)
   IPC_STRUCT_TRAITS_MEMBER(was_fetched_via_cache)
+  IPC_STRUCT_TRAITS_MEMBER(proxy_server)
   IPC_STRUCT_TRAITS_MEMBER(was_fetched_via_service_worker)
   IPC_STRUCT_TRAITS_MEMBER(was_fallback_required_by_service_worker)
   IPC_STRUCT_TRAITS_MEMBER(url_list_via_service_worker)
diff --git a/services/network/public/cpp/resource_response_info.h b/services/network/public/cpp/resource_response_info.h
index e6b3b30..266a7bca 100644
--- a/services/network/public/cpp/resource_response_info.h
+++ b/services/network/public/cpp/resource_response_info.h
@@ -15,6 +15,7 @@
 #include "base/time/time.h"
 #include "net/base/host_port_pair.h"
 #include "net/base/load_timing_info.h"
+#include "net/base/proxy_server.h"
 #include "net/cert/ct_policy_status.h"
 #include "net/cert/signed_certificate_timestamp_and_status.h"
 #include "net/http/http_response_headers.h"
@@ -114,6 +115,9 @@
   // True if the response was delivered through a proxy.
   bool was_fetched_via_proxy;
 
+  // The proxy server used for this request, if any.
+  net::ProxyServer proxy_server;
+
   // True if the response was fetched by a ServiceWorker.
   bool was_fetched_via_service_worker;
 
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index 41bf21ac..765ac8f 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -322,11 +322,6 @@
   // (even as an opaque int) in //services/network.  See also the TODO comment
   // for network::ResourceRequest::resource_type.
   int32 corb_excluded_resource_type = -1;
-  // TODO(lukasza): https://crbug.com/846346: Replace the field below with a
-  // granular list of origins that content scripts can XHR into (based on
-  // extension manifest V3 / assumming that content scripts have a
-  // URLLoaderFactory separate from the rest of the renderer).
-  string corb_excluded_initiator_scheme;
 
   // True if web related security (e.g., CORS) should be disabled. This is
   // mainly used by people testing their sites, via a command line switch.
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index 1594e7e..1f85c070 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -68,6 +68,7 @@
   response->head.socket_address = response_info.socket_address;
   response->head.was_fetched_via_cache = request->was_cached();
   response->head.was_fetched_via_proxy = request->was_fetched_via_proxy();
+  response->head.proxy_server = request->proxy_server();
   response->head.network_accessed = response_info.network_accessed;
   response->head.async_revalidation_requested =
       response_info.async_revalidation_requested;
@@ -699,10 +700,7 @@
                  base::Unretained(this)));
 
   // Figure out if we need to sniff (for MIME type detection or for CORB).
-  if (factory_params_->is_corb_enabled && !is_nocors_corb_excluded_request_ &&
-      (factory_params_->corb_excluded_initiator_scheme.empty() ||
-       factory_params_->corb_excluded_initiator_scheme !=
-           url_request->initiator().value_or(url::Origin()).scheme())) {
+  if (factory_params_->is_corb_enabled && !is_nocors_corb_excluded_request_) {
     CrossOriginReadBlocking::LogAction(
         CrossOriginReadBlocking::Action::kResponseStarted);
 
diff --git a/services/ws/public/mojom/window_manager.mojom b/services/ws/public/mojom/window_manager.mojom
index 6a30b3b3..e0b2c8d 100644
--- a/services/ws/public/mojom/window_manager.mojom
+++ b/services/ws/public/mojom/window_manager.mojom
@@ -34,11 +34,6 @@
   // Type: int32_t.
   const string kContainerId_InitProperty = "init:container_id";
 
-  // Disables the window manager from handling immersive fullscreen for the
-  // window. This is typically done if the client wants to handle immersive
-  // themselves. Type: bool.
-  const string kDisableImmersive_InitProperty = "init:disable_immersive";
-
   // The id of the display (display::Display::id()) to create the window on.
   // Type: int64.
   const string kDisplayId_InitProperty = "init:display_id";
diff --git a/testing/buildbot/chromium.perf.fyi.json b/testing/buildbot/chromium.perf.fyi.json
index 84e472a0..f605621e 100644
--- a/testing/buildbot/chromium.perf.fyi.json
+++ b/testing/buildbot/chromium.perf.fyi.json
@@ -294,7 +294,7 @@
     "isolated_scripts": [
       {
         "args": [
-          "--benchmarks=blink_perf.layout_ng",
+          "--benchmarks=blink_perf.layout_ng,blink_perf.paint_layout_ng,loading.desktop_layout_ng",
           "-v",
           "--upload-results",
           "--output-format=histograms",
diff --git a/testing/buildbot/filters/chromeos.single_process_mash.browser_tests.filter b/testing/buildbot/filters/chromeos.single_process_mash.browser_tests.filter
index e3c8d8f..e76edb77 100644
--- a/testing/buildbot/filters/chromeos.single_process_mash.browser_tests.filter
+++ b/testing/buildbot/filters/chromeos.single_process_mash.browser_tests.filter
@@ -232,10 +232,6 @@
 # Flaky tests. crbug.com/880584
 -UnifiedAutoplaySettingBrowserTest.*
 
-# This seems to timeout only on the msan bot, but I suspect it could equally
-# occur on the non-msan bots. https://crbug.com/891383
--UserAddingScreenTest.AddingSeveralUsers
-
 # Window Open API tests.  https://crbug.com/815379
 -WindowOpenApiTest.OpenLockedFullscreenWindow
 -WindowOpenApiTest.RemoveLockedFullscreenFromWindow
diff --git a/testing/buildbot/filters/mac_window_server_killers.browser_tests.filter b/testing/buildbot/filters/mac_window_server_killers.browser_tests.filter
index dc367ea..3b4a718 100644
--- a/testing/buildbot/filters/mac_window_server_killers.browser_tests.filter
+++ b/testing/buildbot/filters/mac_window_server_killers.browser_tests.filter
@@ -231,6 +231,5 @@
 -WebUIResourceBrowserTest.*
 -WebViewTest.*
 -WebrtcLoggingPrivateApiTest.*
--WebstoreInlineInstallerTest.*
 -WindowAppleScriptTest.*
 -WindowOpenApiTest.*
diff --git a/testing/buildbot/filters/webui_polymer2_browser_tests.filter b/testing/buildbot/filters/webui_polymer2_browser_tests.filter
index c959b96..58c4c28 100644
--- a/testing/buildbot/filters/webui_polymer2_browser_tests.filter
+++ b/testing/buildbot/filters/webui_polymer2_browser_tests.filter
@@ -28,9 +28,6 @@
 # Mac only failure. See crbug.com/876990
 -CrSettingsPrivacyPageTest.All
 
-# Tests that fail only on official builds.
--CrSettingsIncompatibleApplicationsPageTest.All
-
 # ChromeOS only test failures.
 -ActiveDirectoryJoinTest.TestActiveDirectoryEnrollment_DistinguishedName
 -ActiveDirectoryJoinTest.TestActiveDirectoryEnrollment_ErrorCard
@@ -45,9 +42,6 @@
 -CrSettingsFingerprintProgressArcTest.All
 -CrSettingsInternetDetailPageTest.InternetDetailPage
 -CrSettingsInternetPageTest.InternetPage
--CrSettingsLanguagesPageTest.AddLanguagesDialog
--CrSettingsLanguagesPageTest.LanguageMenu
--CrSettingsLanguagesPageTest.Spellcheck
 -CrSettingsMultidevicePageTest.All
 -CrSettingsPeoplePageLockScreenTest.All
 -CrSettingsPeoplePageQuickUnlockAuthenticateTest.All
@@ -150,7 +144,8 @@
 CrSettingsFocusRowBehavior.*
 CrSettingsGoogleAssistantPageTest.*
 CrSettingsImportDataDialogTest.*
-CrSettingsLanguagesPageTest.InputMethods
+CrSettingsIncompatibleApplicationsPageTest.*
+CrSettingsLanguagesPageTest.*
 CrSettingsLanguagesTest.*
 CrSettingsMainPageTest.*
 CrSettingsMenuTest.*
diff --git a/testing/buildbot/filters/webui_polymer2_interactive_ui_tests.filter b/testing/buildbot/filters/webui_polymer2_interactive_ui_tests.filter
index e8f5f670..e125069 100644
--- a/testing/buildbot/filters/webui_polymer2_interactive_ui_tests.filter
+++ b/testing/buildbot/filters/webui_polymer2_interactive_ui_tests.filter
@@ -13,7 +13,8 @@
 CrSettingsSyncPageTest.All
 MaterialBookmarksFocusTest.All
 MaterialHistoryFocusTest.All
-PrintPreviewDestinationDialogInteractiveTest.FocusSearchBox
-PrintPreviewPrintHeaderInteractiveTest.FocusPrintOnReady
+PrintPreviewDestinationDialogInteractiveTest.*
+PrintPreviewNumberSettingsSectionInteractiveTest.BlurResetsEmptyInput
 PrintPreviewPagesSettingsTest.*
+PrintPreviewPrintHeaderInteractiveTest.FocusPrintOnReady
 SettingsUIBrowserTest.All
diff --git a/testing/scripts/run_telemetry_benchmark_as_googletest.py b/testing/scripts/run_telemetry_benchmark_as_googletest.py
index 1aa1c22..464ead6e 100755
--- a/testing/scripts/run_telemetry_benchmark_as_googletest.py
+++ b/testing/scripts/run_telemetry_benchmark_as_googletest.py
@@ -126,6 +126,7 @@
     cmd_args = cmd_args + [
       '--story-filter=' + filter_regex
     ]
+  rc = 1  # Set default returncode in case there is an exception.
   try:
     cmd = [sys.executable] + cmd_args + [
       '--output-dir', tempfile_dir,
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 69b8363c6..6021bc27 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -5036,21 +5036,6 @@
             ]
         }
     ],
-    "ViewsBrowserWindows": [
-        {
-            "platforms": [
-                "mac"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled_20180703",
-                    "enable_features": [
-                        "ViewsBrowserWindows"
-                    ]
-                }
-            ]
-        }
-    ],
     "VizDisplayCompositor": [
         {
             "platforms": [
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-gen-property-trees b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-gen-property-trees
index 0d4ba7161..9b69c86 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-gen-property-trees
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-gen-property-trees
@@ -60,7 +60,6 @@
 Bug(none) vibration/ [ Skip ]
 Bug(none) virtual/cors-rfc1918/ [ Skip ]
 Bug(none) virtual/custom-user-timing/ [ Skip ]
-Bug(none) virtual/enable_wasm/ [ Skip ]
 Bug(none) virtual/exotic-color-space/ [ Skip ]
 Bug(none) virtual/feature-policy-permissions/ [ Skip ]
 Bug(none) virtual/feature-policy-vibrate/ [ Skip ]
@@ -154,6 +153,7 @@
 # Benign subpixel differences.
 Bug(none) transforms/3d/point-mapping/3d-point-mapping-deep.html [ Failure ]
 Bug(none) transforms/3d/point-mapping/3d-point-mapping-preserve-3d.html [ Failure ]
+Bug(none) compositing/direct-image-compositing.html [ Failure ]
 Bug(none) compositing/masks/direct-image-mask.html [ Failure ]
 Bug(none) compositing/geometry/layer-due-to-layer-children.html [ Failure ]
 Bug(none) compositing/perpendicular-layer-sorting.html [ Failure ]
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2 b/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2
index 6f821ef..05f7c4de8 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2
@@ -68,7 +68,6 @@
 
 Bug(none) virtual/android/ [ Skip ]
 Bug(none) virtual/blink-gen-property-trees/ [ Skip ]
-Bug(none) virtual/enable_wasm/ [ Skip ]
 Bug(none) virtual/exotic-color-space/ [ Skip ]
 Bug(none) virtual/gpu/ [ Skip ]
 Bug(none) virtual/gpu-rasterization/ [ Skip ]
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/site-per-process b/third_party/WebKit/LayoutTests/FlagExpectations/site-per-process
index f1d40e3..753b6dd7 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/site-per-process
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/site-per-process
@@ -105,8 +105,6 @@
 Bug(none) virtual/sharedarraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-similar-but-cross-origin-success.sub.html [ Skip ]
 Bug(none) external/wpt/wasm/serialization/window-domain-success.sub.html [ Skip ]
 Bug(none) external/wpt/wasm/serialization/window-similar-but-cross-origin-success.sub.html [ Skip ]
-Bug(none) virtual/enable_wasm/external/wpt/wasm/serialization/window-domain-success.sub.html [ Skip ]
-Bug(none) virtual/enable_wasm/external/wpt/wasm/serialization/window-similar-but-cross-origin-success.sub.html [ Skip ]
 
 # Layout tests don't work for printing cross-site frames.
 crbug.com/822372 http/tests/printing/cross-site-frame.html [ Crash ]
diff --git a/third_party/WebKit/LayoutTests/NeverFixTests b/third_party/WebKit/LayoutTests/NeverFixTests
index 5d32c9dd..f3362ea 100644
--- a/third_party/WebKit/LayoutTests/NeverFixTests
+++ b/third_party/WebKit/LayoutTests/NeverFixTests
@@ -285,9 +285,6 @@
 # Only Windows supports Symbol CMAP encoded fonts.
 crbug.com/627953 [ Android Linux Mac ] fast/text/symbol-cmap.html [ WontFix ]
 
-# wasm tests. Currently under virtual/enable_wasm
-crbug.com/642912 http/tests/wasm/ [ WontFix ]
-
 # These tests require audio codecs which are generally not available;
 # these tests can still be run manually with --skipped=ignore.
 webaudio/codec-tests/aac [ WontFix ]
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index dcb69d3..7ba34e4 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -3917,7 +3917,6 @@
 
 
 crbug.com/765738 [ Linux Win Mac ] http/tests/wasm/wasm_remote_postMessage_test.https.html [ Pass Timeout ]
-crbug.com/765738 [ Linux Win Mac ] virtual/enable_wasm/http/tests/wasm/wasm_remote_postMessage_test.https.html [ Pass Timeout ]
 
 # ====== Random order flaky tests from here ======
 # These tests are flaky when run in random order, which is the default on Linux & Mac since since 2016-12-16.
@@ -4065,14 +4064,10 @@
 crbug.com/874302 virtual/sharedarraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success-and-failure.html [ Timeout ]
 crbug.com/874302 virtual/sharedarraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success.html [ Timeout ]
 crbug.com/874302 external/wpt/wasm/serialization/broadcastchannel-success-and-failure.html [ Timeout ]
-crbug.com/874302 virtual/enable_wasm/external/wpt/wasm/serialization/broadcastchannel-success-and-failure.html [ Timeout ]
 crbug.com/874302 external/wpt/wasm/serialization/broadcastchannel-success.html [ Timeout ]
-crbug.com/874302 virtual/enable_wasm/external/wpt/wasm/serialization/broadcastchannel-success.html [ Timeout ]
 
 crbug.com/877286 external/wpt/wasm/serialization/no-transferring.html [ Failure ]
-crbug.com/877286 virtual/enable_wasm/external/wpt/wasm/serialization/no-transferring.html [ Failure ]
 crbug.com/877296 external/wpt/wasm/serialization/window-serviceworker-failure.https.html [ Failure ]
-crbug.com/877296 virtual/enable_wasm/external/wpt/wasm/serialization/window-serviceworker-failure.https.html [ Failure ]
 
 crbug.com/831509 external/wpt/service-workers/service-worker/skip-waiting-installed.https.html [ Failure Pass ]
 crbug.com/831509 virtual/navigation-mojo-response/external/wpt/service-workers/service-worker/skip-waiting-installed.https.html [ Failure Pass ]
diff --git a/third_party/WebKit/LayoutTests/VirtualTestSuites b/third_party/WebKit/LayoutTests/VirtualTestSuites
index bc5d0c1..7a71b181 100644
--- a/third_party/WebKit/LayoutTests/VirtualTestSuites
+++ b/third_party/WebKit/LayoutTests/VirtualTestSuites
@@ -327,16 +327,6 @@
     "args": ["--force-device-scale-factor=1.5"]
   },
   {
-    "prefix": "enable_wasm",
-    "base": "http/tests/wasm",
-    "args": ["--enable-features=WebAssembly"]
-  },
-  {
-    "prefix": "enable_wasm",
-    "base": "external/wpt/wasm",
-    "args": ["--enable-features=WebAssembly"]
-  },
-  {
     "prefix": "layout_ng",
     "base": "fast/block/basic",
     "args": ["--enable-blink-features=LayoutNG"]
diff --git a/third_party/WebKit/LayoutTests/compositing/overflow/rotate-clip-expected.png b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-clip-expected.png
new file mode 100644
index 0000000..9daec2d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-clip-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/compositing/overflow/rotate-clip-expected.txt b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-clip-expected.txt
new file mode 100644
index 0000000..9801d91
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-clip-expected.txt
@@ -0,0 +1,64 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [240, 240],
+      "transform": 2
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [20, 20],
+      "bounds": [200, 200],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [20, 20],
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [120, 120]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/compositing/overflow/rotate-clip.html b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-clip.html
new file mode 100644
index 0000000..c7b8fe2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-clip.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<div style="margin: 100px; transform: rotate(45deg); overflow: hidden;
+            width: 200px; height: 200px; border: 20px solid green">
+  <div style="will-change: transform; width: 400px; height: 400px; background: blue"></div>
+</div>
+<script>
+if (window.testRunner)
+  testRunner.setCustomTextOutput(internals.layerTreeAsText(document));
+</script>
diff --git a/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-effect-interleave-expected.png b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-effect-interleave-expected.png
new file mode 100644
index 0000000..2aed34e1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-effect-interleave-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-effect-interleave-expected.txt b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-effect-interleave-expected.txt
new file mode 100644
index 0000000..a390599f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-effect-interleave-expected.txt
@@ -0,0 +1,94 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [240, 240],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [240, 240],
+      "opacity": 0.899999976158142,
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [20, 20],
+      "bounds": [200, 200],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [20, 20],
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "position": [20, 0],
+      "bounds": [400, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#00FFFF",
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [20, 20],
+      "bounds": [200, 200],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV",
+      "position": [20, 20],
+      "bounds": [100, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF00FF",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [120, 120]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-effect-interleave.html b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-effect-interleave.html
new file mode 100644
index 0000000..0b6cd606
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-effect-interleave.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<div style="margin: 100px; width: 240px; height: 240px; transform: rotate(45deg)">
+  <div style="opacity: 0.9">
+    <div style="overflow: hidden; width: 200px; height: 200px; border: 20px solid green">
+      <div style="will-change: transform; width: 400px; height: 400px; background: blue"></div>
+      <div style="position: fixed; top: 0; width: 400px; height: 100px; background: cyan"></div>
+      <div style="will-change: transform; z-index: 1; position: relative; top: -400px; width: 100px; height: 400px; background: magenta"></div>
+    </div>
+  </div>
+</div>
+<script>
+if (window.testRunner)
+  testRunner.setCustomTextOutput(internals.layerTreeAsText(document));
+</script>
diff --git a/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-expected.png b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-expected.png
new file mode 100644
index 0000000..9daec2d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-expected.txt b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-expected.txt
new file mode 100644
index 0000000..fbb4681
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-expected.txt
@@ -0,0 +1,64 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [240, 240],
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [20, 20],
+      "bounds": [200, 200],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [20, 20],
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [120, 120]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
new file mode 100644
index 0000000..d4ef5fe
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-z-order-interleave-expected.txt b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-z-order-interleave-expected.txt
new file mode 100644
index 0000000..f97eb4f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-z-order-interleave-expected.txt
@@ -0,0 +1,82 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [300, 100],
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV",
+      "bounds": [200, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "bounds": [200, 22],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFF00",
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV",
+      "bounds": [50, 200],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF0000",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [150, 50]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-z-order-interleave.html b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-z-order-interleave.html
new file mode 100644
index 0000000..f3af2dcd
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip-z-order-interleave.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<div style="margin: 100px; width: 300px; transform: rotate(45deg)">
+  <div style="position: absolute; width: 200px; height: 22px; background: yellow; z-index: 1; will-change: transform"></div>
+  <div style="width: 100px; height: 100px; overflow: hidden">
+    <div style="position: relative; width: 200px; height: 100px; background: green; will-change: transform"></div>
+    <div style="position: relative; top: -100px; z-index: 2; width: 50px; height: 200px; background: red; will-change: transform"></div>
+  </div>
+</div>
+<script>
+if (window.testRunner)
+  testRunner.setCustomTextOutput(internals.layerTreeAsText(document));
+</script>
diff --git a/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip.html b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip.html
new file mode 100644
index 0000000..7172ca2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/compositing/overflow/rotate-then-clip.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<div style="margin: 100px; width: 240px; height: 240px; transform: rotate(45deg)">
+  <div style="overflow: hidden; width: 200px; height: 200px; border: 20px solid green">
+    <div style="will-change: transform; width: 400px; height: 400px; background: blue"></div>
+  </div>
+</div>
+<script>
+if (window.testRunner)
+  testRunner.setCustomTextOutput(internals.layerTreeAsText(document));
+</script>
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-clip-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-clip-expected.txt
new file mode 100644
index 0000000..101f13c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-clip-expected.txt
@@ -0,0 +1,57 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [240, 240],
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [20, 20],
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [120, 120]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-effect-interleave-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-effect-interleave-expected.txt
new file mode 100644
index 0000000..c087562
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-effect-interleave-expected.txt
@@ -0,0 +1,80 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [240, 240],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [240, 240],
+      "opacity": 0.899999976158142,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [20, 20],
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "position": [20, 0],
+      "bounds": [400, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#00FFFF",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV",
+      "position": [20, 20],
+      "bounds": [100, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF00FF",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [120, 120]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-expected.txt
new file mode 100644
index 0000000..101f13c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-expected.txt
@@ -0,0 +1,57 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [240, 240],
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [20, 20],
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [120, 120]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
new file mode 100644
index 0000000..5b9c2a3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-z-order-interleave-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-z-order-interleave-expected.txt
new file mode 100644
index 0000000..c40716aa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-z-order-interleave-expected.txt
@@ -0,0 +1,70 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [300, 100],
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV",
+      "bounds": [200, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "bounds": [200, 22],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFF00",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV",
+      "bounds": [50, 200],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF0000",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [150, 50]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-clip-expected.png b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-clip-expected.png
new file mode 100644
index 0000000..7bb3d1f43c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-clip-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-clip-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-clip-expected.txt
new file mode 100644
index 0000000..f437231
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-clip-expected.txt
@@ -0,0 +1,41 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [20, 20],
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [120, 120]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-effect-interleave-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-effect-interleave-expected.txt
new file mode 100644
index 0000000..77428afc
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-effect-interleave-expected.txt
@@ -0,0 +1,62 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [240, 240],
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [20, 20],
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "position": [20, 0],
+      "bounds": [400, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#00FFFF",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV",
+      "position": [20, 20],
+      "bounds": [100, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF00FF",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [120, 120]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-expected.png b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-expected.png
new file mode 100644
index 0000000..7bb3d1f43c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-expected.txt
new file mode 100644
index 0000000..f437231
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-expected.txt
@@ -0,0 +1,41 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [20, 20],
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [120, 120]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
new file mode 100644
index 0000000..5b9c2a3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-z-order-interleave-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-z-order-interleave-expected.txt
new file mode 100644
index 0000000..53ca7f2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-slimming-paint-v2/compositing/overflow/rotate-then-clip-z-order-interleave-expected.txt
@@ -0,0 +1,54 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV",
+      "bounds": [200, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "bounds": [200, 22],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFF00",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV",
+      "bounds": [50, 200],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF0000",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [150, 50]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/resources/onunload.html b/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/resources/onunload.html
new file mode 100644
index 0000000..46e36a64
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/resources/onunload.html
@@ -0,0 +1,4 @@
+<html>
+<body onunload="debugger">
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/resources/page-with-unload.html b/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/resources/page-with-unload.html
new file mode 100644
index 0000000..859daa6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/resources/page-with-unload.html
@@ -0,0 +1,2 @@
+<script>console.log('ready')</script>
+<body onunload="debugger"></body>
\ No newline at end of file
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/skip-pause-during-navigation-expected.txt b/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/skip-pause-during-navigation-expected.txt
new file mode 100644
index 0000000..37c8fcb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/skip-pause-during-navigation-expected.txt
@@ -0,0 +1,7 @@
+Tests that we skip all pauses during navigation
+Navigate page..
+Wait for ready message..
+done!
+Script execution paused.
+Script execution resumed.
+
diff --git a/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/skip-pause-during-navigation.js b/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/skip-pause-during-navigation.js
new file mode 100644
index 0000000..7cd59c8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/devtools/sources/debugger/skip-pause-during-navigation.js
@@ -0,0 +1,29 @@
+// 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.
+
+(async function() {
+  TestRunner.addResult(
+      `Tests that we skip all pauses during navigation`);
+  await TestRunner.loadModule('sources_test_runner');
+  await TestRunner.loadModule('console_test_runner');
+  await TestRunner.showPanel('sources');
+  await SourcesTestRunner.startDebuggerTestPromise();
+  await TestRunner.navigatePromise('resources/page-with-unload.html');
+  TestRunner.addResult('Navigate page..');
+  TestRunner.evaluateInPagePromise('window.location.href = window.location.href');
+  TestRunner.addResult('Wait for ready message..');
+  await ConsoleTestRunner.waitUntilMessageReceivedPromise();
+  TestRunner.addResult('done!');
+
+  await TestRunner.addIframe('http://127.0.0.1:8000/devtools/sources/debugger/resources/onunload.html', {
+    name: 'myIFrame'
+  });
+
+  ConsoleTestRunner.changeExecutionContext('myIFrame');
+  ConsoleTestRunner.evaluateInConsolePromise('window.location.href = window.location.href', true);
+  await SourcesTestRunner.waitUntilPausedPromise();
+  SourcesTestRunner.resumeExecution();
+
+  SourcesTestRunner.completeDebuggerTest();
+})();
diff --git a/third_party/WebKit/LayoutTests/virtual/enable_wasm/README.txt b/third_party/WebKit/LayoutTests/virtual/enable_wasm/README.txt
deleted file mode 100644
index 5888cfc..0000000
--- a/third_party/WebKit/LayoutTests/virtual/enable_wasm/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-Tests that depend on --enable-features=WebAssembly
diff --git a/third_party/WebKit/LayoutTests/virtual/enable_wasm/external/wpt/wasm/README.txt b/third_party/WebKit/LayoutTests/virtual/enable_wasm/external/wpt/wasm/README.txt
deleted file mode 100644
index 5888cfc..0000000
--- a/third_party/WebKit/LayoutTests/virtual/enable_wasm/external/wpt/wasm/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-Tests that depend on --enable-features=WebAssembly
diff --git a/third_party/WebKit/LayoutTests/virtual/enable_wasm/http/tests/wasm/README.txt b/third_party/WebKit/LayoutTests/virtual/enable_wasm/http/tests/wasm/README.txt
deleted file mode 100644
index 5888cfc..0000000
--- a/third_party/WebKit/LayoutTests/virtual/enable_wasm/http/tests/wasm/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-Tests that depend on --enable-features=WebAssembly
diff --git a/third_party/blink/public/web/web_view.h b/third_party/blink/public/web/web_view.h
index bafddd24..3d6721a 100644
--- a/third_party/blink/public/web/web_view.h
+++ b/third_party/blink/public/web/web_view.h
@@ -272,9 +272,10 @@
 
   // Returns the "preferred" contents size, defined as the preferred minimum
   // width of the main document's contents and the minimum height required to
-  // display the main document without scrollbars.  The returned size has the
-  // page zoom factor applied. The lifecycle must be updated to at least layout
-  // before calling this (see: |UpdateLifecycle|).
+  // display the main document without scrollbars. If the document is in quirks
+  // mode (does not have <!doctype html>), the height will stretch to fill the
+  // viewport. The returned size has the page zoom factor applied. The lifecycle
+  // must be updated to at least layout before calling (see: |UpdateLifecycle|).
   virtual WebSize ContentsPreferredMinimumSize() = 0;
 
   // Sets the display mode of the web app.
diff --git a/third_party/blink/renderer/core/exported/local_frame_client_impl.cc b/third_party/blink/renderer/core/exported/local_frame_client_impl.cc
index 3a57e97..2cd6c4c 100644
--- a/third_party/blink/renderer/core/exported/local_frame_client_impl.cc
+++ b/third_party/blink/renderer/core/exported/local_frame_client_impl.cc
@@ -496,6 +496,8 @@
     const ResourceError& error,
     WebHistoryCommitType commit_type) {
   web_frame_->DidFail(error, true, commit_type);
+  if (WebDevToolsAgentImpl* dev_tools = DevToolsAgent())
+    dev_tools->DidFailProvisionalLoad(web_frame_->GetFrame());
   virtual_time_pauser_.UnpauseVirtualTime();
 }
 
diff --git a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc
index d1d9b39..04eb8da 100644
--- a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.cc
@@ -412,12 +412,25 @@
   resource_content_loader_->DidCommitLoadForLocalFrame(frame);
   for (auto& session : sessions_)
     session->DidCommitLoadForLocalFrame(frame);
+  if (inspected_frames_->Root() == frame) {
+    for (auto& session : sessions_)
+      session->V8Session()->setSkipAllPauses(false);
+  }
+}
+
+void WebDevToolsAgentImpl::DidFailProvisionalLoad(LocalFrame* frame) {
+  if (inspected_frames_->Root() == frame) {
+    for (auto& session : sessions_)
+      session->V8Session()->setSkipAllPauses(false);
+  }
 }
 
 void WebDevToolsAgentImpl::DidStartProvisionalLoad(LocalFrame* frame) {
   if (inspected_frames_->Root() == frame) {
-    for (auto& session : sessions_)
+    for (auto& session : sessions_) {
+      session->V8Session()->setSkipAllPauses(true);
       session->V8Session()->resume();
+    }
   }
 }
 
diff --git a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h
index acc14e3..e63f89aa 100644
--- a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h
+++ b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h
@@ -83,6 +83,7 @@
 
   // Instrumentation from web/ layer.
   void DidCommitLoadForLocalFrame(LocalFrame*);
+  void DidFailProvisionalLoad(LocalFrame*);
   void DidStartProvisionalLoad(LocalFrame*);
   bool ScreencastEnabled();
   String NavigationInitiatorInfo(LocalFrame*);
diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc
index c81ef4a..1ede952d 100644
--- a/third_party/blink/renderer/core/exported/web_view_test.cc
+++ b/third_party/blink/renderer/core/exported/web_view_test.cc
@@ -4075,6 +4075,24 @@
   EXPECT_EQ(2, size.height);
 }
 
+TEST_F(WebViewTest, PreferredMinimumSizeQuirksMode) {
+  WebViewImpl* web_view = web_view_helper_.Initialize();
+  web_view->Resize(WebSize(800, 600));
+  FrameTestHelpers::LoadHTMLString(
+      web_view->MainFrameImpl(),
+      R"HTML(<html>
+        <body style="margin: 0px;">
+          <div style="width: 99px; height: 100px; display: inline-block;"></div>
+        </body>
+      </html>)HTML",
+      URLTestHelpers::ToKURL("http://example.com/"));
+
+  WebSize size = web_view->ContentsPreferredMinimumSize();
+  EXPECT_EQ(99, size.width);
+  // When in quirks mode the preferred height stretches to fill the viewport.
+  EXPECT_EQ(600, size.height);
+}
+
 TEST_F(WebViewTest, PreferredSizeWithGrid) {
   WebViewImpl* web_view = web_view_helper_.Initialize();
   WebURL base_url = URLTestHelpers::ToKURL("http://example.com/");
diff --git a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
index 83e35ee4..1c898a2 100644
--- a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
@@ -453,7 +453,6 @@
     : inspected_frames_(inspected_frames),
       v8_session_(v8_session),
       client_(client),
-      reloading_(false),
       inspector_resource_content_loader_(resource_content_loader),
       resource_content_loader_client_id_(
           resource_content_loader->CreateClientId()),
@@ -546,7 +545,6 @@
 
   stopScreencast();
 
-  FinishReload();
   return Response::OK();
 }
 
@@ -647,7 +645,6 @@
   pending_script_to_evaluate_on_load_once_ =
       optional_script_to_evaluate_on_load.fromMaybe("");
   v8_session_->setSkipAllPauses(true);
-  reloading_ = true;
   return Response::OK();
 }
 
@@ -717,13 +714,6 @@
   return Response::OK();
 }
 
-void InspectorPageAgent::FinishReload() {
-  if (!reloading_)
-    return;
-  reloading_ = false;
-  v8_session_->setSkipAllPauses(false);
-}
-
 void InspectorPageAgent::GetResourceContentAfterResourcesContentLoaded(
     const String& frame_id,
     const String& url,
@@ -910,7 +900,6 @@
 
 void InspectorPageAgent::WillCommitLoad(LocalFrame*, DocumentLoader* loader) {
   if (loader->GetFrame() == inspected_frames_->Root()) {
-    FinishReload();
     script_to_evaluate_on_load_once_ = pending_script_to_evaluate_on_load_once_;
     pending_script_to_evaluate_on_load_once_ = String();
   }
diff --git a/third_party/blink/renderer/core/inspector/inspector_page_agent.h b/third_party/blink/renderer/core/inspector/inspector_page_agent.h
index d241417c..65325e4 100644
--- a/third_party/blink/renderer/core/inspector/inspector_page_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_page_agent.h
@@ -214,7 +214,6 @@
                      InspectorResourceContentLoader*,
                      v8_inspector::V8InspectorSession*);
 
-  void FinishReload();
   void GetResourceContentAfterResourcesContentLoaded(
       const String& frame_id,
       const String& url,
@@ -242,7 +241,6 @@
   Client* client_;
   String pending_script_to_evaluate_on_load_once_;
   String script_to_evaluate_on_load_once_;
-  bool reloading_;
   Member<InspectorResourceContentLoader> inspector_resource_content_loader_;
   int resource_content_loader_client_id_;
   InspectorAgentState::Boolean enabled_;
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
index 2fc72fa5..3dc73c2 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
@@ -35,47 +35,42 @@
   explicit LayoutNGMixin(Element* element);
   ~LayoutNGMixin() override;
 
-  bool IsLayoutNGObject() const override { return true; }
+  bool IsLayoutNGObject() const final { return true; }
 
-  NGInlineNodeData* TakeNGInlineNodeData() override;
-  NGInlineNodeData* GetNGInlineNodeData() const override;
-  void ResetNGInlineNodeData() override;
-  bool HasNGInlineNodeData() const override {
-    return ng_inline_node_data_.get();
-  }
+  NGInlineNodeData* TakeNGInlineNodeData() final;
+  NGInlineNodeData* GetNGInlineNodeData() const final;
+  void ResetNGInlineNodeData() final;
+  bool HasNGInlineNodeData() const final { return ng_inline_node_data_.get(); }
 
-  LayoutUnit FirstLineBoxBaseline() const override;
-  LayoutUnit InlineBlockBaseline(LineDirectionMode) const override;
+  LayoutUnit FirstLineBoxBaseline() const final;
+  LayoutUnit InlineBlockBaseline(LineDirectionMode) const final;
 
-  void InvalidateDisplayItemClients(PaintInvalidationReason) const override;
+  void InvalidateDisplayItemClients(PaintInvalidationReason) const final;
 
-  void Paint(const PaintInfo&) const override;
+  void Paint(const PaintInfo&) const final;
 
   bool NodeAtPoint(HitTestResult&,
                    const HitTestLocation& location_in_container,
                    const LayoutPoint& accumulated_offset,
-                   HitTestAction) override;
+                   HitTestAction) final;
 
-  PositionWithAffinity PositionForPoint(const LayoutPoint&) const override;
+  PositionWithAffinity PositionForPoint(const LayoutPoint&) const final;
 
   // Returns the last layout result for this block flow with the given
   // constraint space and break token, or null if it is not up-to-date or
   // otherwise unavailable.
-  scoped_refptr<NGLayoutResult> CachedLayoutResult(
-      const NGConstraintSpace&,
-      NGBreakToken*) const override;
+  scoped_refptr<NGLayoutResult> CachedLayoutResult(const NGConstraintSpace&,
+                                                   NGBreakToken*) const final;
 
   void SetCachedLayoutResult(const NGConstraintSpace&,
                              const NGBreakToken*,
-                             const NGLayoutResult&) override;
-  void ClearCachedLayoutResult() override;
+                             const NGLayoutResult&) final;
+  void ClearCachedLayoutResult() final;
 
   // For testing only.
-  scoped_refptr<const NGLayoutResult> CachedLayoutResultForTesting() override;
+  scoped_refptr<const NGLayoutResult> CachedLayoutResultForTesting() final;
 
-  NGPaintFragment* PaintFragment() const override {
-    return paint_fragment_.get();
-  }
+  NGPaintFragment* PaintFragment() const final { return paint_fragment_.get(); }
   void SetPaintFragment(const NGBreakToken*,
                         scoped_refptr<const NGPhysicalFragment>,
                         NGPhysicalOffset) final;
@@ -87,7 +82,7 @@
  protected:
   bool IsOfType(LayoutObject::LayoutObjectType) const override;
 
-  void AddOverflowFromChildren() override;
+  void AddOverflowFromChildren() final;
 
  private:
   void AddScrollingOverflowFromChildren();
@@ -98,14 +93,14 @@
  protected:
   void AddOutlineRects(Vector<LayoutRect>&,
                        const LayoutPoint& additional_offset,
-                       NGOutlineType) const override;
+                       NGOutlineType) const final;
 
-  const NGPhysicalBoxFragment* CurrentFragment() const override;
+  const NGPhysicalBoxFragment* CurrentFragment() const final;
 
   const NGBaseline* FragmentBaseline(NGBaselineAlgorithmType) const;
 
   void DirtyLinesFromChangedChild(LayoutObject* child,
-                                  MarkingBehavior marking_behavior) override;
+                                  MarkingBehavior marking_behavior) final;
 
   std::unique_ptr<NGInlineNodeData> ng_inline_node_data_;
 
diff --git a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h
index cb5c14d..a060bb1 100644
--- a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h
+++ b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h
@@ -26,7 +26,6 @@
     return StyleRef().ListStyleImage() &&
            !StyleRef().ListStyleImage()->ErrorOccurred();
   }
-  bool IsLayoutNGObject() const override { return true; }
 
   void UpdateMarkerTextIfNeeded() {
     if (marker_ && !is_marker_text_updated_ && !IsMarkerImage())
diff --git a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h
index c85d0a8..469de1b 100644
--- a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h
+++ b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h
@@ -33,8 +33,6 @@
 
   bool IsContentImage() const;
 
-  bool IsLayoutNGObject() const override { return true; }
-
   LayoutObject* SymbolMarkerLayoutText() const;
 
   // Marker text with suffix, e.g. "1. ", for use in accessibility.
diff --git a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
index e0e178b..71000a9a 100644
--- a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
+++ b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
@@ -266,6 +266,8 @@
   paint_layer.SetPreviousPaintPhaseDescendantOutlinesEmpty(false);
   paint_layer.SetPreviousPaintPhaseFloatEmpty(false);
   paint_layer.SetPreviousPaintPhaseDescendantBlockBackgroundsEmpty(false);
+  context.paint_invalidator_context.subtree_flags |=
+      PaintInvalidatorContext::kSubtreeVisualRectUpdate;
 }
 
 bool PrePaintTreeWalk::NeedsTreeBuilderContextUpdate(
diff --git a/third_party/blink/renderer/modules/cache_storage/cache.cc b/third_party/blink/renderer/modules/cache_storage/cache.cc
index ae162f6..c5758cd9 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache.cc
@@ -169,7 +169,7 @@
 class Cache::BarrierCallbackForPut final
     : public GarbageCollectedFinalized<BarrierCallbackForPut> {
  public:
-  BarrierCallbackForPut(int number_of_operations,
+  BarrierCallbackForPut(wtf_size_t number_of_operations,
                         Cache* cache,
                         const String& method_name,
                         ScriptPromiseResolver* resolver)
@@ -181,7 +181,7 @@
     batch_operations_.resize(number_of_operations);
   }
 
-  void OnSuccess(size_t index,
+  void OnSuccess(wtf_size_t index,
                  mojom::blink::BatchOperationPtr batch_operation) {
     DCHECK_LT(index, batch_operations_.size());
     if (!StillActive())
@@ -314,7 +314,7 @@
   USING_GARBAGE_COLLECTED_MIXIN(BlobHandleCallbackForPut);
 
  public:
-  BlobHandleCallbackForPut(size_t index,
+  BlobHandleCallbackForPut(wtf_size_t index,
                            BarrierCallbackForPut* barrier_callback,
                            Request* request,
                            Response* response)
@@ -347,7 +347,7 @@
   }
 
  private:
-  const size_t index_;
+  const wtf_size_t index_;
   Member<BarrierCallbackForPut> barrier_callback_;
 
   WebServiceWorkerRequest web_request_;
@@ -361,7 +361,7 @@
 
  public:
   CodeCacheHandleCallbackForPut(ScriptState* script_state,
-                                size_t index,
+                                wtf_size_t index,
                                 BarrierCallbackForPut* barrier_callback,
                                 Request* request,
                                 Response* response)
@@ -432,7 +432,7 @@
 
  private:
   const Member<ScriptState> script_state_;
-  const size_t index_;
+  const wtf_size_t index_;
   Member<BarrierCallbackForPut> barrier_callback_;
   const String mime_type_;
 
@@ -708,7 +708,7 @@
   request_infos.resize(requests.size());
   Vector<ScriptPromise> promises;
   promises.resize(requests.size());
-  for (size_t i = 0; i < requests.size(); ++i) {
+  for (wtf_size_t i = 0; i < requests.size(); ++i) {
     if (!requests[i]->url().ProtocolIsInHTTPFamily()) {
       return ScriptPromise::Reject(script_state,
                                    V8ThrowException::CreateTypeError(
@@ -813,7 +813,7 @@
   BarrierCallbackForPut* barrier_callback =
       new BarrierCallbackForPut(requests.size(), this, method_name, resolver);
 
-  for (size_t i = 0; i < requests.size(); ++i) {
+  for (wtf_size_t i = 0; i < requests.size(); ++i) {
     KURL url(NullURL(), requests[i]->url());
     if (!url.ProtocolIsInHTTPFamily()) {
       barrier_callback->OnError("Request scheme '" + url.Protocol() +
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_test.cc b/third_party/blink/renderer/modules/cache_storage/cache_test.cc
index 3209fd4da..e3fe6e8 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_test.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache_test.cc
@@ -213,7 +213,7 @@
       if (expected_batch_operations[i]->response) {
         ASSERT_EQ(expected_batch_operations[i]->response->url_list.size(),
                   batch_operations[i]->response->url_list.size());
-        for (size_t j = 0;
+        for (wtf_size_t j = 0;
              j < expected_batch_operations[i]->response->url_list.size(); ++j) {
           EXPECT_EQ(expected_batch_operations[i]->response->url_list[j],
                     batch_operations[i]->response->url_list[j]);
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
index 4ed61e6f..ba98460 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
@@ -360,11 +360,8 @@
 }
 
 static bool LineDashSequenceIsValid(const Vector<double>& dash) {
-  for (size_t i = 0; i < dash.size(); i++) {
-    if (!std::isfinite(dash[i]) || dash[i] < 0)
-      return false;
-  }
-  return true;
+  return std::all_of(dash.begin(), dash.end(),
+                     [](double d) { return std::isfinite(d) && d >= 0; });
 }
 
 void BaseRenderingContext2D::setLineDash(const Vector<double>& dash) {
@@ -1802,7 +1799,7 @@
       CanvasColorParams(ColorParams().ColorSpace(), PixelFormat(), kNonOpaque);
   if (data_color_params.NeedsColorConversion(context_color_params) ||
       PixelFormat() == kF16CanvasPixelFormat) {
-    unsigned data_length =
+    size_t data_length =
         data->Size().Area() * context_color_params.BytesPerPixel();
     std::unique_ptr<uint8_t[]> converted_pixels(new uint8_t[data_length]);
     if (data->ImageDataInCanvasColorSettings(
diff --git a/third_party/blink/renderer/modules/crypto/crypto_key.cc b/third_party/blink/renderer/modules/crypto/crypto_key.cc
index 6a37e56..8d2319a 100644
--- a/third_party/blink/renderer/modules/crypto/crypto_key.cc
+++ b/third_party/blink/renderer/modules/crypto/crypto_key.cc
@@ -38,6 +38,7 @@
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/to_v8.h"
 #include "third_party/blink/renderer/platform/crypto_result.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 
 namespace blink {
 
@@ -122,7 +123,8 @@
   void SetUint8Array(const char* property_name,
                      const WebVector<unsigned char>& vector) override {
     builder_.Add(property_name,
-                 DOMUint8Array::Create(vector.Data(), vector.size()));
+                 DOMUint8Array::Create(vector.Data(),
+                                       SafeCast<wtf_size_t>(vector.size())));
   }
 
  private:
@@ -227,7 +229,7 @@
                                WebCryptoKeyUsageMask& mask,
                                CryptoResult* result) {
   mask = 0;
-  for (size_t i = 0; i < usages.size(); ++i) {
+  for (wtf_size_t i = 0; i < usages.size(); ++i) {
     WebCryptoKeyUsageMask usage = KeyUsageStringToMask(usages[i]);
     if (!usage) {
       result->CompleteWithError(kWebCryptoErrorTypeType,
diff --git a/third_party/blink/renderer/modules/crypto/normalize_algorithm.cc b/third_party/blink/renderer/modules/crypto/normalize_algorithm.cc
index 013cfad..ab0355a 100644
--- a/third_party/blink/renderer/modules/crypto/normalize_algorithm.cc
+++ b/third_party/blink/renderer/modules/crypto/normalize_algorithm.cc
@@ -228,17 +228,18 @@
       return String();
 
     StringBuilder result;
-    const char* separator = ": ";
+    constexpr const char* separator = ": ";
 
-    size_t length = (messages_.size() - 1) * strlen(separator);
-    for (size_t i = 0; i < messages_.size(); ++i)
+    wtf_size_t length = (messages_.size() - 1) * strlen(separator);
+    for (wtf_size_t i = 0; i < messages_.size(); ++i)
       length += strlen(messages_[i]);
     result.ReserveCapacity(length);
 
-    for (size_t i = 0; i < messages_.size(); ++i) {
+    for (wtf_size_t i = 0; i < messages_.size(); ++i) {
       if (i)
         result.Append(separator, strlen(separator));
-      result.Append(messages_[i], strlen(messages_[i]));
+      result.Append(messages_[i],
+                    static_cast<wtf_size_t>(strlen(messages_[i])));
     }
 
     return result.ToString();
diff --git a/third_party/blink/renderer/modules/eventsource/event_source_parser.cc b/third_party/blink/renderer/modules/eventsource/event_source_parser.cc
index dfca4c7..39b80f1 100644
--- a/third_party/blink/renderer/modules/eventsource/event_source_parser.cc
+++ b/third_party/blink/renderer/modules/eventsource/event_source_parser.cc
@@ -22,12 +22,12 @@
       client_(client),
       codec_(NewTextCodec(UTF8Encoding())) {}
 
-void EventSourceParser::AddBytes(const char* bytes, size_t size) {
+void EventSourceParser::AddBytes(const char* bytes, uint32_t size) {
   // A line consists of |m_line| followed by
   // |bytes[start..(next line break)]|.
-  size_t start = 0;
+  uint32_t start = 0;
   const unsigned char kBOM[] = {0xef, 0xbb, 0xbf};
-  for (size_t i = 0; i < size && !is_stopped_; ++i) {
+  for (uint32_t i = 0; i < size && !is_stopped_; ++i) {
     // As kBOM contains neither CR nor LF, we can think BOM and the line
     // break separately.
     if (is_recognizing_bom_ && line_.size() + (i - start) == arraysize(kBOM)) {
@@ -77,8 +77,8 @@
     event_type_ = g_null_atom;
     return;
   }
-  size_t field_name_end = line_.Find(':');
-  size_t field_value_start;
+  wtf_size_t field_name_end = line_.Find(':');
+  wtf_size_t field_value_start;
   if (field_name_end == WTF::kNotFound) {
     field_name_end = line_.size();
     field_value_start = field_name_end;
@@ -88,7 +88,7 @@
       ++field_value_start;
     }
   }
-  size_t field_value_size = line_.size() - field_value_start;
+  wtf_size_t field_value_size = line_.size() - field_value_start;
   String field_name = FromUTF8(line_.data(), field_name_end);
   if (field_name == "event") {
     event_type_ = AtomicString(
@@ -109,7 +109,8 @@
   }
   if (field_name == "retry") {
     bool has_only_digits = true;
-    for (size_t i = field_value_start; i < line_.size() && has_only_digits; ++i)
+    for (wtf_size_t i = field_value_start; i < line_.size() && has_only_digits;
+         ++i)
       has_only_digits = IsASCIIDigit(line_[i]);
     if (field_value_start == line_.size()) {
       client_->OnReconnectionTimeSet(EventSource::kDefaultReconnectDelay);
@@ -126,7 +127,7 @@
   // Unrecognized field name. Ignore!
 }
 
-String EventSourceParser::FromUTF8(const char* bytes, size_t size) {
+String EventSourceParser::FromUTF8(const char* bytes, uint32_t size) {
   return codec_->Decode(bytes, size, WTF::FlushBehavior::kDataEOF);
 }
 
diff --git a/third_party/blink/renderer/modules/eventsource/event_source_parser.h b/third_party/blink/renderer/modules/eventsource/event_source_parser.h
index 8c4ab1e..e3eb4e0 100644
--- a/third_party/blink/renderer/modules/eventsource/event_source_parser.h
+++ b/third_party/blink/renderer/modules/eventsource/event_source_parser.h
@@ -31,7 +31,7 @@
 
   EventSourceParser(const AtomicString& last_event_id, Client*);
 
-  void AddBytes(const char*, size_t);
+  void AddBytes(const char*, uint32_t);
   const AtomicString& LastEventId() const { return last_event_id_; }
   // Stop parsing. This can be called from Client::onMessageEvent.
   void Stop() { is_stopped_ = true; }
@@ -39,7 +39,7 @@
 
  private:
   void ParseLine();
-  String FromUTF8(const char* bytes, size_t);
+  String FromUTF8(const char* bytes, uint32_t);
 
   Vector<char> line_;
 
diff --git a/third_party/blink/renderer/modules/eventsource/event_source_parser_test.cc b/third_party/blink/renderer/modules/eventsource/event_source_parser_test.cc
index 0b10876..2370a04 100644
--- a/third_party/blink/renderer/modules/eventsource/event_source_parser_test.cc
+++ b/third_party/blink/renderer/modules/eventsource/event_source_parser_test.cc
@@ -95,7 +95,9 @@
         parser_(new EventSourceParser(AtomicString(), client_)) {}
   ~EventSourceParserTest() override = default;
 
-  void Enqueue(const char* data) { parser_->AddBytes(data, strlen(data)); }
+  void Enqueue(const char* data) {
+    parser_->AddBytes(data, static_cast<uint32_t>(strlen(data)));
+  }
   void EnqueueOneByOne(const char* data) {
     const char* p = data;
     while (*p != '\0')
diff --git a/third_party/blink/renderer/modules/exported/web_ax_object.cc b/third_party/blink/renderer/modules/exported/web_ax_object.cc
index 771f1f8..4f763635 100644
--- a/third_party/blink/renderer/modules/exported/web_ax_object.cc
+++ b/third_party/blink/renderer/modules/exported/web_ax_object.cc
@@ -90,8 +90,7 @@
   void AddObjectVectorAttribute(AXObjectVectorAttribute attribute,
                                 HeapVector<Member<AXObject>>& value) override {
     WebVector<WebAXObject> result(value.size());
-    for (size_t i = 0; i < value.size(); i++)
-      result[i] = WebAXObject(value[i]);
+    std::copy(value.begin(), value.end(), result.begin());
     attribute_map_.AddObjectVectorAttribute(
         static_cast<WebAXObjectVectorAttribute>(attribute), result);
   }
@@ -718,8 +717,8 @@
 
   AXObject::AXObjectVector radio_buttons = private_->RadioButtonsInGroup();
   WebVector<WebAXObject> web_radio_buttons(radio_buttons.size());
-  for (size_t i = 0; i < radio_buttons.size(); ++i)
-    web_radio_buttons[i] = WebAXObject(radio_buttons[i]);
+  std::copy(radio_buttons.begin(), radio_buttons.end(),
+            web_radio_buttons.begin());
   return web_radio_buttons;
 }
 
@@ -915,10 +914,9 @@
   WebString result = private_->GetName(name_from, &name_objects);
   out_name_from = name_from;
 
-  WebVector<WebAXObject> web_name_objects(name_objects.size());
-  for (size_t i = 0; i < name_objects.size(); i++)
-    web_name_objects[i] = WebAXObject(name_objects[i]);
-  out_name_objects.Swap(web_name_objects);
+  out_name_objects.reserve(name_objects.size());
+  out_name_objects.resize(name_objects.size());
+  std::copy(name_objects.begin(), name_objects.end(), out_name_objects.begin());
 
   return result;
 }
@@ -946,10 +944,10 @@
       private_->Description(name_from, description_from, &description_objects);
   out_description_from = description_from;
 
-  WebVector<WebAXObject> web_description_objects(description_objects.size());
-  for (size_t i = 0; i < description_objects.size(); i++)
-    web_description_objects[i] = WebAXObject(description_objects[i]);
-  out_description_objects.Swap(web_description_objects);
+  out_description_objects.reserve(description_objects.size());
+  out_description_objects.resize(description_objects.size());
+  std::copy(description_objects.begin(), description_objects.end(),
+            out_description_objects.begin());
 
   return result;
 }
@@ -1076,13 +1074,7 @@
 
   Vector<int> line_breaks_vector;
   private_->LineBreaks(line_breaks_vector);
-
-  size_t vector_size = line_breaks_vector.size();
-  WebVector<int> line_breaks_web_vector(vector_size);
-  for (size_t i = 0; i < vector_size; i++)
-    line_breaks_web_vector[i] = line_breaks_vector[i];
-  result.Swap(line_breaks_web_vector);
-
+  result = line_breaks_vector;
   return true;
 }
 
@@ -1169,14 +1161,9 @@
 
   AXObject::AXObjectVector headers;
   private_->RowHeaders(headers);
-
-  size_t header_count = headers.size();
-  WebVector<WebAXObject> result(header_count);
-
-  for (size_t i = 0; i < header_count; i++)
-    result[i] = WebAXObject(headers[i]);
-
-  row_header_elements.Swap(result);
+  row_header_elements.reserve(headers.size());
+  row_header_elements.resize(headers.size());
+  std::copy(headers.begin(), headers.end(), row_header_elements.begin());
 }
 
 unsigned WebAXObject::ColumnIndex() const {
@@ -1209,14 +1196,9 @@
 
   AXObject::AXObjectVector headers;
   private_->ColumnHeaders(headers);
-
-  size_t header_count = headers.size();
-  WebVector<WebAXObject> result(header_count);
-
-  for (size_t i = 0; i < header_count; i++)
-    result[i] = WebAXObject(headers[i]);
-
-  column_header_elements.Swap(result);
+  column_header_elements.reserve(headers.size());
+  column_header_elements.resize(headers.size());
+  std::copy(headers.begin(), headers.end(), column_header_elements.begin());
 }
 
 unsigned WebAXObject::CellColumnIndex() const {
@@ -1307,7 +1289,7 @@
   WebVector<ax::mojom::MarkerType> web_marker_types(marker_types.size());
   WebVector<int> start_offsets(marker_ranges.size());
   WebVector<int> end_offsets(marker_ranges.size());
-  for (size_t i = 0; i < marker_types.size(); ++i) {
+  for (wtf_size_t i = 0; i < marker_types.size(); ++i) {
     web_marker_types[i] = ToAXMarkerType(marker_types[i]);
     DCHECK(marker_ranges[i].IsValid());
     DCHECK_EQ(marker_ranges[i].Start().ContainerObject(),
@@ -1327,12 +1309,7 @@
 
   Vector<int> offsets_vector;
   private_->TextCharacterOffsets(offsets_vector);
-
-  size_t vector_size = offsets_vector.size();
-  WebVector<int> offsets_web_vector(vector_size);
-  for (size_t i = 0; i < vector_size; i++)
-    offsets_web_vector[i] = offsets_vector[i];
-  offsets.Swap(offsets_web_vector);
+  offsets = offsets_vector;
 }
 
 void WebAXObject::GetWordBoundaries(WebVector<int>& starts,
@@ -1345,7 +1322,7 @@
 
   WebVector<int> word_start_offsets(word_boundaries.size());
   WebVector<int> word_end_offsets(word_boundaries.size());
-  for (size_t i = 0; i < word_boundaries.size(); ++i) {
+  for (wtf_size_t i = 0; i < word_boundaries.size(); ++i) {
     DCHECK(word_boundaries[i].IsValid());
     DCHECK_EQ(word_boundaries[i].Start().ContainerObject(),
               word_boundaries[i].End().ContainerObject());
diff --git a/third_party/blink/renderer/modules/exported/web_idb_key.cc b/third_party/blink/renderer/modules/exported/web_idb_key.cc
index 9b18332..600bfce 100644
--- a/third_party/blink/renderer/modules/exported/web_idb_key.cc
+++ b/third_party/blink/renderer/modules/exported/web_idb_key.cc
@@ -28,6 +28,7 @@
 #include "third_party/blink/public/platform/modules/indexeddb/web_idb_key.h"
 
 #include "third_party/blink/renderer/modules/indexeddb/idb_key.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 
 namespace blink {
 
@@ -36,7 +37,7 @@
 }
 
 WebIDBKeyView WebIDBKeyArrayView::operator[](size_t index) const {
-  return WebIDBKeyView(private_->Array()[index].get());
+  return WebIDBKeyView(private_->Array()[SafeCast<wtf_size_t>(index)].get());
 }
 
 WebIDBKeyType WebIDBKeyView::KeyType() const {
@@ -69,7 +70,7 @@
 
 WebIDBKey WebIDBKey::CreateArray(WebVector<WebIDBKey> array) {
   IDBKey::KeyArray keys;
-  keys.ReserveCapacity(array.size());
+  keys.ReserveCapacity(SafeCast<wtf_size_t>(array.size()));
   for (WebIDBKey& key : array) {
     DCHECK(key.View().KeyType() != kWebIDBKeyTypeNull);
     keys.emplace_back(key.ReleaseIdbKey());
diff --git a/third_party/blink/renderer/modules/filesystem/dom_file_path.cc b/third_party/blink/renderer/modules/filesystem/dom_file_path.cc
index c50c536..d1ca351 100644
--- a/third_party/blink/renderer/modules/filesystem/dom_file_path.cc
+++ b/third_party/blink/renderer/modules/filesystem/dom_file_path.cc
@@ -83,22 +83,22 @@
   Vector<String> components;
   Vector<String> canonicalized;
   path.Split(DOMFilePath::kSeparator, components);
-  for (size_t i = 0; i < components.size(); ++i) {
-    if (components[i] == ".")
+  for (const auto& component : components) {
+    if (component == ".")
       continue;
-    if (components[i] == "..") {
+    if (component == "..") {
       if (canonicalized.size() > 0)
         canonicalized.pop_back();
       continue;
     }
-    canonicalized.push_back(components[i]);
+    canonicalized.push_back(component);
   }
   if (canonicalized.IsEmpty())
     return DOMFilePath::kRoot;
   StringBuilder result;
-  for (size_t i = 0; i < canonicalized.size(); ++i) {
+  for (const auto& component : canonicalized) {
     result.Append(DOMFilePath::kSeparator);
-    result.Append(canonicalized[i]);
+    result.Append(component);
   }
   return result.ToString();
 }
@@ -120,13 +120,10 @@
   // ".." or "." is likely an attempt to break out of the sandbox.
   Vector<String> components;
   path.Split(DOMFilePath::kSeparator, components);
-  for (size_t i = 0; i < components.size(); ++i) {
-    if (components[i] == ".")
-      return false;
-    if (components[i] == "..")
-      return false;
-  }
-  return true;
+  return std::none_of(components.begin(), components.end(),
+                      [](const String& component) {
+                        return component == "." || component == "..";
+                      });
 }
 
 bool DOMFilePath::IsValidName(const String& name) {
diff --git a/third_party/blink/renderer/modules/filesystem/file_writer.cc b/third_party/blink/renderer/modules/filesystem/file_writer.cc
index 56e910f..6e0cc06 100644
--- a/third_party/blink/renderer/modules/filesystem/file_writer.cc
+++ b/third_party/blink/renderer/modules/filesystem/file_writer.cc
@@ -179,7 +179,7 @@
     operation_in_progress_ = kOperationNone;
   }
 
-  int num_aborts = num_aborts_;
+  long long num_aborts = num_aborts_;
   // We could get an abort in the handler for this event. If we do, it's
   // already handled the cleanup and signalCompletion call.
   double now = CurrentTimeMS();
diff --git a/third_party/blink/renderer/modules/filesystem/local_file_system.cc b/third_party/blink/renderer/modules/filesystem/local_file_system.cc
index 0a7e003..f991a329 100644
--- a/third_party/blink/renderer/modules/filesystem/local_file_system.cc
+++ b/third_party/blink/renderer/modules/filesystem/local_file_system.cc
@@ -51,6 +51,7 @@
 #include "third_party/blink/renderer/platform/async_file_system_callbacks.h"
 #include "third_party/blink/renderer/platform/content_setting_callbacks.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 
 namespace blink {
 
@@ -125,7 +126,7 @@
     ScriptState::Scope scope(resolver_->GetScriptState());
     if (return_multiple_) {
       Vector<ScriptPromise> result;
-      result.ReserveInitialCapacity(entries.size());
+      result.ReserveInitialCapacity(SafeCast<wtf_size_t>(entries.size()));
       for (const auto& entry : entries)
         result.emplace_back(CreateFileHandle(entry));
       resolver_->Resolve(ScriptPromise::All(resolver_->GetScriptState(), result)
diff --git a/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc b/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc
index 51ace37..66209e1 100644
--- a/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc
+++ b/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc
@@ -71,12 +71,12 @@
   // A button press counts as a user activation if the button's value is greater
   // than the activation threshold. A threshold is used so that analog buttons
   // or triggers do not generate an activation from a light touch.
-  for (size_t pad_index = 0; pad_index < gamepads->length(); ++pad_index) {
+  for (wtf_size_t pad_index = 0; pad_index < gamepads->length(); ++pad_index) {
     Gamepad* pad = gamepads->item(pad_index);
     if (pad) {
       const GamepadButtonVector& buttons = pad->buttons();
-      for (size_t i = 0; i < buttons.size(); ++i) {
-        double value = buttons.at(i)->value();
+      for (auto button : buttons) {
+        double value = button->value();
         if (value > kButtonActivationThreshold)
           return true;
       }
@@ -88,7 +88,7 @@
 }  // namespace
 
 template <typename T>
-static void SampleGamepad(size_t index,
+static void SampleGamepad(unsigned index,
                           T& gamepad,
                           const device::Gamepad& device_gamepad,
                           const TimeTicks& navigation_start) {
@@ -144,7 +144,7 @@
 
   GamepadDispatcher::Instance().SampleGamepads(gamepads);
 
-  for (size_t i = 0; i < device::Gamepads::kItemsLengthCap; ++i) {
+  for (unsigned i = 0; i < device::Gamepads::kItemsLengthCap; ++i) {
     device::Gamepad& web_gamepad = gamepads.items[i];
 
     bool hide_xr_gamepad = false;
@@ -390,7 +390,7 @@
 bool NavigatorGamepad::CheckConnectedGamepads(GamepadList* old_gamepads,
                                               GamepadList* new_gamepads) {
   int disconnection_count = 0;
-  for (size_t i = 0; i < device::Gamepads::kItemsLengthCap; ++i) {
+  for (unsigned i = 0; i < device::Gamepads::kItemsLengthCap; ++i) {
     Gamepad* old_gamepad = old_gamepads ? old_gamepads->item(i) : nullptr;
     Gamepad* new_gamepad = new_gamepads->item(i);
     bool connected, disconnected;
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_database.cc b/third_party/blink/renderer/modules/indexeddb/idb_database.cc
index 6a1da96d..db3a230 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_database.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_database.cc
@@ -50,6 +50,7 @@
 #include "third_party/blink/renderer/platform/histogram.h"
 #include "third_party/blink/renderer/platform/wtf/assertions.h"
 #include "third_party/blink/renderer/platform/wtf/atomics.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 
 #include <limits>
 #include <memory>
@@ -192,7 +193,8 @@
     WebVector<WebIDBObservation> web_observations,
     const WebIDBDatabaseCallbacks::TransactionMap& transactions) {
   HeapVector<Member<IDBObservation>> observations;
-  observations.ReserveInitialCapacity(web_observations.size());
+  observations.ReserveInitialCapacity(
+      SafeCast<wtf_size_t>(web_observations.size()));
   for (WebIDBObservation& web_observation : web_observations) {
     observations.emplace_back(
         IDBObservation::Create(std::move(web_observation), isolate_));
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_event_dispatcher.cc b/third_party/blink/renderer/modules/indexeddb/idb_event_dispatcher.cc
index 67f5ea8..5504e12 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_event_dispatcher.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_event_dispatcher.cc
@@ -30,17 +30,18 @@
 
 #include "third_party/blink/renderer/modules/event_modules.h"
 #include "third_party/blink/renderer/modules/event_target_modules.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 
 namespace blink {
 
 DispatchEventResult IDBEventDispatcher::Dispatch(
     Event& event,
     HeapVector<Member<EventTarget>>& event_targets) {
-  size_t size = event_targets.size();
+  wtf_size_t size = event_targets.size();
   DCHECK(size);
 
   event.SetEventPhase(Event::kCapturingPhase);
-  for (size_t i = size - 1; i; --i) {  // Don't do the first element.
+  for (wtf_size_t i = size - 1; i; --i) {  // Don't do the first element.
     event.SetCurrentTarget(event_targets[i].Get());
     event_targets[i]->FireEventListeners(event);
     if (event.PropagationStopped())
@@ -54,7 +55,7 @@
     goto doneDispatching;
 
   event.SetEventPhase(Event::kBubblingPhase);
-  for (size_t i = 1; i < size; ++i) {  // Don't do the first element.
+  for (wtf_size_t i = 1; i < size; ++i) {  // Don't do the first element.
     event.SetCurrentTarget(event_targets[i].Get());
     event_targets[i]->FireEventListeners(event);
     if (event.PropagationStopped() || event.cancelBubble())
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_key.cc b/third_party/blink/renderer/modules/indexeddb/idb_key.cc
index 114db34..8ed421c 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_key.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_key.cc
@@ -41,8 +41,8 @@
     return false;
 
   if (type_ == kArrayType) {
-    for (size_t i = 0; i < array_.size(); i++) {
-      if (!array_[i]->IsValid())
+    for (const auto& element : array_) {
+      if (!element->IsValid())
         return false;
     }
   }
@@ -67,7 +67,8 @@
 
   switch (type_) {
     case kArrayType:
-      for (size_t i = 0; i < array_.size() && i < other->array_.size(); ++i) {
+      for (wtf_size_t i = 0; i < array_.size() && i < other->array_.size();
+           ++i) {
         if (int result = array_[i]->Compare(other->array_[i].get()))
           return result;
       }
@@ -123,7 +124,7 @@
         return static_cast<IDBKey*>(a)->IsLessThan(static_cast<IDBKey*>(b));
       });
   const auto end = std::unique(result.begin(), result.end());
-  DCHECK_LE(static_cast<size_t>(end - result.begin()), result.size());
+  DCHECK_LE(static_cast<wtf_size_t>(end - result.begin()), result.size());
   result.resize(end - result.begin());
 
   return result;
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_key_path.cc b/third_party/blink/renderer/modules/indexeddb/idb_key_path.cc
index 3e1c12b..af493f27 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_key_path.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_key_path.cc
@@ -61,12 +61,12 @@
 }
 
 bool IsIdentifier(const String& s) {
-  size_t length = s.length();
+  wtf_size_t length = s.length();
   if (!length)
     return false;
   if (!IsIdentifierStartCharacter(s[0]))
     return false;
-  for (size_t i = 1; i < length; ++i) {
+  for (wtf_size_t i = 1; i < length; ++i) {
     if (!IsIdentifierCharacter(s[i]))
       return false;
   }
@@ -93,8 +93,8 @@
   }
 
   key_path.Split('.', /*allow_empty_entries=*/true, elements);
-  for (size_t i = 0; i < elements.size(); ++i) {
-    if (!IsIdentifier(elements[i])) {
+  for (const auto& element : elements) {
+    if (!IsIdentifier(element)) {
       error = kIDBKeyPathParseErrorIdentifier;
       return;
     }
@@ -110,8 +110,8 @@
 IDBKeyPath::IDBKeyPath(const Vector<class String>& array)
     : type_(kArrayType), array_(array) {
 #if DCHECK_IS_ON()
-  for (size_t i = 0; i < array_.size(); ++i)
-    DCHECK(!array_[i].IsNull());
+  for (const auto& element : array_)
+    DCHECK(!element.IsNull());
 #endif
 }
 
@@ -127,8 +127,8 @@
     type_ = kArrayType;
     array_ = key_path.GetAsStringSequence();
 #if DCHECK_IS_ON()
-    for (size_t i = 0; i < array_.size(); ++i)
-      DCHECK(!array_[i].IsNull());
+    for (const auto& element : array_)
+      DCHECK(!element.IsNull());
 #endif
   }
 }
@@ -177,8 +177,8 @@
     case kArrayType:
       if (array_.IsEmpty())
         return false;
-      for (size_t i = 0; i < array_.size(); ++i) {
-        if (!IDBIsValidKeyPath(array_[i]))
+      for (const auto& element : array_) {
+        if (!IDBIsValidKeyPath(element))
           return false;
       }
       return true;
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_key_path_test.cc b/third_party/blink/renderer/modules/indexeddb/idb_key_path_test.cc
index 62dfa582..92e1f492 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_key_path_test.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_key_path_test.cc
@@ -49,7 +49,7 @@
   if (error != kIDBKeyPathParseErrorNone)
     return;
   ASSERT_EQ(expected.size(), key_path_elements.size());
-  for (size_t i = 0; i < expected.size(); ++i)
+  for (wtf_size_t i = 0; i < expected.size(); ++i)
     ASSERT_TRUE(expected[i] == key_path_elements[i]) << i;
 }
 
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_object_store.cc b/third_party/blink/renderer/modules/indexeddb/idb_object_store.cc
index edacac1..3884f42 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_object_store.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_object_store.cc
@@ -29,6 +29,7 @@
 
 #include "base/feature_list.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/numerics/safe_conversions.h"
 #include "third_party/blink/public/platform/modules/indexeddb/web_idb_database.h"
 #include "third_party/blink/public/platform/modules/indexeddb/web_idb_key.h"
 #include "third_party/blink/public/platform/modules/indexeddb/web_idb_key_range.h"
@@ -568,8 +569,10 @@
         script_state->GetIsolate(), *it.value, clone));
   }
   // Records 1KB to 1GB.
-  UMA_HISTOGRAM_COUNTS_1M("WebCore.IndexedDB.PutValueSize2",
-                          value_wrapper.DataLengthBeforeWrapInBytes() / 1024);
+  UMA_HISTOGRAM_COUNTS_1M(
+      "WebCore.IndexedDB.PutValueSize2",
+      base::saturated_cast<base::HistogramBase::Sample>(
+          value_wrapper.DataLengthBeforeWrapInBytes() / 1024));
 
   IDBRequest* request = IDBRequest::Create(
       script_state, source, transaction_.Get(), std::move(metrics));
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_request.cc b/third_party/blink/renderer/modules/indexeddb/idb_request.cc
index 9729f0d..765b626 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_request.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_request.cc
@@ -430,8 +430,8 @@
   }
 
   DOMStringList* dom_string_list = DOMStringList::Create();
-  for (size_t i = 0; i < string_list.size(); ++i)
-    dom_string_list->Append(string_list[i]);
+  for (const auto& item : string_list)
+    dom_string_list->Append(item);
   EnqueueResultInternal(IDBAny::Create(dom_string_list));
   metrics_.RecordAndReset();
 }
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_test_helper.cc b/third_party/blink/renderer/modules/indexeddb/idb_test_helper.cc
index 7e8b391..cf37a961 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_test_helper.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_test_helper.cc
@@ -36,9 +36,9 @@
 
 std::unique_ptr<IDBValue> CreateIDBValueForTesting(v8::Isolate* isolate,
                                                    bool create_wrapped_value) {
-  size_t element_count = create_wrapped_value ? 16 : 2;
+  uint32_t element_count = create_wrapped_value ? 16 : 2;
   v8::Local<v8::Array> v8_array = v8::Array::New(isolate, element_count);
-  for (size_t i = 0; i < element_count; ++i)
+  for (uint32_t i = 0; i < element_count; ++i)
     v8_array->Set(i, v8::True(isolate));
 
   NonThrowableExceptionState non_throwable_exception_state;
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_value.cc b/third_party/blink/renderer/modules/indexeddb/idb_value.cc
index cf13a66..21626735 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_value.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_value.cc
@@ -13,6 +13,7 @@
 #include "third_party/blink/public/platform/web_blob_info.h"
 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
 #include "third_party/blink/renderer/platform/blob/blob_data.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 #include "v8/include/v8.h"
 
 namespace blink {
@@ -20,7 +21,7 @@
 IDBValue::IDBValue(const WebData& data,
                    const WebVector<WebBlobInfo>& web_blob_info)
     : data_(data) {
-  blob_info_.ReserveInitialCapacity(web_blob_info.size());
+  blob_info_.ReserveInitialCapacity(SafeCast<wtf_size_t>(web_blob_info.size()));
 
   for (const WebBlobInfo& info : web_blob_info) {
     blob_info_.push_back(info);
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_value_wrapping.cc b/third_party/blink/renderer/modules/indexeddb/idb_value_wrapping.cc
index 7630571..5ab0aac 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_value_wrapping.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_value_wrapping.cc
@@ -13,6 +13,7 @@
 #include "third_party/blink/renderer/modules/indexeddb/idb_request.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_value.h"
 #include "third_party/blink/renderer/platform/blob/blob_data.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
@@ -136,7 +137,7 @@
   DCHECK(owns_wire_bytes_) << __func__ << " called after TakeWireBytes()";
 #endif  // DCHECK_IS_ON()
 
-  unsigned wire_data_size = wire_data_.size();
+  size_t wire_data_size = wire_data_.size();
   if (wire_data_size <= max_bytes)
     return false;
 
@@ -154,7 +155,8 @@
   wire_data_buffer_.push_back(kVersionTag);
   wire_data_buffer_.push_back(kRequiresProcessingSSVPseudoVersion);
   wire_data_buffer_.push_back(kReplaceWithBlob);
-  IDBValueWrapper::WriteVarInt(wire_data_size, wire_data_buffer_);
+  IDBValueWrapper::WriteVarInt(SafeCast<unsigned>(wire_data_size),
+                               wire_data_buffer_);
   IDBValueWrapper::WriteVarInt(serialized_value_->BlobDataHandles().size(),
                                wire_data_buffer_);
 
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_value_wrapping_test.cc b/third_party/blink/renderer/modules/indexeddb/idb_value_wrapping_test.cc
index e52b0d13..f78b2600 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_value_wrapping_test.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_value_wrapping_test.cc
@@ -184,7 +184,7 @@
 // Friend class of IDBValueUnwrapper with access to its internals.
 class IDBValueUnwrapperReadTestHelper {
  public:
-  void ReadVarInt(const char* start, size_t buffer_size) {
+  void ReadVarInt(const char* start, uint32_t buffer_size) {
     IDBValueUnwrapper unwrapper;
 
     const uint8_t* buffer_start = reinterpret_cast<const uint8_t*>(start);
@@ -197,10 +197,10 @@
         << "ReadVarInt should not change end_";
     ASSERT_LE(unwrapper.current_, unwrapper.end_)
         << "ReadVarInt should not move current_ past end_";
-    consumed_bytes_ = unwrapper.current_ - buffer_start;
+    consumed_bytes_ = static_cast<uint32_t>(unwrapper.current_ - buffer_start);
   }
 
-  void ReadBytes(const char* start, size_t buffer_size) {
+  void ReadBytes(const char* start, uint32_t buffer_size) {
     IDBValueUnwrapper unwrapper;
 
     const uint8_t* buffer_start = reinterpret_cast<const uint8_t*>(start);
@@ -212,7 +212,7 @@
     ASSERT_EQ(unwrapper.end_, buffer_end) << "ReadBytes should not change end_";
     ASSERT_LE(unwrapper.current_, unwrapper.end_)
         << "ReadBytes should not move current_ past end_";
-    consumed_bytes_ = unwrapper.current_ - buffer_start;
+    consumed_bytes_ = static_cast<uint32_t>(unwrapper.current_ - buffer_start);
   }
 
   bool success() { return success_; }
@@ -488,7 +488,8 @@
   wrapped_value->SetIsolate(scope.GetIsolate());
   EXPECT_TRUE(IDBValueUnwrapper::IsWrapped(wrapped_value.get()));
 
-  Vector<char> wrapped_marker_bytes(wrapped_marker_buffer->size());
+  Vector<char> wrapped_marker_bytes(
+      static_cast<wtf_size_t>(wrapped_marker_buffer->size()));
   ASSERT_TRUE(wrapped_marker_buffer->GetBytes(wrapped_marker_bytes.data(),
                                               wrapped_marker_bytes.size()));
 
@@ -496,7 +497,7 @@
   // Truncating the array to fewer than 3 bytes should cause IsWrapped() to
   // return false.
   ASSERT_LT(3U, wrapped_marker_bytes.size());
-  for (size_t i = 0; i < 3; ++i) {
+  for (wtf_size_t i = 0; i < 3; ++i) {
     std::unique_ptr<IDBValue> mutant_value = IDBValue::Create(
         SharedBuffer::Create(wrapped_marker_bytes.data(), i), blob_infos);
     mutant_value->SetIsolate(scope.GetIsolate());
@@ -507,7 +508,7 @@
   // IsWrapped() looks at the first 3 bytes in the value. Flipping any bit in
   // these 3 bytes should cause IsWrapped() to return false.
   ASSERT_LT(3U, wrapped_marker_bytes.size());
-  for (size_t i = 0; i < 3; ++i) {
+  for (wtf_size_t i = 0; i < 3; ++i) {
     for (int j = 0; j < 8; ++j) {
       char mask = 1 << j;
       wrapped_marker_bytes[i] ^= mask;
diff --git a/third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.cc b/third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.cc
index d077611..d19dca27 100644
--- a/third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.cc
+++ b/third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.cc
@@ -141,7 +141,7 @@
     DOMStringList* database_names_list = request_result->DomStringList();
     std::unique_ptr<protocol::Array<String>> database_names =
         protocol::Array<String>::create();
-    for (size_t i = 0; i < database_names_list->length(); ++i)
+    for (uint32_t i = 0; i < database_names_list->length(); ++i)
       database_names->addItem(database_names_list->item(i));
     request_callback_->sendSuccess(std::move(database_names));
   }
@@ -401,7 +401,7 @@
       std::unique_ptr<protocol::Array<String>> array =
           protocol::Array<String>::create();
       const Vector<String>& string_array = idb_key_path.Array();
-      for (size_t i = 0; i < string_array.size(); ++i)
+      for (wtf_size_t i = 0; i < string_array.size(); ++i)
         array->addItem(string_array[i]);
       key_path->setArray(std::move(array));
       break;
@@ -463,7 +463,7 @@
     std::unique_ptr<DatabaseWithObjectStores> result =
         DatabaseWithObjectStores::create()
             .setName(idb_database->name())
-            .setVersion(idb_database->version())
+            .setVersion(static_cast<int>(idb_database->version()))
             .setObjectStores(std::move(object_stores))
             .build();
 
diff --git a/third_party/blink/renderer/modules/indexeddb/web_idb_callbacks_impl.cc b/third_party/blink/renderer/modules/indexeddb/web_idb_callbacks_impl.cc
index bebb7e6..fe63972a 100644
--- a/third_party/blink/renderer/modules/indexeddb/web_idb_callbacks_impl.cc
+++ b/third_party/blink/renderer/modules/indexeddb/web_idb_callbacks_impl.cc
@@ -43,6 +43,7 @@
 #include "third_party/blink/renderer/modules/indexeddb/idb_request.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_value.h"
 #include "third_party/blink/renderer/platform/shared_buffer.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 
 using blink::WebIDBCursor;
 using blink::WebIDBDatabase;
@@ -154,7 +155,7 @@
 
   probe::AsyncTask async_task(request_->GetExecutionContext(), this, "success");
   Vector<std::unique_ptr<IDBValue>> idb_values;
-  idb_values.ReserveInitialCapacity(values.size());
+  idb_values.ReserveInitialCapacity(SafeCast<wtf_size_t>(values.size()));
   for (WebIDBValue& value : values) {
     std::unique_ptr<IDBValue> idb_value = value.ReleaseIdbValue();
     idb_value->SetIsolate(request_->GetIsolate());
diff --git a/third_party/blink/renderer/modules/keyboard/keyboard_layout_map.h b/third_party/blink/renderer/modules/keyboard/keyboard_layout_map.h
index d332924c..d6980c28 100644
--- a/third_party/blink/renderer/modules/keyboard/keyboard_layout_map.h
+++ b/third_party/blink/renderer/modules/keyboard/keyboard_layout_map.h
@@ -23,7 +23,7 @@
   const HashMap<String, String>& Map() const { return layout_map_; }
 
   // IDL attributes / methods
-  size_t size() const { return layout_map_.size(); }
+  uint32_t size() const { return layout_map_.size(); }
 
   void Trace(blink::Visitor* visitor) override {
     ScriptWrappable::Trace(visitor);
diff --git a/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc b/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc
index 503294a..93c78e5 100644
--- a/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc
+++ b/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc
@@ -43,7 +43,7 @@
   if (std::isfinite(result))
     return result > 0 ? result : std::numeric_limits<double>::quiet_NaN();
 
-  size_t slash_position = fps_str.find('/');
+  wtf_size_t slash_position = fps_str.find('/');
   if (slash_position == kNotFound)
     return std::numeric_limits<double>::quiet_NaN();
 
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate.cc b/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate.cc
index f11ee55..0f49a4b 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_orientation_lock_delegate.cc
@@ -377,7 +377,7 @@
 
   // device_orientation_angle snapped to nearest multiple of 90.
   int device_orientation_angle90 =
-      std::lround(device_orientation_angle / 90) * 90;
+      static_cast<int>(std::lround(device_orientation_angle / 90) * 90);
 
   // To be considered portrait or landscape, allow the device to be rotated 23
   // degrees (chosen to approximately match Android's behavior) to either side
diff --git a/third_party/blink/renderer/modules/mediacapturefromelement/html_media_element_capture.cc b/third_party/blink/renderer/modules/mediacapturefromelement/html_media_element_capture.cc
index fa721b2..c396e06 100644
--- a/third_party/blink/renderer/modules/mediacapturefromelement/html_media_element_capture.cc
+++ b/third_party/blink/renderer/modules/mediacapturefromelement/html_media_element_capture.cc
@@ -84,11 +84,11 @@
             : MediaStreamRegistry::Registry().LookupMediaStreamDescriptor(
                   media_element_->currentSrc().GetString());
     DCHECK(descriptor);
-    for (size_t i = 0; i < descriptor->NumberOfAudioComponents(); i++) {
+    for (unsigned i = 0; i < descriptor->NumberOfAudioComponents(); i++) {
       media_stream_->AddTrackByComponentAndFireEvents(
           descriptor->AudioComponent(i));
     }
-    for (size_t i = 0; i < descriptor->NumberOfVideoComponents(); i++) {
+    for (unsigned i = 0; i < descriptor->NumberOfVideoComponents(); i++) {
       media_stream_->AddTrackByComponentAndFireEvents(
           descriptor->VideoComponent(i));
     }
diff --git a/third_party/blink/renderer/modules/mediasession/media_metadata.cc b/third_party/blink/renderer/modules/mediasession/media_metadata.cc
index 831942c..caaa6a4 100644
--- a/third_party/blink/renderer/modules/mediasession/media_metadata.cc
+++ b/third_party/blink/renderer/modules/mediasession/media_metadata.cc
@@ -55,7 +55,7 @@
     ScriptState* script_state) const {
   Vector<v8::Local<v8::Value>> result(artwork_.size());
 
-  for (size_t i = 0; i < artwork_.size(); ++i) {
+  for (wtf_size_t i = 0; i < artwork_.size(); ++i) {
     result[i] = FreezeV8Object(ToV8(artwork_[i], script_state),
                                script_state->GetIsolate());
   }
diff --git a/third_party/blink/renderer/modules/mediasource/media_source.cc b/third_party/blink/renderer/modules/mediasource/media_source.cc
index 1fba87a..2591f34 100644
--- a/third_party/blink/renderer/modules/mediasource/media_source.cc
+++ b/third_party/blink/renderer/modules/mediasource/media_source.cc
@@ -269,7 +269,7 @@
   active_source_buffers_->Clear();
 
   // Clear SourceBuffer references to this object.
-  for (unsigned long i = 0; i < source_buffers_->length(); ++i)
+  for (unsigned i = 0; i < source_buffers_->length(); ++i)
     source_buffers_->item(i)->RemovedFromMediaSource();
   source_buffers_->Clear();
 
@@ -280,7 +280,7 @@
 
 bool MediaSource::IsUpdating() const {
   // Return true if any member of |m_sourceBuffers| is updating.
-  for (unsigned long i = 0; i < source_buffers_->length(); ++i) {
+  for (unsigned i = 0; i < source_buffers_->length(); ++i) {
     if (source_buffers_->item(i)->updating())
       return true;
   }
@@ -379,7 +379,7 @@
   // Implements MediaSource algorithm for HTMLMediaElement.buffered.
   // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#htmlmediaelement-extensions
   HeapVector<Member<TimeRanges>> ranges(active_source_buffers_->length());
-  for (size_t i = 0; i < active_source_buffers_->length(); ++i)
+  for (unsigned i = 0; i < active_source_buffers_->length(); ++i)
     ranges[i] = active_source_buffers_->item(i)->buffered(ASSERT_NO_EXCEPTION);
 
   // 1. If activeSourceBuffers.length equals 0 then return an empty TimeRanges
@@ -391,7 +391,7 @@
   //    SourceBuffer object in activeSourceBuffers.
   // 3. Let highest end time be the largest range end time in the active ranges.
   double highest_end_time = -1;
-  for (size_t i = 0; i < ranges.size(); ++i) {
+  for (wtf_size_t i = 0; i < ranges.size(); ++i) {
     unsigned length = ranges[i]->length();
     if (length)
       highest_end_time = std::max(
@@ -409,7 +409,7 @@
   // 5. For each SourceBuffer object in activeSourceBuffers run the following
   //    steps:
   bool ended = readyState() == EndedKeyword();
-  for (size_t i = 0; i < ranges.size(); ++i) {
+  for (wtf_size_t i = 0; i < ranges.size(); ++i) {
     // 5.1 Let source ranges equal the ranges returned by the buffered attribute
     //     on the current SourceBuffer.
     TimeRanges* source_ranges = ranges[i].Get();
@@ -545,7 +545,7 @@
   // media are disallowed. When truncation is necessary, use remove() to
   // reduce the buffered range before updating duration.
   double highest_buffered_presentation_timestamp = 0;
-  for (size_t i = 0; i < source_buffers_->length(); ++i) {
+  for (unsigned i = 0; i < source_buffers_->length(); ++i) {
     highest_buffered_presentation_timestamp =
         std::max(highest_buffered_presentation_timestamp,
                  source_buffers_->item(i)->HighestPresentationTimestamp());
@@ -582,7 +582,7 @@
     // Deprecated behavior: if the new duration is less than old duration,
     // then call remove(new duration, old duration) on all all objects in
     // sourceBuffers.
-    for (size_t i = 0; i < source_buffers_->length(); ++i)
+    for (unsigned i = 0; i < source_buffers_->length(); ++i)
       source_buffers_->item(i)->remove(new_duration, old_duration,
                                        ASSERT_NO_EXCEPTION);
   }
@@ -716,10 +716,10 @@
   // SourceBuffer transitions to active are not guaranteed to occur in the
   // same order as buffers in |m_sourceBuffers|, so this method needs to
   // insert |sourceBuffer| into |m_activeSourceBuffers|.
-  size_t index_in_source_buffers = source_buffers_->Find(source_buffer);
+  wtf_size_t index_in_source_buffers = source_buffers_->Find(source_buffer);
   DCHECK(index_in_source_buffers != kNotFound);
 
-  size_t insert_position = 0;
+  wtf_size_t insert_position = 0;
   while (insert_position < active_source_buffers_->length() &&
          source_buffers_->Find(active_source_buffers_->item(insert_position)) <
              index_in_source_buffers) {
diff --git a/third_party/blink/renderer/modules/mediasource/source_buffer.cc b/third_party/blink/renderer/modules/mediasource/source_buffer.cc
index 8007d2c..a495b2e 100644
--- a/third_party/blink/renderer/modules/mediasource/source_buffer.cc
+++ b/third_party/blink/renderer/modules/mediasource/source_buffer.cc
@@ -939,7 +939,7 @@
     //   tracks), then the Track IDs match the ones in the first initialization
     //   segment.
     if (tracks_match_first_init_segment && new_audio_tracks.size() > 1) {
-      for (size_t i = 0; i < new_audio_tracks.size(); ++i) {
+      for (wtf_size_t i = 0; i < new_audio_tracks.size(); ++i) {
         const String& new_track_id = new_video_tracks[i].id;
         if (new_track_id !=
             String(audioTracks().AnonymousIndexedGetter(i)->id())) {
@@ -950,7 +950,7 @@
     }
 
     if (tracks_match_first_init_segment && new_video_tracks.size() > 1) {
-      for (size_t i = 0; i < new_video_tracks.size(); ++i) {
+      for (wtf_size_t i = 0; i < new_video_tracks.size(); ++i) {
         const String& new_track_id = new_video_tracks[i].id;
         if (new_track_id !=
             String(videoTracks().AnonymousIndexedGetter(i)->id())) {
@@ -1312,7 +1312,7 @@
   // 1. Run the segment parser loop algorithm.
   // Step 2 doesn't apply since we run Step 1 synchronously here.
   DCHECK_GE(pending_append_data_.size(), pending_append_data_offset_);
-  size_t append_size =
+  wtf_size_t append_size =
       pending_append_data_.size() - pending_append_data_offset_;
 
   // Impose an arbitrary max size for a single append() call so that an append
@@ -1320,13 +1320,12 @@
   // by looking at YouTube SourceBuffer usage across a variety of bitrates.
   // This value allows relatively large appends while keeping append() call
   // duration in the  ~5-15ms range.
-  const size_t kMaxAppendSize = 128 * 1024;
+  const wtf_size_t kMaxAppendSize = 128 * 1024;
   if (append_size > kMaxAppendSize)
     append_size = kMaxAppendSize;
 
   TRACE_EVENT_ASYNC_STEP_INTO1("media", "SourceBuffer::appendBuffer", this,
-                               "appending", "appendSize",
-                               static_cast<unsigned>(append_size));
+                               "appending", "appendSize", append_size);
 
   // |zero| is used for 0 byte appends so we always have a valid pointer.
   // We need to convey all appends, even 0 byte ones to |m_webSourceBuffer|
diff --git a/third_party/blink/renderer/modules/nfc/nfc.cc b/third_party/blink/renderer/modules/nfc/nfc.cc
index c4c5ea0..8584cd83 100644
--- a/third_party/blink/renderer/modules/nfc/nfc.cc
+++ b/third_party/blink/renderer/modules/nfc/nfc.cc
@@ -270,7 +270,7 @@
     NFCMessagePtr messagePtr = NFCMessage::New();
     messagePtr->url = message.url();
     messagePtr->data.resize(message.records().size());
-    for (size_t i = 0; i < message.records().size(); ++i) {
+    for (wtf_size_t i = 0; i < message.records().size(); ++i) {
       NFCRecordPtr record = NFCRecord::From(message.records()[i]);
       if (record.is_null())
         return nullptr;
@@ -619,7 +619,7 @@
   NFCMessage nfc_message;
   nfc_message.setURL(message->url);
   blink::HeapVector<NFCRecord> records;
-  for (size_t i = 0; i < message->data.size(); ++i)
+  for (wtf_size_t i = 0; i < message->data.size(); ++i)
     records.push_back(ToNFCRecord(script_state, message->data[i]));
   nfc_message.setRecords(records);
   return nfc_message;
@@ -627,7 +627,7 @@
 
 size_t GetNFCMessageSize(const device::mojom::blink::NFCMessagePtr& message) {
   size_t message_size = message->url.CharactersSizeInBytes();
-  for (size_t i = 0; i < message->data.size(); ++i) {
+  for (wtf_size_t i = 0; i < message->data.size(); ++i) {
     message_size += message->data[i]->media_type.CharactersSizeInBytes();
     message_size += message->data[i]->data.size();
   }
@@ -775,7 +775,7 @@
 }
 
 // https://w3c.github.io/web-nfc/#dom-nfc-cancelwatch
-ScriptPromise NFC::cancelWatch(ScriptState* script_state, long id) {
+ScriptPromise NFC::cancelWatch(ScriptState* script_state, int32_t id) {
   ScriptPromise promise = RejectIfNotSupported(script_state);
   if (!promise.IsEmpty())
     return promise;
diff --git a/third_party/blink/renderer/modules/nfc/nfc.h b/third_party/blink/renderer/modules/nfc/nfc.h
index 39363804..554581d 100644
--- a/third_party/blink/renderer/modules/nfc/nfc.h
+++ b/third_party/blink/renderer/modules/nfc/nfc.h
@@ -52,7 +52,7 @@
   ScriptPromise watch(ScriptState*, V8MessageCallback*, const NFCWatchOptions&);
 
   // Cancels watch operation with id.
-  ScriptPromise cancelWatch(ScriptState*, long id);
+  ScriptPromise cancelWatch(ScriptState*, int32_t id);
 
   // Cancels all watch operations.
   ScriptPromise cancelWatch(ScriptState*);
diff --git a/third_party/blink/renderer/modules/notifications/notification.cc b/third_party/blink/renderer/modules/notifications/notification.cc
index 41afb93..99112d7 100644
--- a/third_party/blink/renderer/modules/notifications/notification.cc
+++ b/third_party/blink/renderer/modules/notifications/notification.cc
@@ -57,6 +57,7 @@
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/wtf/assertions.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 
 namespace blink {
 
@@ -346,7 +347,7 @@
   const Vector<mojom::blink::NotificationActionPtr>& actions =
       data_->actions.value();
   result.Grow(actions.size());
-  for (size_t i = 0; i < actions.size(); ++i) {
+  for (wtf_size_t i = 0; i < actions.size(); ++i) {
     NotificationAction action;
 
     switch (actions[i]->type) {
diff --git a/third_party/blink/renderer/modules/notifications/notification_data.cc b/third_party/blink/renderer/modules/notifications/notification_data.cc
index 7667504..4e22ec98 100644
--- a/third_party/blink/renderer/modules/notifications/notification_data.cc
+++ b/third_party/blink/renderer/modules/notifications/notification_data.cc
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/modules/notifications/notification_options.h"
 #include "third_party/blink/renderer/modules/vibration/vibration_controller.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_view.h"
 #include "third_party/blink/renderer/platform/wtf/time.h"
 
@@ -100,7 +101,7 @@
     notification_data->data = Vector<uint8_t>();
     notification_data->data->Append(
         serialized_script_value->Data(),
-        serialized_script_value->DataLengthInBytes());
+        SafeCast<wtf_size_t>(serialized_script_value->DataLengthInBytes()));
   }
 
   Vector<mojom::blink::NotificationActionPtr> actions;
diff --git a/third_party/blink/renderer/modules/notifications/notification_data_test.cc b/third_party/blink/renderer/modules/notifications/notification_data_test.cc
index edd5925..24296339e 100644
--- a/third_party/blink/renderer/modules/notifications/notification_data_test.cc
+++ b/third_party/blink/renderer/modules/notifications/notification_data_test.cc
@@ -134,7 +134,7 @@
 
   ASSERT_EQ(vibration_pattern.size(),
             notification_data->vibration_pattern->size());
-  for (size_t i = 0; i < vibration_pattern.size(); ++i) {
+  for (wtf_size_t i = 0; i < vibration_pattern.size(); ++i) {
     EXPECT_EQ(
         vibration_pattern[i],
         static_cast<unsigned>(notification_data->vibration_pattern.value()[i]));
@@ -259,7 +259,7 @@
 
   ASSERT_EQ(normalized_pattern.size(),
             notification_data->vibration_pattern->size());
-  for (size_t i = 0; i < normalized_pattern.size(); ++i) {
+  for (wtf_size_t i = 0; i < normalized_pattern.size(); ++i) {
     EXPECT_EQ(normalized_pattern[i],
               notification_data->vibration_pattern.value()[i]);
   }
@@ -323,7 +323,7 @@
   // The stored actions will be capped to |maxActions| entries.
   ASSERT_EQ(Notification::maxActions(), notification_data->actions->size());
 
-  for (size_t i = 0; i < Notification::maxActions(); ++i) {
+  for (wtf_size_t i = 0; i < Notification::maxActions(); ++i) {
     String expected_action = String::Number(i);
     EXPECT_EQ(expected_action, notification_data->actions.value()[i]->action);
   }
diff --git a/third_party/blink/renderer/modules/payments/payment_request.cc b/third_party/blink/renderer/modules/payments/payment_request.cc
index cbdac9c6..a4ea869 100644
--- a/third_party/blink/renderer/modules/payments/payment_request.cc
+++ b/third_party/blink/renderer/modules/payments/payment_request.cc
@@ -739,17 +739,22 @@
   }
 }
 
-bool AllowedToUsePaymentRequest(const Frame* frame) {
+bool AllowedToUsePaymentRequest(const ExecutionContext* execution_context) {
   // To determine whether a Document object |document| is allowed to use the
   // feature indicated by attribute name |allowpaymentrequest|, run these steps:
 
+  // If this context is not a document, return false.
+  if (!execution_context->IsDocument())
+    return false;
+
   // 1. If |document| has no browsing context, then return false.
-  if (!frame)
+  const Document* document = ToDocument(execution_context);
+  if (!document->GetFrame())
     return false;
 
   // 2. If Feature Policy is enabled, return the policy for "payment" feature.
-  return frame->DeprecatedIsFeatureEnabled(
-      mojom::FeaturePolicyFeature::kPayment, ReportOptions::kReportOnFailure);
+  return document->IsFeatureEnabled(mojom::FeaturePolicyFeature::kPayment,
+                                    ReportOptions::kReportOnFailure);
 }
 
 void WarnIgnoringQueryQuotaForCanMakePayment(
@@ -1056,7 +1061,7 @@
           &PaymentRequest::OnCompleteTimeout) {
   DCHECK(GetExecutionContext()->IsSecureContext());
 
-  if (!AllowedToUsePaymentRequest(GetFrame())) {
+  if (!AllowedToUsePaymentRequest(execution_context)) {
     exception_state.ThrowSecurityError(
         "Must be in a top-level browsing context or an iframe needs to specify "
         "'allowpaymentrequest' explicitly");
diff --git a/third_party/blink/renderer/modules/webusb/usb.cc b/third_party/blink/renderer/modules/webusb/usb.cc
index 1a65f8e..da13f98d 100644
--- a/third_party/blink/renderer/modules/webusb/usb.cc
+++ b/third_party/blink/renderer/modules/webusb/usb.cc
@@ -114,9 +114,7 @@
     ExecutionContext* execution_context = ExecutionContext::From(script_state);
     if (execution_context && execution_context->IsDocument()) {
       ToDocument(execution_context)
-          ->GetFrame()
-          ->DeprecatedReportFeaturePolicyViolation(
-              mojom::FeaturePolicyFeature::kUsb);
+          ->ReportFeaturePolicyViolation(mojom::FeaturePolicyFeature::kUsb);
     }
     return ScriptPromise::RejectWithDOMException(
         script_state, DOMException::Create(DOMExceptionCode::kSecurityError,
@@ -134,14 +132,14 @@
 ScriptPromise USB::requestDevice(ScriptState* script_state,
                                  const USBDeviceRequestOptions& options) {
   LocalFrame* frame = GetFrame();
-  if (!frame) {
+  if (!frame || !frame->GetDocument()) {
     return ScriptPromise::RejectWithDOMException(
         script_state,
         DOMException::Create(DOMExceptionCode::kNotSupportedError));
   }
 
-  if (!frame->DeprecatedIsFeatureEnabled(mojom::FeaturePolicyFeature::kUsb,
-                                         ReportOptions::kReportOnFailure)) {
+  if (!frame->GetDocument()->IsFeatureEnabled(
+          mojom::FeaturePolicyFeature::kUsb, ReportOptions::kReportOnFailure)) {
     return ScriptPromise::RejectWithDOMException(
         script_state, DOMException::Create(DOMExceptionCode::kSecurityError,
                                            kFeaturePolicyBlocked));
@@ -315,9 +313,8 @@
 }
 
 bool USB::IsFeatureEnabled() const {
-  ExecutionContext* context = GetExecutionContext();
-  FeaturePolicy* policy = context->GetSecurityContext().GetFeaturePolicy();
-  return policy->IsFeatureEnabled(mojom::FeaturePolicyFeature::kUsb);
+  return GetExecutionContext()->GetSecurityContext().IsFeatureEnabled(
+      mojom::FeaturePolicyFeature::kUsb);
 }
 
 void USB::Trace(blink::Visitor* visitor) {
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
index 6f92724..bfbb5e6 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -136,10 +136,9 @@
   effect_node.has_render_surface = true;
   root_layer_->SetEffectTreeIndex(effect_node.id);
 
-  current_effect_id_ = effect_node.id;
-  current_effect_type_ = CcEffectType::kEffect;
-  current_effect_ = &EffectPaintPropertyNode::Root();
-  current_clip_ = current_effect_->OutputClip();
+  SetCurrentEffectState(effect_node, CcEffectType::kEffect,
+                        &EffectPaintPropertyNode::Root(),
+                        &ClipPaintPropertyNode::Root());
 }
 
 void PropertyTreeManager::SetupRootScrollNode() {
@@ -155,6 +154,26 @@
   root_layer_->SetScrollTreeIndex(scroll_node.id);
 }
 
+void PropertyTreeManager::SetCurrentEffectState(
+    const cc::EffectNode& cc_effect_node,
+    CcEffectType effect_type,
+    const EffectPaintPropertyNode* effect,
+    const ClipPaintPropertyNode* clip) {
+  current_.effect_id = cc_effect_node.id;
+  current_.effect_type = effect_type;
+  current_.effect = effect;
+  current_.clip = clip;
+  if (cc_effect_node.has_render_surface)
+    current_.render_surface_transform = effect->LocalTransformSpace();
+}
+
+// TODO(crbug.com/504464): Remove this when move render surface decision logic
+// into cc compositor thread.
+void PropertyTreeManager::SetCurrentEffectHasRenderSurface() {
+  GetEffectTree().Node(current_.effect_id)->has_render_surface = true;
+  current_.render_surface_transform = current_.effect->LocalTransformSpace();
+}
+
 int PropertyTreeManager::EnsureCompositorTransformNode(
     const TransformPaintPropertyNode* transform_node) {
   DCHECK(transform_node);
@@ -255,14 +274,15 @@
   // If the parent transform node flattens transform (as |transform_node|
   // flattens inherited transform) while it participates in the 3d sorting
   // context of an ancestor, cc needs a render surface for correct flattening.
-  if (transform_node->FlattensInheritedTransform() &&
+  // TODO(crbug.com/504464): Move the logic into cc compositor thread.
+  auto* current_cc_effect = GetEffectTree().Node(current_.effect_id);
+  if (current_cc_effect && !current_cc_effect->has_render_surface &&
+      current_cc_effect->transform_id == parent_id &&
+      transform_node->FlattensInheritedTransform() &&
       transform_node->Parent() &&
       transform_node->Parent()->RenderingContextId() &&
-      !transform_node->Parent()->FlattensInheritedTransform()) {
-    auto* current_cc_effect = GetEffectTree().Node(current_effect_id_);
-    if (current_cc_effect && current_cc_effect->transform_id == parent_id)
-      current_cc_effect->has_render_surface = true;
-  }
+      !transform_node->Parent()->FlattensInheritedTransform())
+    SetCurrentEffectHasRenderSurface();
 
   auto result = transform_node_map_.Set(transform_node, id);
   DCHECK(result.is_new_entry);
@@ -379,12 +399,12 @@
 }
 
 void PropertyTreeManager::EmitClipMaskLayer() {
-  int clip_id = EnsureCompositorClipNode(current_clip_);
+  int clip_id = EnsureCompositorClipNode(current_.clip);
   CompositorElementId mask_isolation_id, mask_effect_id;
   cc::Layer* mask_layer = client_.CreateOrReuseSynthesizedClipLayer(
-      current_clip_, mask_isolation_id, mask_effect_id);
+      current_.clip, mask_isolation_id, mask_effect_id);
 
-  cc::EffectNode& mask_isolation = *GetEffectTree().Node(current_effect_id_);
+  cc::EffectNode& mask_isolation = *GetEffectTree().Node(current_.effect_id);
   // Assignment of mask_isolation.stable_id was delayed until now.
   // See PropertyTreeManager::SynthesizeCcEffectsForClipsIfNeeded().
   DCHECK_EQ(static_cast<uint64_t>(cc::EffectNode::INVALID_STABLE_ID),
@@ -392,13 +412,13 @@
   mask_isolation.stable_id = mask_isolation_id.GetInternalValue();
 
   cc::EffectNode& mask_effect = *GetEffectTree().Node(
-      GetEffectTree().Insert(cc::EffectNode(), current_effect_id_));
+      GetEffectTree().Insert(cc::EffectNode(), current_.effect_id));
   mask_effect.stable_id = mask_effect_id.GetInternalValue();
   mask_effect.clip_id = clip_id;
   mask_effect.has_render_surface = true;
   mask_effect.blend_mode = SkBlendMode::kDstIn;
 
-  const auto* clip_space = current_clip_->LocalTransformSpace();
+  const auto* clip_space = current_.clip->LocalTransformSpace();
   layer_list_builder_->Add(mask_layer);
   mask_layer->set_property_tree_sequence_number(
       root_layer_->property_tree_sequence_number());
@@ -415,7 +435,7 @@
 
 void PropertyTreeManager::CloseCcEffect() {
   DCHECK(effect_stack_.size());
-  const EffectStackEntry& previous_state = effect_stack_.back();
+  const auto& previous_state = effect_stack_.back();
 
   // An effect with exotic blending that is masked by a synthesized clip must
   // have its blending to the outermost synthesized clip. It is because
@@ -425,18 +445,15 @@
   // thus the clip can't be shared with sibling layers, and must be closed now.
   bool clear_synthetic_effects =
       !IsCurrentCcEffectSynthetic() &&
-      current_effect_->BlendMode() != SkBlendMode::kSrcOver;
+      current_.effect->BlendMode() != SkBlendMode::kSrcOver;
 
   // We are about to close an effect that was synthesized for isolating
   // a clip mask. Now emit the actual clip mask that will be composited on
   // top of masked contents with SkBlendMode::kDstIn.
-  if (IsCurrentCcEffectSynthetic())
+  if (IsCurrentCcEffectSyntheticForNonTrivialClip())
     EmitClipMaskLayer();
 
-  current_effect_id_ = previous_state.effect_id;
-  current_effect_type_ = previous_state.effect_type;
-  current_effect_ = previous_state.effect;
-  current_clip_ = previous_state.clip;
+  current_ = previous_state;
   effect_stack_.pop_back();
 
   if (clear_synthetic_effects) {
@@ -445,6 +462,12 @@
   }
 }
 
+static bool TransformsAre2dAxisAligned(const TransformPaintPropertyNode* a,
+                                       const TransformPaintPropertyNode* b) {
+  return a == b || GeometryMapper::SourceToDestinationProjection(a, b)
+                       .Preserves2dAxisAlignment();
+}
+
 int PropertyTreeManager::SwitchToEffectNodeWithSynthesizedClip(
     const EffectPaintPropertyNode& next_effect,
     const ClipPaintPropertyNode& next_clip) {
@@ -460,7 +483,7 @@
   // For example with the following clip and effect tree and pending layers:
   // E0 <-- E1
   // C0 <-- C1(rounded)
-  // [P0(E1,C0), P1(E1,C1), P2(E0, C1)]
+  // [P0(E1,C0), P1(E1,C1), P2(E0,C1)]
   // In effect stack diagram:
   // P0(C0) P1(C1)
   // [    E1     ] P2(C1)
@@ -470,11 +493,11 @@
   // E0 <+- E1 <-- E_C1_1 <-- E_C1_1M
   //     +- E_C1_2 <-- E_C1_2M
   // C0 <-- C1
-  // [L0(E1,C0), L1(E_C1_1, C1), L_C1_1(E_C1_1M, C1), L2(E0, C1),
-  //  L_C1_2(E_C1_2M, C1)]
+  // [L0(E1,C0), L1(E_C1_1, C1), L1M(E_C1_1M, C1), L2(E_C1_2, C1),
+  //  L2M(E_C1_2M, C1)]
   // In effect stack diagram:
-  //                 L_C1_1
-  //        L1(C1) [ E_C1_1M ]          L_C2_2
+  //                 L1M(C1)
+  //        L1(C1) [ E_C1_1M ]          L2M(C1)
   // L0(C0) [     E_C1_1     ] L2(C1) [ E_C1_2M ]
   // [          E1           ][     E_C1_2      ]
   // [                    E0                    ]
@@ -488,23 +511,22 @@
   // emits P1.
   // Prior to emitting P2, this method is invoked with (E0, C1). Both previously
   // entered effects must be closed, because synthetic effect for C1 is enclosed
-  // by E1, thus must be closed before E1 can be closed. A mask layer L_C1_1
-  // is generated along with an internal effect node for blending. After closing
+  // by E1, thus must be closed before E1 can be closed. A mask layer L1M is
+  // generated along with an internal effect node for blending. After closing
   // both effects, C1 has to be entered again, thus generates another synthetic
   // compositor effect. The caller emits P2.
   // At last, the caller invokes Finalize() to close the unclosed synthetic
-  // effect. Another mask layer L_C1_2 is generated, along with its internal
+  // effect. Another mask layer L2M is generated, along with its internal
   // effect node for blending.
   const auto& ancestor =
-      *LowestCommonAncestor(*current_effect_, next_effect).Unalias();
-  while (current_effect_ != &ancestor)
+      *LowestCommonAncestor(*current_.effect, next_effect).Unalias();
+  while (current_.effect != &ancestor)
     CloseCcEffect();
 
   bool newly_built = BuildEffectNodesRecursively(&next_effect);
   SynthesizeCcEffectsForClipsIfNeeded(&next_clip, SkBlendMode::kSrcOver,
                                       newly_built);
-
-  return current_effect_id_;
+  return current_.effect_id;
 }
 
 static bool IsNodeOnAncestorChain(const ClipPaintPropertyNode& find,
@@ -523,34 +545,48 @@
   return false;
 }
 
+base::Optional<PropertyTreeManager::CcEffectType>
+PropertyTreeManager::NeedsSyntheticEffect(
+    const ClipPaintPropertyNode& clip) const {
+  if (clip.ClipRect().IsRounded() || clip.ClipPath())
+    return CcEffectType::kSyntheticForNonTrivialClip;
+
+  // Cc requires that a rectangluar clip is 2d-axis-aligned with the render
+  // surface to correctly apply the clip.
+  if (!TransformsAre2dAxisAligned(clip.LocalTransformSpace(),
+                                  current_.render_surface_transform))
+    return CcEffectType::kSyntheticFor2dAxisAlignment;
+
+  return base::nullopt;
+}
+
 SkBlendMode PropertyTreeManager::SynthesizeCcEffectsForClipsIfNeeded(
     const ClipPaintPropertyNode* target_clip,
     SkBlendMode delegated_blend,
     bool effect_is_newly_built) {
   target_clip = target_clip->Unalias();
   if (delegated_blend != SkBlendMode::kSrcOver) {
-    // Exit all synthetic effect node for rounded clip if the next child has
-    // exotic blending mode because it has to access the backdrop of enclosing
-    // effect.
+    // Exit all synthetic effect node if the next child has exotic blending mode
+    // because it has to access the backdrop of enclosing effect.
     while (IsCurrentCcEffectSynthetic())
       CloseCcEffect();
 
     // An effect node can't omit render surface if it has child with exotic
     // blending mode. See comments below for more detail.
     // TODO(crbug.com/504464): Remove premature optimization here.
-    GetEffectTree().Node(current_effect_id_)->has_render_surface = true;
+    SetCurrentEffectHasRenderSurface();
   } else {
     // Exit synthetic effects until there are no more synthesized clips below
     // our lowest common ancestor.
     const auto& lca =
-        *LowestCommonAncestor(*current_clip_, *target_clip).Unalias();
-    while (current_clip_ != &lca) {
+        *LowestCommonAncestor(*current_.clip, *target_clip).Unalias();
+    while (current_.clip != &lca) {
       DCHECK(IsCurrentCcEffectSynthetic());
-      const auto* pre_exit_clip = current_clip_;
+      const auto* pre_exit_clip = current_.clip;
       CloseCcEffect();
       // We may run past the lowest common ancestor because it may not have
       // been synthesized.
-      if (IsNodeOnAncestorChain(lca, *pre_exit_clip, *current_clip_))
+      if (IsNodeOnAncestorChain(lca, *pre_exit_clip, *current_.clip))
         break;
     }
 
@@ -561,48 +597,64 @@
     // See comments in PropertyTreeManager::BuildEffectNodesRecursively().
     // TODO(crbug.com/504464): Remove premature optimization here.
     if (!effect_is_newly_built && !IsCurrentCcEffectSynthetic() &&
-        current_effect_->Opacity() != 1.f)
-      GetEffectTree().Node(current_effect_id_)->has_render_surface = true;
+        current_.effect->Opacity() != 1.f)
+      SetCurrentEffectHasRenderSurface();
   }
 
-  DCHECK(current_clip_->IsAncestorOf(*target_clip));
+  DCHECK(current_.clip->IsAncestorOf(*target_clip));
 
-  Vector<const ClipPaintPropertyNode*> pending_clips;
-  for (; target_clip != current_clip_;
+  struct PendingClip {
+    const ClipPaintPropertyNode* clip;
+    CcEffectType type;
+  };
+  Vector<PendingClip> pending_clips;
+  for (; target_clip != current_.clip;
        target_clip = target_clip->Parent()->Unalias()) {
     DCHECK(target_clip);
-    bool should_synthesize =
-        target_clip->ClipRect().IsRounded() || target_clip->ClipPath();
-    if (should_synthesize)
-      pending_clips.push_back(target_clip);
+    if (auto type = NeedsSyntheticEffect(*target_clip))
+      pending_clips.emplace_back(PendingClip{target_clip, *type});
   }
 
   for (size_t i = pending_clips.size(); i--;) {
-    const ClipPaintPropertyNode* next_clip = pending_clips[i];
+    const auto& pending_clip = pending_clips[i];
 
-    // For each of clip synthesized, an isolation effect node needs to be
-    // created to enclose only the layers that should be masked by the clip.
-    cc::EffectNode& mask_isolation = *GetEffectTree().Node(
-        GetEffectTree().Insert(cc::EffectNode(), current_effect_id_));
-    // mask_isolation.stable_id will be assigned later when the effect is
-    // closed. For now the default value of INVALID_STABLE_ID is used.
-    // See PropertyTreeManager::EmitClipMaskLayer().
-    mask_isolation.clip_id = EnsureCompositorClipNode(next_clip);
-    mask_isolation.has_render_surface = true;
+    // For a non-trivial clip, the synthetic effect is an isolation to enclose
+    // only the layers that should be masked by the synthesized clip.
+    // For a non-2d-axis-preserving clip, the synthetic effect creates a render
+    // surface which is axis-aligned with the clip.
+    cc::EffectNode& synthetic_effect = *GetEffectTree().Node(
+        GetEffectTree().Insert(cc::EffectNode(), current_.effect_id));
+    if (pending_clip.type == CcEffectType::kSyntheticForNonTrivialClip) {
+      synthetic_effect.clip_id = EnsureCompositorClipNode(pending_clip.clip);
+      // For non-trivial clip, isolation_effect.stable_id will be assigned later
+      // when the effect is closed. For now the default value INVALID_STABLE_ID
+      // is used. See PropertyTreeManager::EmitClipMaskLayer().
+    } else {
+      DCHECK_EQ(pending_clip.type, CcEffectType::kSyntheticFor2dAxisAlignment);
+      synthetic_effect.stable_id =
+          CompositorElementIdFromUniqueObjectId(NewUniqueObjectId())
+              .GetInternalValue();
+      // The clip of the synthetic effect is the parent of the clip, so that
+      // the clip itself will be applied in the render surface.
+      synthetic_effect.clip_id =
+          EnsureCompositorClipNode(pending_clip.clip->Parent());
+    }
+    synthetic_effect.transform_id =
+        EnsureCompositorTransformNode(pending_clip.clip->LocalTransformSpace());
+    synthetic_effect.has_render_surface = true;
     // Clip and kDstIn do not commute. This shall never be reached because
     // kDstIn is only used internally to implement CSS clip-path and mask,
     // and there is never a difference between the output clip of the effect
     // and the mask content.
     DCHECK(delegated_blend != SkBlendMode::kDstIn);
-    mask_isolation.blend_mode = delegated_blend;
+    synthetic_effect.blend_mode = delegated_blend;
     delegated_blend = SkBlendMode::kSrcOver;
 
-    effect_stack_.emplace_back(
-        EffectStackEntry{current_effect_id_, current_effect_type_,
-                         current_effect_, current_clip_});
-    current_effect_id_ = mask_isolation.id;
-    current_effect_type_ = CcEffectType::kSynthesizedClip;
-    current_clip_ = next_clip;
+    effect_stack_.emplace_back(current_);
+    SetCurrentEffectState(synthetic_effect, pending_clip.type, current_.effect,
+                          pending_clip.clip);
+    current_.render_surface_transform =
+        pending_clip.clip->LocalTransformSpace();
   }
 
   return delegated_blend;
@@ -611,12 +663,12 @@
 bool PropertyTreeManager::BuildEffectNodesRecursively(
     const EffectPaintPropertyNode* next_effect) {
   next_effect = next_effect ? next_effect->Unalias() : nullptr;
-  if (next_effect == current_effect_)
+  if (next_effect == current_.effect)
     return false;
   DCHECK(next_effect);
 
   bool newly_built = BuildEffectNodesRecursively(next_effect->Parent());
-  DCHECK_EQ(next_effect->Parent()->Unalias(), current_effect_);
+  DCHECK_EQ(next_effect->Parent()->Unalias(), current_.effect);
 
 #if DCHECK_IS_ON()
   DCHECK(!effect_nodes_converted_.Contains(next_effect))
@@ -627,10 +679,11 @@
 
   SkBlendMode used_blend_mode;
   int output_clip_id;
-  if (next_effect->OutputClip()) {
+  const auto* output_clip = next_effect->OutputClip();
+  if (output_clip) {
     used_blend_mode = SynthesizeCcEffectsForClipsIfNeeded(
-        next_effect->OutputClip(), next_effect->BlendMode(), newly_built);
-    output_clip_id = EnsureCompositorClipNode(next_effect->OutputClip());
+        output_clip, next_effect->BlendMode(), newly_built);
+    output_clip_id = EnsureCompositorClipNode(output_clip);
   } else {
     while (IsCurrentCcEffectSynthetic())
       CloseCcEffect();
@@ -638,15 +691,17 @@
     // blending mode, nor being opacity-only node with more than one child.
     // TODO(crbug.com/504464): Remove premature optimization here.
     if (next_effect->BlendMode() != SkBlendMode::kSrcOver ||
-        (!newly_built && current_effect_->Opacity() != 1.f))
-      GetEffectTree().Node(current_effect_id_)->has_render_surface = true;
+        (!newly_built && current_.effect->Opacity() != 1.f))
+      SetCurrentEffectHasRenderSurface();
 
     used_blend_mode = next_effect->BlendMode();
-    output_clip_id = GetEffectTree().Node(current_effect_id_)->clip_id;
+    output_clip = current_.clip;
+    output_clip_id = GetEffectTree().Node(current_.effect_id)->clip_id;
+    DCHECK_EQ(output_clip_id, EnsureCompositorClipNode(output_clip));
   }
 
   cc::EffectNode& effect_node = *GetEffectTree().Node(
-      GetEffectTree().Insert(cc::EffectNode(), current_effect_id_));
+      GetEffectTree().Insert(cc::EffectNode(), current_.effect_id));
   effect_node.stable_id =
       next_effect->GetCompositorElementId().GetInternalValue();
   effect_node.clip_id = output_clip_id;
@@ -663,6 +718,7 @@
   if (!next_effect->Filter().IsEmpty() ||
       used_blend_mode != SkBlendMode::kSrcOver)
     effect_node.has_render_surface = true;
+
   effect_node.opacity = next_effect->Opacity();
   if (next_effect->GetColorFilter() != kColorFilterNone) {
     // Currently color filter is only used by SVG masks.
@@ -691,14 +747,9 @@
     property_trees_.element_id_to_effect_node_index[compositor_element_id] =
         effect_node.id;
   }
-  effect_stack_.emplace_back(EffectStackEntry{current_effect_id_,
-                                              current_effect_type_,
-                                              current_effect_, current_clip_});
-  current_effect_id_ = effect_node.id;
-  current_effect_type_ = CcEffectType::kEffect;
-  current_effect_ = next_effect;
-  if (next_effect->OutputClip())
-    current_clip_ = next_effect->OutputClip()->Unalias();
+  effect_stack_.emplace_back(current_);
+  SetCurrentEffectState(effect_node, CcEffectType::kEffect, next_effect,
+                        output_clip);
 
   return true;
 }
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h
index ee0fc81..edfce2ee 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h
@@ -19,6 +19,7 @@
 class PropertyTrees;
 class ScrollTree;
 class TransformTree;
+struct EffectNode;
 struct TransformNode;
 }
 
@@ -126,9 +127,38 @@
       bool effect_is_newly_built);
   void EmitClipMaskLayer();
   void CloseCcEffect();
+
   bool IsCurrentCcEffectSynthetic() const {
-    return current_effect_type_ != CcEffectType::kEffect;
+    return current_.effect_type != CcEffectType::kEffect;
   }
+  bool IsCurrentCcEffectSyntheticForNonTrivialClip() const {
+    return current_.effect_type == CcEffectType::kSyntheticForNonTrivialClip;
+  }
+
+  // The type of operation the current cc effect node applies.
+  enum class CcEffectType {
+    // The cc effect corresponds to a Blink effect node.
+    kEffect,
+    // The cc effect is synthetic for a blink clip node that has to be
+    // rasterized because the clip is non-trivial.
+    kSyntheticForNonTrivialClip,
+    // The cc effect is synthetic to create a render surface that is
+    // 2d-axis-aligned with a blink clip node that is non-2d-axis-aligned
+    // in the the original render surface. Cc requires a rectangular clip to be
+    // 2d-axis-aligned with the render surface to correctly apply the clip.
+    // TODO(crbug.com/504464): This will be changed when we move render surface
+    // decision logic into the cc compositor thread.
+    kSyntheticFor2dAxisAlignment,
+  };
+
+  base::Optional<CcEffectType> NeedsSyntheticEffect(
+      const ClipPaintPropertyNode&) const;
+
+  void SetCurrentEffectState(const cc::EffectNode&,
+                             CcEffectType,
+                             const EffectPaintPropertyNode*,
+                             const ClipPaintPropertyNode*);
+  void SetCurrentEffectHasRenderSurface();
 
   cc::TransformTree& GetTransformTree();
   cc::ClipTree& GetClipTree();
@@ -158,32 +188,36 @@
   HashMap<const ClipPaintPropertyNode*, int> clip_node_map_;
   HashMap<const ScrollPaintPropertyNode*, int> scroll_node_map_;
 
-  // The cc effect node that has the corresponding drawing state to the
-  // effect and clip state from the last SwitchToEffectNodeWithSynthesizedClip.
-  int current_effect_id_;
-  // The type of operation the current cc effect node applies. kEffect means
-  // it corresponds to a Blink effect node. kSynthesizedClip means it implements
-  // a Blink clip node that has to be rasterized.
-  enum class CcEffectType { kEffect, kSynthesizedClip } current_effect_type_;
-  // The effect state of the current cc effect node.
-  const EffectPaintPropertyNode* current_effect_;
-  // The clip state of the current cc effect node. This value may be shallower
-  // than the one passed into SwitchToEffectNodeWithSynthesizedClip because not
-  // every clip needs to be synthesized as cc effect.
-  // Is set to output clip of the effect if the type is kEffect, or set to the
-  // synthesized clip node if the type is kSynthesizedClip.
-  const ClipPaintPropertyNode* current_clip_;
+  struct EffectState {
+    // The cc effect node that has the corresponding drawing state to the
+    // effect and clip state from the last
+    // SwitchToEffectNodeWithSynthesizedClip.
+    int effect_id;
+    CcEffectType effect_type;
+    // The effect state of the cc effect node.
+    const EffectPaintPropertyNode* effect;
+    // The clip state of the cc effect node. This value may be shallower than
+    // the one passed into SwitchToEffectNodeWithSynthesizedClip because not
+    // every clip needs to be synthesized as cc effect.
+    // Is set to output clip of the effect if the type is kEffect, or set to the
+    // synthesized clip node if the type is kSyntheticForNonTrivialClip.
+    const ClipPaintPropertyNode* clip;
+    // The transform space of the containing render surface.
+    // TODO(crbug.com/504464): Remove this when move render surface decision
+    // logic into cc compositor thread.
+    const TransformPaintPropertyNode* render_surface_transform;
+  };
+
+  // The current effect state. Virtually it's the top of the effect stack if
+  // it and effect_stack_ are treated as a whole stack.
+  EffectState current_;
+
   // This keep track of cc effect stack. Whenever a new cc effect is nested,
   // a new entry is pushed, and the entry will be popped when the effect closed.
   // Note: This is a "restore stack", i.e. the top element does not represent
-  // the current state, but the state prior to most recent push.
-  struct EffectStackEntry {
-    int effect_id;
-    CcEffectType effect_type;
-    const EffectPaintPropertyNode* effect;
-    const ClipPaintPropertyNode* clip;
-  };
-  Vector<EffectStackEntry> effect_stack_;
+  // the current state (which is in current_), but the state prior to most
+  // recent push.
+  Vector<EffectState> effect_stack_;
 
 #if DCHECK_IS_ON()
   HashSet<const EffectPaintPropertyNode*> effect_nodes_converted_;
diff --git a/third_party/blink/renderer/platform/transforms/transformation_matrix.cc b/third_party/blink/renderer/platform/transforms/transformation_matrix.cc
index 23ded0e..e14c893 100644
--- a/third_party/blink/renderer/platform/transforms/transformation_matrix.cc
+++ b/third_party/blink/renderer/platform/transforms/transformation_matrix.cc
@@ -1827,6 +1827,56 @@
   return true;
 }
 
+// This is the same as gfx::Transform::Preserves2dAxisAlignment().
+bool TransformationMatrix::Preserves2dAxisAlignment() const {
+  // Check whether an axis aligned 2-dimensional rect would remain axis-aligned
+  // after being transformed by this matrix (and implicitly projected by
+  // dropping any non-zero z-values).
+  //
+  // The 4th column can be ignored because translations don't affect axis
+  // alignment. The 3rd column can be ignored because we are assuming 2d
+  // inputs, where z-values will be zero. The 3rd row can also be ignored
+  // because we are assuming 2d outputs, and any resulting z-value is dropped
+  // anyway. For the inner 2x2 portion, the only effects that keep a rect axis
+  // aligned are (1) swapping axes and (2) scaling axes. This can be checked by
+  // verifying only 1 element of every column and row is non-zero.  Degenerate
+  // cases that project the x or y dimension to zero are considered to preserve
+  // axis alignment.
+  //
+  // If the matrix does have perspective component that is affected by x or y
+  // values: The current implementation conservatively assumes that axis
+  // alignment is not preserved.
+  bool has_x_or_y_perspective = M14() != 0 || M24() != 0;
+  if (has_x_or_y_perspective)
+    return false;
+
+  constexpr double kEpsilon = std::numeric_limits<double>::epsilon();
+
+  int num_non_zero_in_row_1 = 0;
+  int num_non_zero_in_row_2 = 0;
+  int num_non_zero_in_col_1 = 0;
+  int num_non_zero_in_col_2 = 0;
+  if (std::abs(M11()) > kEpsilon) {
+    num_non_zero_in_col_1++;
+    num_non_zero_in_row_1++;
+  }
+  if (std::abs(M12()) > kEpsilon) {
+    num_non_zero_in_col_1++;
+    num_non_zero_in_row_2++;
+  }
+  if (std::abs(M21()) > kEpsilon) {
+    num_non_zero_in_col_2++;
+    num_non_zero_in_row_1++;
+  }
+  if (std::abs(M22()) > kEpsilon) {
+    num_non_zero_in_col_2++;
+    num_non_zero_in_row_2++;
+  }
+
+  return num_non_zero_in_row_1 <= 1 && num_non_zero_in_row_2 <= 1 &&
+         num_non_zero_in_col_1 <= 1 && num_non_zero_in_col_2 <= 1;
+}
+
 FloatSize TransformationMatrix::To2DTranslation() const {
   DCHECK(IsIdentityOr2DTranslation());
   return FloatSize(matrix_[3][0], matrix_[3][1]);
diff --git a/third_party/blink/renderer/platform/transforms/transformation_matrix.h b/third_party/blink/renderer/platform/transforms/transformation_matrix.h
index 8d88d5d..e943e58 100644
--- a/third_party/blink/renderer/platform/transforms/transformation_matrix.h
+++ b/third_party/blink/renderer/platform/transforms/transformation_matrix.h
@@ -474,6 +474,10 @@
 
   bool IsIntegerTranslation() const;
 
+  // Returns true if axis-aligned 2d rects will remain axis-aligned after being
+  // transformed by this matrix.
+  bool Preserves2dAxisAlignment() const;
+
   // If this transformation is identity or 2D translation, returns the
   // translation.
   FloatSize To2DTranslation() const;
diff --git a/third_party/blink/tools/blinkpy/w3c/chromium_commit.py b/third_party/blink/tools/blinkpy/w3c/chromium_commit.py
index 2632639..f0282d0 100644
--- a/third_party/blink/tools/blinkpy/w3c/chromium_commit.py
+++ b/third_party/blink/tools/blinkpy/w3c/chromium_commit.py
@@ -86,7 +86,7 @@
 
     def author(self):
         return self.host.executive.run_command([
-            'git', 'show', '--format="%aN <%aE>"', '--no-patch', self.sha
+            'git', 'show', '--format=%aN <%aE>', '--no-patch', self.sha
         ], cwd=self.absolute_chromium_dir).strip()
 
     def message(self):
diff --git a/third_party/blink/tools/blinkpy/w3c/common.py b/third_party/blink/tools/blinkpy/w3c/common.py
index a5cd9b1..a0f8932 100644
--- a/third_party/blink/tools/blinkpy/w3c/common.py
+++ b/third_party/blink/tools/blinkpy/w3c/common.py
@@ -17,6 +17,11 @@
 EXPORT_PR_LABEL = 'chromium-export'
 PROVISIONAL_PR_LABEL = 'do not merge yet'
 
+# These are only set in a new WPT checkout, and they should be consistent with
+# the bot's GitHub account (chromium-wpt-export-bot).
+DEFAULT_WPT_COMMITTER_NAME = 'Chromium WPT Sync'
+DEFAULT_WPT_COMMITTER_EMAIL = 'blink-w3c-test-autoroller@chromium.org'
+
 # TODO(qyearsley): Avoid hard-coding third_party/WebKit/LayoutTests.
 CHROMIUM_WPT_DIR = 'third_party/WebKit/LayoutTests/external/wpt/'
 
diff --git a/third_party/blink/tools/blinkpy/w3c/local_wpt.py b/third_party/blink/tools/blinkpy/w3c/local_wpt.py
index 240a0de..4ab3b9e 100644
--- a/third_party/blink/tools/blinkpy/w3c/local_wpt.py
+++ b/third_party/blink/tools/blinkpy/w3c/local_wpt.py
@@ -7,7 +7,13 @@
 import logging
 
 from blinkpy.common.system.executive import ScriptError
-from blinkpy.w3c.common import WPT_GH_SSH_URL_TEMPLATE, WPT_MIRROR_URL, CHROMIUM_WPT_DIR
+from blinkpy.w3c.common import (
+    CHROMIUM_WPT_DIR,
+    DEFAULT_WPT_COMMITTER_EMAIL,
+    DEFAULT_WPT_COMMITTER_NAME,
+    WPT_GH_SSH_URL_TEMPLATE,
+    WPT_MIRROR_URL,
+)
 
 _log = logging.getLogger(__name__)
 
@@ -42,8 +48,13 @@
             remote_url = WPT_MIRROR_URL
             _log.info('No credentials given, using wpt mirror URL.')
             _log.info('It is possible for the mirror to be delayed; see https://crbug.com/698272.')
+        # Do not use self.run here because self.path doesn't exist yet.
         self.host.executive.run_command(['git', 'clone', remote_url, self.path])
 
+        _log.info('Setting git user name & email in %s', self.path)
+        self.run(['git', 'config', 'user.name', DEFAULT_WPT_COMMITTER_NAME])
+        self.run(['git', 'config', 'user.email', DEFAULT_WPT_COMMITTER_EMAIL])
+
     def run(self, command, **kwargs):
         """Runs a command in the local WPT directory."""
         # TODO(robertma): Migrate to blinkpy.common.checkout.Git. (crbug.com/676399)
diff --git a/third_party/blink/tools/blinkpy/w3c/local_wpt_unittest.py b/third_party/blink/tools/blinkpy/w3c/local_wpt_unittest.py
index beadebde..e99dbb2 100644
--- a/third_party/blink/tools/blinkpy/w3c/local_wpt_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/local_wpt_unittest.py
@@ -8,6 +8,7 @@
 from blinkpy.common.system.executive import ScriptError
 from blinkpy.common.system.executive_mock import MockExecutive, mock_git_commands
 from blinkpy.common.system.filesystem_mock import MockFileSystem
+from blinkpy.w3c.common import DEFAULT_WPT_COMMITTER_EMAIL, DEFAULT_WPT_COMMITTER_NAME
 from blinkpy.w3c.local_wpt import LocalWPT
 
 
@@ -34,6 +35,8 @@
 
         self.assertEqual(host.executive.calls, [
             ['git', 'clone', 'https://token@github.com/web-platform-tests/wpt.git', '/tmp/wpt'],
+            ['git', 'config', 'user.name', DEFAULT_WPT_COMMITTER_NAME],
+            ['git', 'config', 'user.email', DEFAULT_WPT_COMMITTER_EMAIL],
         ])
 
     def test_constructor(self):
@@ -55,6 +58,8 @@
         local_wpt.create_branch_with_patch('chromium-export-decafbad', 'message', 'patch', 'author <author@author.com>')
         self.assertEqual(host.executive.calls, [
             ['git', 'clone', 'https://token@github.com/web-platform-tests/wpt.git', '/tmp/wpt'],
+            ['git', 'config', 'user.name', DEFAULT_WPT_COMMITTER_NAME],
+            ['git', 'config', 'user.email', DEFAULT_WPT_COMMITTER_EMAIL],
             ['git', 'reset', '--hard', 'HEAD'],
             ['git', 'clean', '-fdx'],
             ['git', 'checkout', 'origin/master'],
diff --git a/third_party/blink/tools/blinkpy/w3c/test_exporter_unittest.py b/third_party/blink/tools/blinkpy/w3c/test_exporter_unittest.py
index 574be29..a099068 100644
--- a/third_party/blink/tools/blinkpy/w3c/test_exporter_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/test_exporter_unittest.py
@@ -379,6 +379,7 @@
 
         self.assertFalse(success)
         self.assertLog(['INFO: Cloning GitHub web-platform-tests/wpt into /tmp/wpt\n',
+                        'INFO: Setting git user name & email in /tmp/wpt\n',
                         'INFO: Searching for exportable in-flight CLs.\n',
                         'INFO: In-flight CLs cannot be exported due to the following error:\n',
                         'ERROR: Gerrit API fails.\n',
@@ -393,6 +394,7 @@
 
         self.assertFalse(success)
         self.assertLog(['INFO: Cloning GitHub web-platform-tests/wpt into /tmp/wpt\n',
+                        'INFO: Setting git user name & email in /tmp/wpt\n',
                         'INFO: Searching for exportable in-flight CLs.\n',
                         'INFO: Searching for exportable Chromium commits.\n',
                         'INFO: Attention: The following errors have prevented some commits from being exported:\n',
diff --git a/third_party/closure_compiler/externs/accessibility_private.js b/third_party/closure_compiler/externs/accessibility_private.js
index 903fdf8..ba1d7b7 100644
--- a/third_party/closure_compiler/externs/accessibility_private.js
+++ b/third_party/closure_compiler/externs/accessibility_private.js
@@ -208,3 +208,8 @@
  * @type {!ChromeEvent}
  */
 chrome.accessibilityPrivate.onSelectToSpeakStateChangeRequested;
+
+/**
+ * Called when a Switch Access user activates dictation from the context menu.
+ */
+chrome.accessibilityPrivate.toggleDictation = function() {};
diff --git a/third_party/feed/BUILD.gn b/third_party/feed/BUILD.gn
index 0ce5943..798d3eb 100644
--- a/third_party/feed/BUILD.gn
+++ b/third_party/feed/BUILD.gn
@@ -14,20 +14,41 @@
   custom_package = "com.google.android.libraries.feed.piet"
 }
 
-android_resources("basicstream_resources") {
+android_resources("basicstream_internal_viewholders_resources") {
   resource_dirs = [ "src/src/main/java/com/google/android/libraries/feed/basicstream/internal/viewholders/res" ]
   custom_package =
       "com.google.android.libraries.feed.basicstream.internal.viewholders"
 }
 
+android_resources("basicstream_resources") {
+  resource_dirs =
+      [ "src/src/main/java/com/google/android/libraries/feed/basicstream/res/" ]
+  custom_package = "com.google.android.libraries.feed.basicstream"
+}
+
+android_resources("shared_stream_publicapi_menumeasurer_resources") {
+  resource_dirs = [ "src/src/main/java/com/google/android/libraries/feed/sharedstream/publicapi/menumeasurer/res/" ]
+  custom_package =
+      "com.google.android.libraries.feed.sharedstream.publicapi.menumeasurer"
+}
+
+android_resources("basicstream_internal_actions_resources") {
+  resource_dirs = [ "src/src/main/java/com/google/android/libraries/feed/basicstream/internal/actions/res/" ]
+  custom_package =
+      "com.google.android.libraries.feed.basicstream.internal.actions"
+}
+
 android_library("feed_lib_java") {
   chromium_code = false
   java_files = feed_lib_java_sources
 
   deps = [
+    ":basicstream_internal_actions_resources",
+    ":basicstream_internal_viewholders_resources",
     ":basicstream_resources",
     ":feed_lib_proto_java",
     ":piet_resources",
+    ":shared_stream_publicapi_menumeasurer_resources",
     "//third_party/android_deps:android_support_annotations_java",
     "//third_party/android_deps:android_support_cardview_java",
     "//third_party/android_deps:android_support_v7_appcompat_java",
diff --git a/third_party/feed/README.chromium b/third_party/feed/README.chromium
index c34f4df..0aa68d1 100644
--- a/third_party/feed/README.chromium
+++ b/third_party/feed/README.chromium
@@ -2,7 +2,7 @@
 Short name: feed
 URL: https://chromium.googlesource.com/feed
 Version: 0
-Revision: 3c5a5efeacbb25d349b36c3482c5794c5229e869
+Revision: e291161061d18d222bc1b546225fe0567386cbf1
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/feed/java_sources.gni b/third_party/feed/java_sources.gni
index 067a06c..58fdb01 100644
--- a/third_party/feed/java_sources.gni
+++ b/third_party/feed/java_sources.gni
@@ -158,6 +158,7 @@
   "src/src/main/java/com/google/android/libraries/feed/host/action/StreamActionApi.java",
   "src/src/main/java/com/google/android/libraries/feed/host/config/Configuration.java",
   "src/src/main/java/com/google/android/libraries/feed/host/config/DebugBehavior.java",
+  "src/src/main/java/com/google/android/libraries/feed/host/imageloader/BundledAssets.java",
   "src/src/main/java/com/google/android/libraries/feed/host/imageloader/ImageLoaderApi.java",
   "src/src/main/java/com/google/android/libraries/feed/host/logging/ActionType.java",
   "src/src/main/java/com/google/android/libraries/feed/host/logging/BasicLoggingApi.java",
@@ -228,6 +229,7 @@
   "src/src/main/java/com/google/android/libraries/feed/piet/host/CustomElementProvider.java",
   "src/src/main/java/com/google/android/libraries/feed/piet/host/HostBindingProvider.java",
   "src/src/main/java/com/google/android/libraries/feed/piet/ui/DrawableScalingHelper.java",
+  "src/src/main/java/com/google/android/libraries/feed/piet/ui/GridRowView.java",
   "src/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerColorDrawable.java",
   "src/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerImageView.java",
   "src/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerViewHelper.java",
@@ -235,6 +237,8 @@
   "src/src/main/java/com/google/android/libraries/feed/sharedstream/piet/PietAssetProvider.java",
   "src/src/main/java/com/google/android/libraries/feed/sharedstream/piet/PietCustomElementProvider.java",
   "src/src/main/java/com/google/android/libraries/feed/sharedstream/piet/PietHostBindingProvider.java",
+  "src/src/main/java/com/google/android/libraries/feed/sharedstream/publicapi/menumeasurer/MenuMeasurer.java",
+  "src/src/main/java/com/google/android/libraries/feed/sharedstream/publicapi/menumeasurer/Size.java",
   "src/src/main/java/com/google/android/libraries/feed/sharedstream/removetrackingfactory/StreamRemoveTrackingFactory.java",
 ]
 
diff --git a/third_party/tcmalloc/chromium/src/gperftools/malloc_extension.h b/third_party/tcmalloc/chromium/src/gperftools/malloc_extension.h
index 689b5f1..077aef8b 100644
--- a/third_party/tcmalloc/chromium/src/gperftools/malloc_extension.h
+++ b/third_party/tcmalloc/chromium/src/gperftools/malloc_extension.h
@@ -164,6 +164,14 @@
   //            freed memory regions
   //      This property is not writable.
   //
+  //  "generic.total_physical_bytes"
+  //      Estimate of total bytes of the physical memory usage by the
+  //      allocator ==
+  //            current_allocated_bytes +
+  //            fragmentation +
+  //            metadata
+  //      This property is not writable.
+  //
   // tcmalloc
   // --------
   // "tcmalloc.max_total_thread_cache_bytes"
diff --git a/third_party/tcmalloc/chromium/src/tcmalloc.cc b/third_party/tcmalloc/chromium/src/tcmalloc.cc
index 6c5bd8b..5b4d064 100644
--- a/third_party/tcmalloc/chromium/src/tcmalloc.cc
+++ b/third_party/tcmalloc/chromium/src/tcmalloc.cc
@@ -723,6 +723,14 @@
       return true;
     }
 
+    if (strcmp(name, "generic.total_physical_bytes") == 0) {
+      TCMallocStats stats;
+      ExtractStats(&stats, NULL, NULL, NULL);
+      *value = stats.pageheap.system_bytes + stats.metadata_bytes -
+               stats.pageheap.unmapped_bytes;
+      return true;
+    }
+
     if (strcmp(name, "tcmalloc.slack_bytes") == 0) {
       // Kept for backwards compatibility.  Now defined externally as:
       //    pageheap_free_bytes + pageheap_unmapped_bytes.
diff --git a/tools/fuchsia/fidlgen_js/BUILD.gn b/tools/fuchsia/fidlgen_js/BUILD.gn
new file mode 100644
index 0000000..7a4e8f4d
--- /dev/null
+++ b/tools/fuchsia/fidlgen_js/BUILD.gn
@@ -0,0 +1,60 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/fuchsia/fidl_library.gni")
+import("//testing/test.gni")
+
+test("fidlgen_js_unittests") {
+  testonly = true
+
+  sources = [
+    "test/fidlgen_js_unittest.cc",
+  ]
+
+  deps = [
+    ":fidljstest",
+    ":runtime",
+    "//base/test:test_support",
+    "//gin:gin_test",
+    "//testing/gtest",
+    "//v8",
+  ]
+
+  configs += [
+    "//tools/v8_context_snapshot:use_v8_context_snapshot",
+    "//v8:external_startup_data",
+  ]
+
+  data_deps = [
+    "//tools/v8_context_snapshot:v8_context_snapshot",
+  ]
+
+  data = [
+    "runtime/fidl.mjs",
+  ]
+}
+
+static_library("runtime") {
+  sources = [
+    "runtime/zircon.cc",
+    "runtime/zircon.h",
+  ]
+
+  deps = [
+    "//gin",
+    "//v8",
+  ]
+}
+
+fidl_library("fidljstest") {
+  testonly = true
+  sources = [
+    "test/simple.fidl",
+  ]
+
+  languages = [
+    "cpp",
+    "js",
+  ]
+}
diff --git a/tools/fuchsia/fidlgen_js/DEPS b/tools/fuchsia/fidlgen_js/DEPS
new file mode 100644
index 0000000..681254d
--- /dev/null
+++ b/tools/fuchsia/fidlgen_js/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+gin",
+  "+v8/include",
+]
diff --git a/tools/fuchsia/fidlgen_js/fidl.py b/tools/fuchsia/fidlgen_js/fidl.py
new file mode 100644
index 0000000..6f8b99f
--- /dev/null
+++ b/tools/fuchsia/fidlgen_js/fidl.py
@@ -0,0 +1,549 @@
+# 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.
+
+# This was generated (and can be regenerated) by pasting
+# zircon/system/host/fidl/schema.json from Fuchsia into
+# https://app.quicktype.io and choosing Python 2.7 output. The only manual
+# change is to modify the import path for Enum.
+
+from third_party.enum34 import Enum
+
+
+def from_str(x):
+    assert isinstance(x, (str, unicode))
+    return x
+
+
+def from_int(x):
+    assert isinstance(x, int) and not isinstance(x, bool)
+    return x
+
+
+def from_none(x):
+    assert x is None
+    return x
+
+
+def from_union(fs, x):
+    for f in fs:
+        try:
+            return f(x)
+        except:
+            pass
+    assert False
+
+
+def from_bool(x):
+    assert isinstance(x, bool)
+    return x
+
+
+def to_class(c, x):
+    assert isinstance(x, c)
+    return x.to_dict()
+
+
+def to_enum(c, x):
+    assert isinstance(x, c)
+    return x.value
+
+
+def from_list(f, x):
+    assert isinstance(x, list)
+    return [f(y) for y in x]
+
+
+def from_dict(f, x):
+    assert isinstance(x, dict)
+    return { k: f(v) for (k, v) in x.items() }
+
+
+class Attribute:
+    def __init__(self, name, value):
+        self.name = name
+        self.value = value
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        name = from_str(obj.get(u"name"))
+        value = from_str(obj.get(u"value"))
+        return Attribute(name, value)
+
+    def to_dict(self):
+        result = {}
+        result[u"name"] = from_str(self.name)
+        result[u"value"] = from_str(self.value)
+        return result
+
+
+class TypeKind(Enum):
+    ARRAY = u"array"
+    HANDLE = u"handle"
+    IDENTIFIER = u"identifier"
+    PRIMITIVE = u"primitive"
+    REQUEST = u"request"
+    STRING = u"string"
+    VECTOR = u"vector"
+
+
+class TypeClass:
+    def __init__(self, element_count, element_type, kind, maybe_element_count, nullable, subtype, identifier):
+        self.element_count = element_count
+        self.element_type = element_type
+        self.kind = kind
+        self.maybe_element_count = maybe_element_count
+        self.nullable = nullable
+        self.subtype = subtype
+        self.identifier = identifier
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        element_count = from_union([from_int, from_none], obj.get(u"element_count"))
+        element_type = from_union([TypeClass.from_dict, from_none], obj.get(u"element_type"))
+        kind = TypeKind(obj.get(u"kind"))
+        maybe_element_count = from_union([from_int, from_none], obj.get(u"maybe_element_count"))
+        nullable = from_union([from_bool, from_none], obj.get(u"nullable"))
+        subtype = from_union([from_str, from_none], obj.get(u"subtype"))
+        identifier = from_union([from_str, from_none], obj.get(u"identifier"))
+        return TypeClass(element_count, element_type, kind, maybe_element_count, nullable, subtype, identifier)
+
+    def to_dict(self):
+        result = {}
+        result[u"element_count"] = from_union([from_int, from_none], self.element_count)
+        result[u"element_type"] = from_union([lambda x: to_class(TypeClass, x), from_none], self.element_type)
+        result[u"kind"] = to_enum(TypeKind, self.kind)
+        result[u"maybe_element_count"] = from_union([from_int, from_none], self.maybe_element_count)
+        result[u"nullable"] = from_union([from_bool, from_none], self.nullable)
+        result[u"subtype"] = from_union([from_str, from_none], self.subtype)
+        result[u"identifier"] = from_union([from_str, from_none], self.identifier)
+        return result
+
+
+class ConstantKind(Enum):
+    IDENTIFIER = u"identifier"
+    LITERAL = u"literal"
+
+
+class LiteralKind(Enum):
+    DEFAULT = u"default"
+    FALSE = u"false"
+    NUMERIC = u"numeric"
+    STRING = u"string"
+    TRUE = u"true"
+
+
+class Literal:
+    def __init__(self, kind, value):
+        self.kind = kind
+        self.value = value
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        kind = LiteralKind(obj.get(u"kind"))
+        value = from_union([from_str, from_none], obj.get(u"value"))
+        return Literal(kind, value)
+
+    def to_dict(self):
+        result = {}
+        result[u"kind"] = to_enum(LiteralKind, self.kind)
+        result[u"value"] = from_union([from_str, from_none], self.value)
+        return result
+
+
+class Constant:
+    def __init__(self, identifier, kind, literal):
+        self.identifier = identifier
+        self.kind = kind
+        self.literal = literal
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        identifier = from_union([from_str, from_none], obj.get(u"identifier"))
+        kind = ConstantKind(obj.get(u"kind"))
+        literal = from_union([Literal.from_dict, from_none], obj.get(u"literal"))
+        return Constant(identifier, kind, literal)
+
+    def to_dict(self):
+        result = {}
+        result[u"identifier"] = from_union([from_str, from_none], self.identifier)
+        result[u"kind"] = to_enum(ConstantKind, self.kind)
+        result[u"literal"] = from_union([lambda x: to_class(Literal, x), from_none], self.literal)
+        return result
+
+
+class Const:
+    def __init__(self, maybe_attributes, name, type, value):
+        self.maybe_attributes = maybe_attributes
+        self.name = name
+        self.type = type
+        self.value = value
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        maybe_attributes = from_union([lambda x: from_list(Attribute.from_dict, x), from_none], obj.get(u"maybe_attributes"))
+        name = from_str(obj.get(u"name"))
+        type = TypeClass.from_dict(obj.get(u"type"))
+        value = Constant.from_dict(obj.get(u"value"))
+        return Const(maybe_attributes, name, type, value)
+
+    def to_dict(self):
+        result = {}
+        result[u"maybe_attributes"] = from_union([lambda x: from_list(lambda x: to_class(Attribute, x), x), from_none], self.maybe_attributes)
+        result[u"name"] = from_str(self.name)
+        result[u"type"] = to_class(TypeClass, self.type)
+        result[u"value"] = to_class(Constant, self.value)
+        return result
+
+
+class DeclarationsMap(Enum):
+    CONST = u"const"
+    ENUM = u"enum"
+    INTERFACE = u"interface"
+    STRUCT = u"struct"
+    UNION = u"union"
+
+
+class EnumMember:
+    def __init__(self, name, value):
+        self.name = name
+        self.value = value
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        name = from_str(obj.get(u"name"))
+        value = Constant.from_dict(obj.get(u"value"))
+        return EnumMember(name, value)
+
+    def to_dict(self):
+        result = {}
+        result[u"name"] = from_str(self.name)
+        result[u"value"] = to_class(Constant, self.value)
+        return result
+
+
+class IntegerType(Enum):
+    INT16 = u"int16"
+    INT32 = u"int32"
+    INT64 = u"int64"
+    INT8 = u"int8"
+    UINT16 = u"uint16"
+    UINT32 = u"uint32"
+    UINT64 = u"uint64"
+    UINT8 = u"uint8"
+
+
+class EnumDeclarationElement:
+    def __init__(self, maybe_attributes, members, name, type):
+        self.maybe_attributes = maybe_attributes
+        self.members = members
+        self.name = name
+        self.type = type
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        maybe_attributes = from_union([lambda x: from_list(Attribute.from_dict, x), from_none], obj.get(u"maybe_attributes"))
+        members = from_list(EnumMember.from_dict, obj.get(u"members"))
+        name = from_str(obj.get(u"name"))
+        type = IntegerType(obj.get(u"type"))
+        return EnumDeclarationElement(maybe_attributes, members, name, type)
+
+    def to_dict(self):
+        result = {}
+        result[u"maybe_attributes"] = from_union([lambda x: from_list(lambda x: to_class(Attribute, x), x), from_none], self.maybe_attributes)
+        result[u"members"] = from_list(lambda x: to_class(EnumMember, x), self.members)
+        result[u"name"] = from_str(self.name)
+        result[u"type"] = to_enum(IntegerType, self.type)
+        return result
+
+
+class InterfaceMethodParameter:
+    def __init__(self, alignment, name, offset, size, type):
+        self.alignment = alignment
+        self.name = name
+        self.offset = offset
+        self.size = size
+        self.type = type
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        alignment = from_int(obj.get(u"alignment"))
+        name = from_str(obj.get(u"name"))
+        offset = from_int(obj.get(u"offset"))
+        size = from_int(obj.get(u"size"))
+        type = TypeClass.from_dict(obj.get(u"type"))
+        return InterfaceMethodParameter(alignment, name, offset, size, type)
+
+    def to_dict(self):
+        result = {}
+        result[u"alignment"] = from_int(self.alignment)
+        result[u"name"] = from_str(self.name)
+        result[u"offset"] = from_int(self.offset)
+        result[u"size"] = from_int(self.size)
+        result[u"type"] = to_class(TypeClass, self.type)
+        return result
+
+
+class InterfaceMethod:
+    def __init__(self, has_request, has_response, maybe_attributes, maybe_request, maybe_request_alignment, maybe_request_size, maybe_response, maybe_response_alignment, maybe_response_size, name, ordinal):
+        self.has_request = has_request
+        self.has_response = has_response
+        self.maybe_attributes = maybe_attributes
+        self.maybe_request = maybe_request
+        self.maybe_request_alignment = maybe_request_alignment
+        self.maybe_request_size = maybe_request_size
+        self.maybe_response = maybe_response
+        self.maybe_response_alignment = maybe_response_alignment
+        self.maybe_response_size = maybe_response_size
+        self.name = name
+        self.ordinal = ordinal
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        has_request = from_bool(obj.get(u"has_request"))
+        has_response = from_bool(obj.get(u"has_response"))
+        maybe_attributes = from_union([lambda x: from_list(Attribute.from_dict, x), from_none], obj.get(u"maybe_attributes"))
+        maybe_request = from_union([lambda x: from_list(InterfaceMethodParameter.from_dict, x), from_none], obj.get(u"maybe_request"))
+        maybe_request_alignment = from_union([from_int, from_none], obj.get(u"maybe_request_alignment"))
+        maybe_request_size = from_union([from_int, from_none], obj.get(u"maybe_request_size"))
+        maybe_response = from_union([lambda x: from_list(InterfaceMethodParameter.from_dict, x), from_none], obj.get(u"maybe_response"))
+        maybe_response_alignment = from_union([from_int, from_none], obj.get(u"maybe_response_alignment"))
+        maybe_response_size = from_union([from_int, from_none], obj.get(u"maybe_response_size"))
+        name = from_str(obj.get(u"name"))
+        ordinal = from_int(obj.get(u"ordinal"))
+        return InterfaceMethod(has_request, has_response, maybe_attributes, maybe_request, maybe_request_alignment, maybe_request_size, maybe_response, maybe_response_alignment, maybe_response_size, name, ordinal)
+
+    def to_dict(self):
+        result = {}
+        result[u"has_request"] = from_bool(self.has_request)
+        result[u"has_response"] = from_bool(self.has_response)
+        result[u"maybe_attributes"] = from_union([lambda x: from_list(lambda x: to_class(Attribute, x), x), from_none], self.maybe_attributes)
+        result[u"maybe_request"] = from_union([lambda x: from_list(lambda x: to_class(InterfaceMethodParameter, x), x), from_none], self.maybe_request)
+        result[u"maybe_request_alignment"] = from_union([from_int, from_none], self.maybe_request_alignment)
+        result[u"maybe_request_size"] = from_union([from_int, from_none], self.maybe_request_size)
+        result[u"maybe_response"] = from_union([lambda x: from_list(lambda x: to_class(InterfaceMethodParameter, x), x), from_none], self.maybe_response)
+        result[u"maybe_response_alignment"] = from_union([from_int, from_none], self.maybe_response_alignment)
+        result[u"maybe_response_size"] = from_union([from_int, from_none], self.maybe_response_size)
+        result[u"name"] = from_str(self.name)
+        result[u"ordinal"] = from_int(self.ordinal)
+        return result
+
+
+class Interface:
+    def __init__(self, maybe_attributes, methods, name):
+        self.maybe_attributes = maybe_attributes
+        self.methods = methods
+        self.name = name
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        maybe_attributes = from_union([lambda x: from_list(Attribute.from_dict, x), from_none], obj.get(u"maybe_attributes"))
+        methods = from_list(InterfaceMethod.from_dict, obj.get(u"methods"))
+        name = from_str(obj.get(u"name"))
+        return Interface(maybe_attributes, methods, name)
+
+    def to_dict(self):
+        result = {}
+        result[u"maybe_attributes"] = from_union([lambda x: from_list(lambda x: to_class(Attribute, x), x), from_none], self.maybe_attributes)
+        result[u"methods"] = from_list(lambda x: to_class(InterfaceMethod, x), self.methods)
+        result[u"name"] = from_str(self.name)
+        return result
+
+
+class Library:
+    def __init__(self, declarations, name):
+        self.declarations = declarations
+        self.name = name
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        declarations = from_dict(DeclarationsMap, obj.get(u"declarations"))
+        name = from_str(obj.get(u"name"))
+        return Library(declarations, name)
+
+    def to_dict(self):
+        result = {}
+        result[u"declarations"] = from_dict(lambda x: to_enum(DeclarationsMap, x), self.declarations)
+        result[u"name"] = from_str(self.name)
+        return result
+
+
+class StructMember:
+    def __init__(self, alignment, maybe_default_value, name, offset, size, type):
+        self.alignment = alignment
+        self.maybe_default_value = maybe_default_value
+        self.name = name
+        self.offset = offset
+        self.size = size
+        self.type = type
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        alignment = from_int(obj.get(u"alignment"))
+        maybe_default_value = from_union([Constant.from_dict, from_none], obj.get(u"maybe_default_value"))
+        name = from_str(obj.get(u"name"))
+        offset = from_int(obj.get(u"offset"))
+        size = from_int(obj.get(u"size"))
+        type = TypeClass.from_dict(obj.get(u"type"))
+        return StructMember(alignment, maybe_default_value, name, offset, size, type)
+
+    def to_dict(self):
+        result = {}
+        result[u"alignment"] = from_int(self.alignment)
+        result[u"maybe_default_value"] = from_union([lambda x: to_class(Constant, x), from_none], self.maybe_default_value)
+        result[u"name"] = from_str(self.name)
+        result[u"offset"] = from_int(self.offset)
+        result[u"size"] = from_int(self.size)
+        result[u"type"] = to_class(TypeClass, self.type)
+        return result
+
+
+class Struct:
+    def __init__(self, max_handles, maybe_attributes, members, name, size):
+        self.max_handles = max_handles
+        self.maybe_attributes = maybe_attributes
+        self.members = members
+        self.name = name
+        self.size = size
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        max_handles = from_union([from_int, from_none], obj.get(u"max_handles"))
+        maybe_attributes = from_union([lambda x: from_list(Attribute.from_dict, x), from_none], obj.get(u"maybe_attributes"))
+        members = from_list(StructMember.from_dict, obj.get(u"members"))
+        name = from_str(obj.get(u"name"))
+        size = from_int(obj.get(u"size"))
+        return Struct(max_handles, maybe_attributes, members, name, size)
+
+    def to_dict(self):
+        result = {}
+        result[u"max_handles"] = from_union([from_int, from_none], self.max_handles)
+        result[u"maybe_attributes"] = from_union([lambda x: from_list(lambda x: to_class(Attribute, x), x), from_none], self.maybe_attributes)
+        result[u"members"] = from_list(lambda x: to_class(StructMember, x), self.members)
+        result[u"name"] = from_str(self.name)
+        result[u"size"] = from_int(self.size)
+        return result
+
+
+class UnionMember:
+    def __init__(self, alignment, name, offset, size, type):
+        self.alignment = alignment
+        self.name = name
+        self.offset = offset
+        self.size = size
+        self.type = type
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        alignment = from_int(obj.get(u"alignment"))
+        name = from_str(obj.get(u"name"))
+        offset = from_int(obj.get(u"offset"))
+        size = from_int(obj.get(u"size"))
+        type = TypeClass.from_dict(obj.get(u"type"))
+        return UnionMember(alignment, name, offset, size, type)
+
+    def to_dict(self):
+        result = {}
+        result[u"alignment"] = from_int(self.alignment)
+        result[u"name"] = from_str(self.name)
+        result[u"offset"] = from_int(self.offset)
+        result[u"size"] = from_int(self.size)
+        result[u"type"] = to_class(TypeClass, self.type)
+        return result
+
+
+class UnionDeclarationElement:
+    def __init__(self, alignment, max_handles, maybe_attributes, members, name, size):
+        self.alignment = alignment
+        self.max_handles = max_handles
+        self.maybe_attributes = maybe_attributes
+        self.members = members
+        self.name = name
+        self.size = size
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        alignment = from_int(obj.get(u"alignment"))
+        max_handles = from_union([from_int, from_none], obj.get(u"max_handles"))
+        maybe_attributes = from_union([lambda x: from_list(Attribute.from_dict, x), from_none], obj.get(u"maybe_attributes"))
+        members = from_list(UnionMember.from_dict, obj.get(u"members"))
+        name = from_str(obj.get(u"name"))
+        size = from_int(obj.get(u"size"))
+        return UnionDeclarationElement(alignment, max_handles, maybe_attributes, members, name, size)
+
+    def to_dict(self):
+        result = {}
+        result[u"alignment"] = from_int(self.alignment)
+        result[u"max_handles"] = from_union([from_int, from_none], self.max_handles)
+        result[u"maybe_attributes"] = from_union([lambda x: from_list(lambda x: to_class(Attribute, x), x), from_none], self.maybe_attributes)
+        result[u"members"] = from_list(lambda x: to_class(UnionMember, x), self.members)
+        result[u"name"] = from_str(self.name)
+        result[u"size"] = from_int(self.size)
+        return result
+
+
+class Fidl:
+    def __init__(self, const_declarations, declaration_order, declarations, enum_declarations, interface_declarations, library_dependencies, name, struct_declarations, union_declarations, version):
+        self.const_declarations = const_declarations
+        self.declaration_order = declaration_order
+        self.declarations = declarations
+        self.enum_declarations = enum_declarations
+        self.interface_declarations = interface_declarations
+        self.library_dependencies = library_dependencies
+        self.name = name
+        self.struct_declarations = struct_declarations
+        self.union_declarations = union_declarations
+        self.version = version
+
+    @staticmethod
+    def from_dict(obj):
+        assert isinstance(obj, dict)
+        const_declarations = from_list(Const.from_dict, obj.get(u"const_declarations"))
+        declaration_order = from_list(from_str, obj.get(u"declaration_order"))
+        declarations = from_dict(DeclarationsMap, obj.get(u"declarations"))
+        enum_declarations = from_list(EnumDeclarationElement.from_dict, obj.get(u"enum_declarations"))
+        interface_declarations = from_list(Interface.from_dict, obj.get(u"interface_declarations"))
+        library_dependencies = from_list(Library.from_dict, obj.get(u"library_dependencies"))
+        name = from_str(obj.get(u"name"))
+        struct_declarations = from_list(Struct.from_dict, obj.get(u"struct_declarations"))
+        union_declarations = from_list(UnionDeclarationElement.from_dict, obj.get(u"union_declarations"))
+        version = from_str(obj.get(u"version"))
+        return Fidl(const_declarations, declaration_order, declarations, enum_declarations, interface_declarations, library_dependencies, name, struct_declarations, union_declarations, version)
+
+    def to_dict(self):
+        result = {}
+        result[u"const_declarations"] = from_list(lambda x: to_class(Const, x), self.const_declarations)
+        result[u"declaration_order"] = from_list(from_str, self.declaration_order)
+        result[u"declarations"] = from_dict(lambda x: to_enum(DeclarationsMap, x), self.declarations)
+        result[u"enum_declarations"] = from_list(lambda x: to_class(EnumDeclarationElement, x), self.enum_declarations)
+        result[u"interface_declarations"] = from_list(lambda x: to_class(Interface, x), self.interface_declarations)
+        result[u"library_dependencies"] = from_list(lambda x: to_class(Library, x), self.library_dependencies)
+        result[u"name"] = from_str(self.name)
+        result[u"struct_declarations"] = from_list(lambda x: to_class(Struct, x), self.struct_declarations)
+        result[u"union_declarations"] = from_list(lambda x: to_class(UnionDeclarationElement, x), self.union_declarations)
+        result[u"version"] = from_str(self.version)
+        return result
+
+
+def fidl_from_dict(s):
+    return Fidl.from_dict(s)
+
+
+def fidl_to_dict(x):
+    return to_class(Fidl, x)
+
diff --git a/tools/fuchsia/fidlgen_js/gen.py b/tools/fuchsia/fidlgen_js/gen.py
new file mode 100755
index 0000000..3d3aae1
--- /dev/null
+++ b/tools/fuchsia/fidlgen_js/gen.py
@@ -0,0 +1,320 @@
+#!/usr/bin/env python
+
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+from fidl import *
+import json
+
+
+class _CompoundIdentifier(object):
+  def __init__(self, library, name):
+    self.library = library
+    self.name = name
+
+
+def _ParseLibraryName(lib):
+  return lib.split('.')
+
+
+def _ParseCompoundIdentifier(ident):
+  parts = ident.split('/', 2)
+  raw_library = ''
+  raw_name = parts[0]
+  if len(parts) == 2:
+    raw_library, raw_name = parts
+  library = _ParseLibraryName(raw_library)
+  return _CompoundIdentifier(library, raw_name)
+
+
+def _ChangeIfReserved(name):
+  # TODO(crbug.com/883496): Remap any JS keywords.
+  return name
+
+
+def _CompileCompoundIdentifier(compound, ext=''):
+  result = _ChangeIfReserved(compound.name) + ext
+  return result
+
+
+def _CompileIdentifier(ident):
+  return _ChangeIfReserved(ident)
+
+
+def _JsTypeForPrimitiveType(t):
+  mapping = {
+    IntegerType.INT16: 'number',
+    IntegerType.INT32: 'number',
+    IntegerType.INT64: 'BigInt',
+    IntegerType.INT8: 'number',
+    IntegerType.UINT16: 'number',
+    IntegerType.UINT32: 'number',
+    IntegerType.UINT64: 'BigInt',
+    IntegerType.UINT8: 'number',
+  }
+  return mapping[t]
+
+
+def _InlineSizeOfType(t):
+  if t.kind == TypeKind.PRIMITIVE:
+    return {
+        'int16': 2,
+        'int32': 4,
+        'int64': 8,
+        'int8': 1,
+        'uint16': 2,
+        'uint32': 4,
+        'uint64': 8,
+        'uint8': 1,
+        }[t.subtype]
+  else:
+    raise NotImplementedError()
+
+
+def _CompileConstant(val):
+  if val.kind == ConstantKind.IDENTIFIER:
+    raise NotImplementedError()
+  elif val.kind == ConstantKind.LITERAL:
+    return _CompileLiteral(val.literal)
+  else:
+    raise Exception('unexpected kind')
+
+
+def _CompileLiteral(val):
+  if val.kind == LiteralKind.STRING:
+    # TODO(crbug.com/883496): This needs to encode the string in an escaped
+    # form suitable to JS. Currently using the escaped Python representation,
+    # which is passably compatible, but surely has differences in edge cases.
+    return repr(val.value)
+  elif val.kind == LiteralKind.NUMERIC:
+    return val.value
+  elif val.kind == LiteralKind.TRUE:
+    return 'true'
+  elif val.kind == LiteralKind.FALSE:
+    return 'false'
+  elif val.kind == LiteralKind.DEFAULT:
+    return 'default'
+  else:
+    raise Exception('unexpected kind')
+
+
+class Compiler(object):
+  def __init__(self, fidl, output_file):
+    self.fidl = fidl
+    self.f = output_file
+
+  def Compile(self):
+    self._EmitHeader()
+    for c in self.fidl.const_declarations:
+      self._CompileConst(c)
+    for e in self.fidl.enum_declarations:
+      self._CompileEnum(e)
+    if self.fidl.union_declarations:
+      raise NotImplementedError()
+    if self.fidl.struct_declarations:
+      raise NotImplementedError()
+    for i in self.fidl.interface_declarations:
+      self._CompileInterface(i)
+
+  def _EmitHeader(self):
+    self.f.write('''// 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.
+//
+// WARNING: This file is machine generated by fidlgen_js.
+
+''')
+
+  def _CompileConst(self, c):
+      self.f.write('''/**
+  * @const {%(type)s}
+  */
+const %(name)s = %(value)s;
+''' % c.to_dict())
+
+  def _CompileEnum(self, enum):
+    compound = _ParseCompoundIdentifier(enum.name)
+    name = _CompileCompoundIdentifier(compound)
+    js_type = _JsTypeForPrimitiveType(enum.type)
+    data = { 'js_type': js_type, 'type': enum.type.value, 'name': name }
+    self.f.write('''/**
+ * @enum {%(js_type)s}
+ */
+const %(name)s = {
+''' % data)
+    for member in enum.members:
+      self.f.write('''  %s: %s,\n''' %
+          (member.name, _CompileConstant(member.value)))
+    self.f.write('};\n')
+    self.f.write('const _kTT_%(name)s = _kTT_%(type)s;\n\n' % data)
+
+
+  def _CompileType(self, t):
+    if t.kind == TypeKind.PRIMITIVE:
+      return t.subtype
+    elif t.kind == TypeKind.STRING:
+      return 'String' + ('_Nullable' if t.nullable else '_Nonnull')
+    elif t.kind == TypeKind.IDENTIFIER:
+      compound = _ParseCompoundIdentifier(t.identifier)
+      name = _CompileCompoundIdentifier(compound)
+      return name
+    elif t.kind == TypeKind.VECTOR:
+      element_ttname = self._CompileType(t.element_type)
+      ttname = ('VEC_' + ('Nullable_' if t.nullable else 'Nonnull_') +
+                element_ttname)
+      throw_if_null = '/* v may be null */'
+      pointer_set = '''    if (v === null || v === undefined) {
+      e.data.setUint32(o + 8, 0, $fidl__kLE);
+      e.data.setUint32(o + 12, 0, $fidl__kLE);
+    } else {
+      e.data.setUint32(o + 8, 0xffffffff, $fidl__kLE);
+      e.data.setUint32(o + 12, 0xffffffff, $fidl__kLE);
+    }'''
+      if not t.nullable:
+        throw_if_null = ('if (v === null || v === undefined) '
+                         'throw "non-null vector required";')
+        pointer_set = '''    e.data.setUint32(o + 8, 0xffffffff, $fidl__kLE);
+    e.data.setUint32(o + 12, 0xffffffff, $fidl__kLE);'''
+
+      self.f.write(
+'''const _kTT_%(ttname)s = {
+  enc: function(e, o, v) {
+    %(throw_if_null)s
+    e.data.setUint32(o, v.length, $fidl__kLE);
+    e.data.setUint32(o + 4, 0, $fidl__kLE);
+%(pointer_set)s
+    e.outOfLine.push([function(e, body) {
+        var start = e.alloc(body.length * %(element_size)s);
+        for (var i = 0; i < body.length; i++) {
+          _kTT_%(element_ttname)s.enc(e, start + (i * %(element_size)s), body[i]);
+        }
+      },
+      v]);
+  },
+};
+
+''' % { 'ttname': ttname,
+        'element_ttname': element_ttname,
+        'element_size': _InlineSizeOfType(t.element_type),
+        'pointer_set': pointer_set,
+        'throw_if_null': throw_if_null })
+      return ttname
+    else:
+      raise NotImplementedError()
+
+
+  def _GenerateJsInterfaceForInterface(self, name, interface):
+    """Generates a JS @interface for the given FIDL interface."""
+    self.f.write('''/**
+ * @interface
+ */
+function %(name)s() {}
+
+''' % { 'name': name })
+
+    # Define a JS interface part for the interface for typechecking.
+    for method in interface.methods:
+      method_name = _CompileIdentifier(method.name)
+      if method.has_request:
+        param_names = [_CompileIdentifier(x.name)
+                       for x in method.maybe_request]
+        if len(param_names):
+          self.f.write('/**\n')
+          # TODO(crbug.com/883496): Emit @param type comments here.
+          self.f.write(' */\n')
+        self.f.write('%(name)s.prototype.%(method_name)s = '
+                'function(%(param_names)s) {};\n\n' % {
+                  'name': name,
+                  'method_name': method_name,
+                  'param_names': ', '.join(param_names)})
+
+    # Emit message ordinals for later use.
+    for method in interface.methods:
+      method_name = _CompileIdentifier(method.name)
+      self.f.write(
+          'const _k%(name)s_%(method_name)s_Ordinal = %(ordinal)s;\n' % {
+              'name': name,
+              'method_name': method_name,
+              'ordinal': method.ordinal})
+
+    self.f.write('\n')
+
+  def _GenerateJsProxyForInterface(self, name, interface):
+    """Generates the JS side implementation of a proxy class implementing the
+    given interface."""
+    proxy_name = name + 'Proxy'
+    self.f.write('''/**
+ * @constructor
+ * @implements %(name)s
+ */
+function %(proxy_name)s() {
+  this.channel = zx.ZX_HANDLE_INVALID;
+}
+
+%(proxy_name)s.prototype.$bind = function(channel) {
+  this.channel = channel;
+};
+
+''' % { 'name': name,
+        'proxy_name': proxy_name })
+    for method in interface.methods:
+      method_name = _CompileIdentifier(method.name)
+      if method.has_request:
+        type_tables = []
+        for param in method.maybe_request:
+          type_tables.append(self._CompileType(param.type))
+        param_names = [_CompileIdentifier(x.name) for x in method.maybe_request]
+        self.f.write(
+'''%(proxy_name)s.prototype.%(method_name)s = function(%(param_names)s) {
+  if (this.channel === zx.ZX_HANDLE_INVALID) {
+    throw "channel closed";
+  }
+  var $encoder = new $fidl_Encoder(_k%(name)s_%(method_name)s_Ordinal);
+  $encoder.alloc(%(size)s - $fidl_kMessageHeaderSize);
+''' % { 'name': name,
+        'proxy_name': proxy_name,
+        'method_name': method_name,
+        'param_names': ', '.join(param_names),
+        'size': method.maybe_request_size})
+
+        for param, ttname in zip(method.maybe_request, type_tables):
+          self.f.write(
+'''  _kTT_%(type_table)s.enc($encoder, %(offset)s, %(param_name)s);
+''' % { 'type_table': ttname,
+        'param_name': _CompileIdentifier(param.name),
+        'offset': param.offset })
+
+        self.f.write(
+'''  var $result = zx.channelWrite(this.channel,
+                                $encoder.messageData(),
+                                $encoder.messageHandles());
+  if ($result !== zx.ZX_OK) {
+    throw "zx.channelWrite failed: " + $result;
+  }
+};
+
+''')
+
+  def _CompileInterface(self, interface):
+    compound = _ParseCompoundIdentifier(interface.name)
+    name = _CompileCompoundIdentifier(compound)
+    self._GenerateJsInterfaceForInterface(name, interface)
+    self._GenerateJsProxyForInterface(name, interface)
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('json')
+  parser.add_argument('--output', required=True)
+  args = parser.parse_args()
+
+  fidl = fidl_from_dict(json.load(open(args.json, 'r')))
+  with open(args.output, 'w') as f:
+    c = Compiler(fidl, f)
+    c.Compile()
+
+
+if __name__ == '__main__':
+  main()
diff --git a/tools/fuchsia/fidlgen_js/runtime/fidl.mjs b/tools/fuchsia/fidlgen_js/runtime/fidl.mjs
new file mode 100644
index 0000000..7ccc5155
--- /dev/null
+++ b/tools/fuchsia/fidlgen_js/runtime/fidl.mjs
@@ -0,0 +1,136 @@
+// 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.
+
+// This is the JS runtime support library for code generated by fidlgen_js. It
+// mostly consists of helpers to facilitate encoding and decoding of FIDL
+// messages.
+
+const $fidl_kInitialBufferSize = 1024;
+
+const $fidl_kMessageHeaderSize = 16;
+const $fidl_kMessageTxidOffset = 0;
+const $fidl_kMessageOrdinalOffset = 12;
+
+const $fidl__kAlignment = 8;
+const $fidl__kAlignmentMask = 0x7;
+
+const $fidl__kLE = true;
+
+function $fidl__align(size) {
+  return size + (($fidl__kAlignment - (size & $fidl__kAlignmentMask)) &
+                 $fidl__kAlignmentMask);
+}
+
+/**
+ * @constructor
+ * @param {number} ordinal
+ */
+function $fidl_Encoder(ordinal) {
+  var buf = new ArrayBuffer($fidl_kInitialBufferSize);
+  this.data = new DataView(buf);
+  this.extent = 0;
+  this.handles = [];
+  this.outOfLine = [];
+  this._encodeMessageHeader(ordinal);
+}
+
+/**
+ * @param {number} ordinal
+ */
+$fidl_Encoder.prototype._encodeMessageHeader = function(ordinal) {
+  this.alloc($fidl_kMessageHeaderSize);
+  this.data.setUint32($fidl_kMessageOrdinalOffset, ordinal, $fidl__kLE);
+};
+
+/**
+ * @param {number} size
+ */
+$fidl_Encoder.prototype.alloc = function(size) {
+  var offset = this.extent;
+  this._claimMemory($fidl__align(size));
+  return offset;
+};
+
+/**
+ * @param {number} claimSize
+ */
+$fidl_Encoder.prototype._claimMemory = function(claimSize) {
+  this.extent += claimSize;
+  if (this.extent > this.data.byteLength) {
+    var newSize = this.data.byteLength + claimSize;
+    newSize += newSize * 2;
+    this._grow(newSize);
+  }
+};
+
+/**
+ * @param {number} newSize
+ */
+$fidl_Encoder.prototype._grow = function(newSize) {
+  var newBuffer = new ArrayBuffer(newSize);
+  new Uint8Array(newBuffer).set(new Uint8Array(this.data.buffer));
+  this.data = new DataView(newBuffer);
+};
+
+$fidl_Encoder.prototype.messageData = function() {
+  // Add all out of line data.
+  var len = this.outOfLine.length;
+  for (var i = 0; i < len; i++) {
+    this.outOfLine[i][0](this, this.outOfLine[i][1]);
+  }
+
+  // Return final result.
+  return new DataView(this.data.buffer, 0, this.extent);
+};
+
+$fidl_Encoder.prototype.messageHandles = function() {
+  return this.handles;
+};
+
+
+// Type tables and encoding helpers for generated Proxy code.
+const _kTT_int8 = {
+  enc: function(e, o, v) { e.data.setInt8(o, v, $fidl__kLE); },
+};
+
+const _kTT_int16 = {
+  enc: function(e, o, v) { e.data.setInt16(o, v, $fidl__kLE); },
+};
+
+const _kTT_int32 = {
+  enc: function(e, o, v) { e.data.setUint32(o, v, $fidl__kLE); },
+};
+
+const _kTT_uint8 = {
+  enc: function(e, o, v) { e.data.setUint8(o, v, $fidl__kLE); },
+};
+
+const _kTT_uint16 = {
+  enc: function(e, o, v) { e.data.setUint16(o, v, $fidl__kLE); },
+};
+
+const _kTT_uint32 = {
+  enc: function(e, o, v) { e.data.setUint32(o, v, $fidl__kLE); },
+};
+
+const _kTT_String_Nonnull = {
+  enc: function(e, o, v) {
+    if (v === null || v === undefined) throw "non-null string required";
+    // Both size and data are uint64, but that's awkward in JS, so for now only
+    // support a maximum of 32b lengths.
+    var asUtf8 = zx.strToUtf8Array(v);
+    e.data.setUint32(o, asUtf8.length, $fidl__kLE);
+    e.data.setUint32(o + 4, 0, $fidl__kLE);
+    e.data.setUint32(o + 8, 0xffffffff, $fidl__kLE);
+    e.data.setUint32(o + 12, 0xffffffff, $fidl__kLE);
+    e.outOfLine.push([$fidl_OutOfLineStringEnc, asUtf8]);
+  },
+};
+
+function $fidl_OutOfLineStringEnc(e, strAsUtf8Array) {
+  var start = e.alloc(strAsUtf8Array.length);
+  for (var i = 0; i < strAsUtf8Array.length; i++) {
+    e.data.setUint8(start + i, strAsUtf8Array[i], $fidl__kLE);
+  }
+}
diff --git a/tools/fuchsia/fidlgen_js/runtime/zircon.cc b/tools/fuchsia/fidlgen_js/runtime/zircon.cc
new file mode 100644
index 0000000..7ae91489
--- /dev/null
+++ b/tools/fuchsia/fidlgen_js/runtime/zircon.cc
@@ -0,0 +1,178 @@
+// 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 "tools/fuchsia/fidlgen_js/runtime/zircon.h"
+
+#include <lib/zx/channel.h>
+#include <zircon/errors.h>
+#include <zircon/syscalls.h>
+#include <zircon/types.h>
+
+#include "base/bind.h"
+#include "gin/arguments.h"
+#include "gin/array_buffer.h"
+#include "gin/converter.h"
+#include "gin/data_object_builder.h"
+#include "gin/function_template.h"
+
+namespace {
+
+v8::Local<v8::Object> ZxChannelCreate(v8::Isolate* isolate) {
+  zx::channel c1, c2;
+  zx_status_t status = zx::channel::create(0, &c1, &c2);
+  return gin::DataObjectBuilder(isolate)
+      .Set("status", status)
+      .Set("first", c1.release())
+      .Set("second", c2.release())
+      .Build();
+}
+
+zx_status_t ZxChannelWrite(gin::Arguments* args) {
+  zx_handle_t handle;
+  if (!args->GetNext(&handle)) {
+    args->ThrowError();
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  gin::ArrayBufferView data;
+  if (!args->GetNext(&data)) {
+    args->ThrowError();
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  std::vector<zx_handle_t> handles;
+  if (!args->GetNext(&handles)) {
+    args->ThrowError();
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  zx_status_t status =
+      zx_channel_write(handle, 0, data.bytes(), data.num_bytes(),
+                       handles.data(), handles.size());
+  return status;
+}
+
+v8::Local<v8::Value> StrToUtf8Array(gin::Arguments* args) {
+  std::string str;
+  // This converts the string to utf8 from ucs2, so then just repackage the
+  // string as an array and return it.
+  if (!args->GetNext(&str)) {
+    args->ThrowError();
+    return v8::Local<v8::Object>();
+  }
+
+  // TODO(crbug.com/883496): Not sure how to make a Uint8Array to return here
+  // which would be a bit more efficient.
+  std::vector<int> data;
+  std::copy(str.begin(), str.end(), std::back_inserter(data));
+  return gin::ConvertToV8(args->isolate(), data);
+}
+
+v8::Local<v8::Object> GetOrCreateZxObject(v8::Isolate* isolate,
+                                          v8::Local<v8::Object> global) {
+  v8::Local<v8::Object> zx;
+  v8::Local<v8::Value> zx_value = global->Get(gin::StringToV8(isolate, "zx"));
+  if (zx_value.IsEmpty() || !zx_value->IsObject()) {
+    zx = v8::Object::New(isolate);
+    global->Set(gin::StringToSymbol(isolate, "zx"), zx);
+  } else {
+    zx = v8::Local<v8::Object>::Cast(zx);
+  }
+  return zx;
+}
+
+}  // namespace
+
+namespace fidljs {
+
+void InjectZxBindings(v8::Isolate* isolate, v8::Local<v8::Object> global) {
+  v8::Local<v8::Object> zx = GetOrCreateZxObject(isolate, global);
+
+#define SET_CONSTANT(k) \
+  zx->Set(gin::StringToSymbol(isolate, #k), gin::ConvertToV8(isolate, k))
+
+  // zx_status_t.
+  SET_CONSTANT(ZX_OK);
+  SET_CONSTANT(ZX_ERR_INTERNAL);
+  SET_CONSTANT(ZX_ERR_NOT_SUPPORTED);
+  SET_CONSTANT(ZX_ERR_NO_RESOURCES);
+  SET_CONSTANT(ZX_ERR_NO_MEMORY);
+  SET_CONSTANT(ZX_ERR_INTERNAL_INTR_RETRY);
+  SET_CONSTANT(ZX_ERR_INVALID_ARGS);
+  SET_CONSTANT(ZX_ERR_BAD_HANDLE);
+  SET_CONSTANT(ZX_ERR_WRONG_TYPE);
+  SET_CONSTANT(ZX_ERR_BAD_SYSCALL);
+  SET_CONSTANT(ZX_ERR_OUT_OF_RANGE);
+  SET_CONSTANT(ZX_ERR_BUFFER_TOO_SMALL);
+  SET_CONSTANT(ZX_ERR_BAD_STATE);
+  SET_CONSTANT(ZX_ERR_TIMED_OUT);
+  SET_CONSTANT(ZX_ERR_SHOULD_WAIT);
+  SET_CONSTANT(ZX_ERR_CANCELED);
+  SET_CONSTANT(ZX_ERR_PEER_CLOSED);
+  SET_CONSTANT(ZX_ERR_NOT_FOUND);
+  SET_CONSTANT(ZX_ERR_ALREADY_EXISTS);
+  SET_CONSTANT(ZX_ERR_ALREADY_BOUND);
+  SET_CONSTANT(ZX_ERR_UNAVAILABLE);
+  SET_CONSTANT(ZX_ERR_ACCESS_DENIED);
+  SET_CONSTANT(ZX_ERR_IO);
+  SET_CONSTANT(ZX_ERR_IO_REFUSED);
+  SET_CONSTANT(ZX_ERR_IO_DATA_INTEGRITY);
+  SET_CONSTANT(ZX_ERR_IO_DATA_LOSS);
+  SET_CONSTANT(ZX_ERR_IO_NOT_PRESENT);
+  SET_CONSTANT(ZX_ERR_IO_OVERRUN);
+  SET_CONSTANT(ZX_ERR_IO_MISSED_DEADLINE);
+  SET_CONSTANT(ZX_ERR_IO_INVALID);
+  SET_CONSTANT(ZX_ERR_BAD_PATH);
+  SET_CONSTANT(ZX_ERR_NOT_DIR);
+  SET_CONSTANT(ZX_ERR_NOT_FILE);
+  SET_CONSTANT(ZX_ERR_FILE_BIG);
+  SET_CONSTANT(ZX_ERR_NO_SPACE);
+  SET_CONSTANT(ZX_ERR_NOT_EMPTY);
+  SET_CONSTANT(ZX_ERR_STOP);
+  SET_CONSTANT(ZX_ERR_NEXT);
+  SET_CONSTANT(ZX_ERR_ASYNC);
+  SET_CONSTANT(ZX_ERR_PROTOCOL_NOT_SUPPORTED);
+  SET_CONSTANT(ZX_ERR_ADDRESS_UNREACHABLE);
+  SET_CONSTANT(ZX_ERR_ADDRESS_IN_USE);
+  SET_CONSTANT(ZX_ERR_NOT_CONNECTED);
+  SET_CONSTANT(ZX_ERR_CONNECTION_REFUSED);
+  SET_CONSTANT(ZX_ERR_CONNECTION_RESET);
+  SET_CONSTANT(ZX_ERR_CONNECTION_ABORTED);
+
+  // Handle APIs.
+  zx->Set(gin::StringToSymbol(isolate, "handleClose"),
+          gin::CreateFunctionTemplate(isolate,
+                                      base::BindRepeating(&zx_handle_close))
+              ->GetFunction());
+  SET_CONSTANT(ZX_HANDLE_INVALID);
+
+  // Channel APIs.
+  zx->Set(gin::StringToSymbol(isolate, "channelCreate"),
+          gin::CreateFunctionTemplate(isolate,
+                                      base::BindRepeating(&ZxChannelCreate))
+              ->GetFunction());
+  zx->Set(
+      gin::StringToSymbol(isolate, "channelWrite"),
+      gin::CreateFunctionTemplate(isolate, base::BindRepeating(&ZxChannelWrite))
+          ->GetFunction());
+  SET_CONSTANT(ZX_CHANNEL_READABLE);
+  SET_CONSTANT(ZX_CHANNEL_WRITABLE);
+  SET_CONSTANT(ZX_CHANNEL_PEER_CLOSED);
+  SET_CONSTANT(ZX_CHANNEL_READ_MAY_DISCARD);
+  SET_CONSTANT(ZX_CHANNEL_MAX_MSG_BYTES);
+  SET_CONSTANT(ZX_CHANNEL_MAX_MSG_HANDLES);
+
+  // Utility to make string handling easier to convert from a UCS2 JS string to
+  // an array of UTF-8 (which is how strings are represented in FIDL).
+  // TODO(crbug.com/883496): This is not really zx, should move to a generic
+  // runtime helper file if there are more similar C++ helpers required.
+  zx->Set(
+      gin::StringToSymbol(isolate, "strToUtf8Array"),
+      gin::CreateFunctionTemplate(isolate, base::BindRepeating(&StrToUtf8Array))
+          ->GetFunction());
+
+#undef SET_CONSTANT
+}
+
+}  // namespace fidljs
diff --git a/tools/fuchsia/fidlgen_js/runtime/zircon.h b/tools/fuchsia/fidlgen_js/runtime/zircon.h
new file mode 100644
index 0000000..7bf6ea7
--- /dev/null
+++ b/tools/fuchsia/fidlgen_js/runtime/zircon.h
@@ -0,0 +1,17 @@
+// 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 TOOLS_FUCHSIA_FIDLGEN_JS_RUNTIME_ZIRCON_H_
+#define TOOLS_FUCHSIA_FIDLGEN_JS_RUNTIME_ZIRCON_H_
+
+#include "v8/include/v8.h"
+
+namespace fidljs {
+
+// Adds Zircon APIs bindings to |global|, for use by JavaScript callers.
+void InjectZxBindings(v8::Isolate* isolate, v8::Local<v8::Object> global);
+
+}  // namespace fidljs
+
+#endif  // TOOLS_FUCHSIA_FIDLGEN_JS_RUNTIME_ZIRCON_H_
diff --git a/tools/fuchsia/fidlgen_js/test/fidlgen_js_unittest.cc b/tools/fuchsia/fidlgen_js/test/fidlgen_js_unittest.cc
new file mode 100644
index 0000000..260fd1c
--- /dev/null
+++ b/tools/fuchsia/fidlgen_js/test/fidlgen_js_unittest.cc
@@ -0,0 +1,332 @@
+// 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 "base/bind.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "gin/converter.h"
+#include "gin/modules/console.h"
+#include "gin/object_template_builder.h"
+#include "gin/public/isolate_holder.h"
+#include "gin/shell_runner.h"
+#include "gin/test/v8_test.h"
+#include "gin/try_catch.h"
+#include "tools/fuchsia/fidlgen_js/fidl/fidljstest/cpp/fidl.h"
+#include "tools/fuchsia/fidlgen_js/runtime/zircon.h"
+#include "v8/include/v8.h"
+
+static const char kRuntimeFile[] =
+    "/pkg/tools/fuchsia/fidlgen_js/runtime/fidl.mjs";
+static const char kTestBindingFile[] =
+    "/pkg/tools/fuchsia/fidlgen_js/fidl/fidljstest/js/fidl.js";
+
+class FidlGenJsTestShellRunnerDelegate : public gin::ShellRunnerDelegate {
+ public:
+  FidlGenJsTestShellRunnerDelegate() {}
+
+  v8::Local<v8::ObjectTemplate> GetGlobalTemplate(
+      gin::ShellRunner* runner,
+      v8::Isolate* isolate) override {
+    v8::Local<v8::ObjectTemplate> templ =
+        gin::ObjectTemplateBuilder(isolate).Build();
+    gin::Console::Register(isolate, templ);
+    return templ;
+  }
+
+  void UnhandledException(gin::ShellRunner* runner,
+                          gin::TryCatch& try_catch) override {
+    LOG(ERROR) << try_catch.GetStackTrace();
+    ADD_FAILURE();
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FidlGenJsTestShellRunnerDelegate);
+};
+
+using FidlGenJsTest = gin::V8Test;
+
+TEST_F(FidlGenJsTest, BasicJSSetup) {
+  v8::Isolate* isolate = instance_->isolate();
+
+  std::string source = "log('this is a log'); this.stuff = 'HAI';";
+  FidlGenJsTestShellRunnerDelegate delegate;
+  gin::ShellRunner runner(&delegate, isolate);
+  gin::Runner::Scope scope(&runner);
+  runner.Run(source, "test.js");
+
+  std::string result;
+  EXPECT_TRUE(gin::Converter<std::string>::FromV8(
+      isolate, runner.global()->Get(gin::StringToV8(isolate, "stuff")),
+      &result));
+  EXPECT_EQ("HAI", result);
+}
+
+TEST_F(FidlGenJsTest, CreateChannelPair) {
+  v8::Isolate* isolate = instance_->isolate();
+  v8::HandleScope handle_scope(isolate);
+
+  FidlGenJsTestShellRunnerDelegate delegate;
+  gin::ShellRunner runner(&delegate, isolate);
+  gin::Runner::Scope scope(&runner);
+
+  fidljs::InjectZxBindings(isolate, runner.global());
+
+  std::string source = R"(
+    var result = zx.channelCreate();
+    this.result_status = result.status;
+    this.result_h1 = result.first;
+    this.result_h2 = result.second;
+    if (result.status == zx.ZX_OK) {
+      zx.handleClose(result.first);
+      zx.handleClose(result.second);
+    }
+  )";
+
+  runner.Run(source, "test.js");
+
+  zx_status_t status = ZX_ERR_INTERNAL;
+  EXPECT_TRUE(gin::Converter<zx_status_t>::FromV8(
+      isolate, runner.global()->Get(gin::StringToV8(isolate, "result_status")),
+      &status));
+  EXPECT_EQ(status, ZX_OK);
+
+  zx_handle_t handle = ZX_HANDLE_INVALID;
+
+  EXPECT_TRUE(gin::Converter<zx_handle_t>::FromV8(
+      isolate, runner.global()->Get(gin::StringToV8(isolate, "result_h1")),
+      &handle));
+  EXPECT_NE(handle, ZX_HANDLE_INVALID);
+
+  EXPECT_TRUE(gin::Converter<zx_handle_t>::FromV8(
+      isolate, runner.global()->Get(gin::StringToV8(isolate, "result_h2")),
+      &handle));
+  EXPECT_NE(handle, ZX_HANDLE_INVALID);
+}
+
+class TestolaImpl : public fidljstest::Testola {
+ public:
+  TestolaImpl() = default;
+  ~TestolaImpl() override {}
+
+  void DoSomething() override { was_do_something_called_ = true; }
+
+  void PrintInt(int32_t number) override { received_int_ = number; }
+
+  void PrintMsg(fidl::StringPtr message) override {
+    std::string as_str = message.get();
+    received_msg_ = as_str;
+  }
+
+  void VariousArgs(fidljstest::Blorp blorp,
+                   fidl::StringPtr msg,
+                   fidl::VectorPtr<uint32_t> stuff) override {
+    std::string msg_as_str = msg.get();
+    std::vector<uint32_t> stuff_as_vec = stuff.get();
+    various_blorp_ = blorp;
+    various_msg_ = msg_as_str;
+    various_stuff_ = stuff_as_vec;
+  }
+
+  bool was_do_something_called() const { return was_do_something_called_; }
+  int32_t received_int() const { return received_int_; }
+  const std::string& received_msg() const { return received_msg_; }
+
+  fidljstest::Blorp various_blorp() const { return various_blorp_; }
+  const std::string& various_msg() const { return various_msg_; }
+  const std::vector<uint32_t>& various_stuff() const { return various_stuff_; }
+
+ private:
+  bool was_do_something_called_ = false;
+  int32_t received_int_ = -1;
+  std::string received_msg_;
+  fidljstest::Blorp various_blorp_;
+  std::string various_msg_;
+  std::vector<uint32_t> various_stuff_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestolaImpl);
+};
+
+void LoadAndSource(gin::ShellRunner* runner, const base::FilePath& filename) {
+  std::string contents;
+  ASSERT_TRUE(base::ReadFileToString(filename, &contents));
+
+  runner->Run(contents, filename.MaybeAsASCII());
+}
+
+class BindingsSetupHelper {
+ public:
+  explicit BindingsSetupHelper(v8::Isolate* isolate)
+      : handle_scope_(isolate), delegate_(), runner_(&delegate_, isolate) {
+    gin::Runner::Scope scope(&runner_);
+
+    fidljs::InjectZxBindings(isolate, runner_.global());
+
+    // TODO(crbug.com/883496): Figure out how to set up v8 import hooking and
+    // make fidl_Xyz into $fidl.Xyz. Manually inject the runtime support js
+    // files for now.
+    LoadAndSource(&runner_, base::FilePath(kRuntimeFile));
+    LoadAndSource(&runner_, base::FilePath(kTestBindingFile));
+
+    zx_status_t status = zx::channel::create(0, &server_, &client_);
+    EXPECT_EQ(status, ZX_OK);
+
+    runner_.global()->Set(gin::StringToSymbol(isolate, "testHandle"),
+                          gin::ConvertToV8(isolate, client_.get()));
+  }
+
+  zx::channel& server() { return server_; }
+  zx::channel& client() { return client_; }
+  gin::ShellRunner& runner() { return runner_; }
+
+ private:
+  v8::HandleScope handle_scope_;
+  FidlGenJsTestShellRunnerDelegate delegate_;
+  gin::ShellRunner runner_;
+  zx::channel server_;
+  zx::channel client_;
+
+  DISALLOW_COPY_AND_ASSIGN(BindingsSetupHelper);
+};
+
+TEST_F(FidlGenJsTest, RawReceiveFidlMessage) {
+  v8::Isolate* isolate = instance_->isolate();
+  BindingsSetupHelper helper(isolate);
+
+  // Send the data from the JS side into the channel.
+  std::string source = R"(
+    var proxy = new TestolaProxy();
+    proxy.$bind(testHandle);
+    proxy.DoSomething();
+  )";
+  helper.runner().Run(source, "test.js");
+
+  // Read it out, decode, and confirm it was dispatched.
+  TestolaImpl testola_impl;
+  fidljstest::Testola_Stub stub(&testola_impl);
+  uint8_t data[1024];
+  zx_handle_t handles[1];
+  uint32_t actual_bytes, actual_handles;
+  ASSERT_EQ(helper.server().read(0, data, base::size(data), &actual_bytes,
+                                 handles, base::size(handles), &actual_handles),
+            ZX_OK);
+  EXPECT_EQ(actual_bytes, 16u);
+  EXPECT_EQ(actual_handles, 0u);
+
+  fidl::Message message(
+      fidl::BytePart(data, actual_bytes, actual_bytes),
+      fidl::HandlePart(handles, actual_handles, actual_handles));
+  stub.Dispatch_(std::move(message), fidl::internal::PendingResponse());
+
+  EXPECT_TRUE(testola_impl.was_do_something_called());
+}
+
+TEST_F(FidlGenJsTest, RawReceiveFidlMessageWithSimpleArg) {
+  v8::Isolate* isolate = instance_->isolate();
+  BindingsSetupHelper helper(isolate);
+
+  // Send the data from the JS side into the channel.
+  std::string source = R"(
+    var proxy = new TestolaProxy();
+    proxy.$bind(testHandle);
+    proxy.PrintInt(12345);
+  )";
+  helper.runner().Run(source, "test.js");
+
+  // Read it out, decode, and confirm it was dispatched.
+  TestolaImpl testola_impl;
+  fidljstest::Testola_Stub stub(&testola_impl);
+  uint8_t data[1024];
+  zx_handle_t handles[1];
+  uint32_t actual_bytes, actual_handles;
+  ASSERT_EQ(helper.server().read(0, data, base::size(data), &actual_bytes,
+                                 handles, base::size(handles), &actual_handles),
+            ZX_OK);
+  // 24 rather than 20 because everything's 8 aligned.
+  EXPECT_EQ(actual_bytes, 24u);
+  EXPECT_EQ(actual_handles, 0u);
+
+  fidl::Message message(
+      fidl::BytePart(data, actual_bytes, actual_bytes),
+      fidl::HandlePart(handles, actual_handles, actual_handles));
+  stub.Dispatch_(std::move(message), fidl::internal::PendingResponse());
+
+  EXPECT_EQ(testola_impl.received_int(), 12345);
+}
+
+TEST_F(FidlGenJsTest, RawReceiveFidlMessageWithStringArg) {
+  v8::Isolate* isolate = instance_->isolate();
+  BindingsSetupHelper helper(isolate);
+
+  // Send the data from the JS side into the channel.
+  std::string source = R"(
+    var proxy = new TestolaProxy();
+    proxy.$bind(testHandle);
+    proxy.PrintMsg('Ça c\'est a 你好 from deep in JS');
+  )";
+  helper.runner().Run(source, "test.js");
+
+  // Read it out, decode, and confirm it was dispatched.
+  TestolaImpl testola_impl;
+  fidljstest::Testola_Stub stub(&testola_impl);
+  uint8_t data[1024];
+  zx_handle_t handles[1];
+  uint32_t actual_bytes, actual_handles;
+  ASSERT_EQ(helper.server().read(0, data, base::size(data), &actual_bytes,
+                                 handles, base::size(handles), &actual_handles),
+            ZX_OK);
+  EXPECT_EQ(actual_handles, 0u);
+
+  fidl::Message message(
+      fidl::BytePart(data, actual_bytes, actual_bytes),
+      fidl::HandlePart(handles, actual_handles, actual_handles));
+  stub.Dispatch_(std::move(message), fidl::internal::PendingResponse());
+
+  EXPECT_EQ(testola_impl.received_msg(), "Ça c'est a 你好 from deep in JS");
+}
+
+TEST_F(FidlGenJsTest, RawReceiveFidlMessageWithMultipleArgs) {
+  v8::Isolate* isolate = instance_->isolate();
+  BindingsSetupHelper helper(isolate);
+
+  // Send the data from the JS side into the channel.
+  std::string source = R"(
+    var proxy = new TestolaProxy();
+    proxy.$bind(testHandle);
+    proxy.VariousArgs(Blorp.GAMMA, 'zippy zap', [ 999, 987, 123456 ]);
+  )";
+  helper.runner().Run(source, "test.js");
+
+  // Read it out, decode, and confirm it was dispatched.
+  TestolaImpl testola_impl;
+  fidljstest::Testola_Stub stub(&testola_impl);
+  uint8_t data[1024];
+  zx_handle_t handles[1];
+  uint32_t actual_bytes, actual_handles;
+  ASSERT_EQ(helper.server().read(0, data, base::size(data), &actual_bytes,
+                                 handles, base::size(handles), &actual_handles),
+            ZX_OK);
+  EXPECT_EQ(actual_handles, 0u);
+
+  fidl::Message message(
+      fidl::BytePart(data, actual_bytes, actual_bytes),
+      fidl::HandlePart(handles, actual_handles, actual_handles));
+  stub.Dispatch_(std::move(message), fidl::internal::PendingResponse());
+
+  EXPECT_EQ(testola_impl.various_blorp(), fidljstest::Blorp::GAMMA);
+  EXPECT_EQ(testola_impl.various_msg(), "zippy zap");
+  ASSERT_EQ(testola_impl.various_stuff().size(), 3u);
+  EXPECT_EQ(testola_impl.various_stuff()[0], 999u);
+  EXPECT_EQ(testola_impl.various_stuff()[1], 987u);
+  EXPECT_EQ(testola_impl.various_stuff()[2], 123456u);
+}
+
+int main(int argc, char** argv) {
+  base::TestSuite test_suite(argc, argv);
+
+  return base::LaunchUnitTests(
+      argc, argv,
+      base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/tools/fuchsia/fidlgen_js/test/simple.fidl b/tools/fuchsia/fidlgen_js/test/simple.fidl
new file mode 100644
index 0000000..95d5b84
--- /dev/null
+++ b/tools/fuchsia/fidlgen_js/test/simple.fidl
@@ -0,0 +1,21 @@
+// 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.
+
+library fidljstest;
+
+enum Blorp : int8 {
+  ALPHA = 1;
+  BETA = 2;
+  GAMMA = 0x48;
+};
+
+interface Testola {
+  1: DoSomething();
+
+  2: PrintInt(int32 num);
+
+  3: PrintMsg(string msg);
+
+  4: VariousArgs(Blorp blorp, string:32 msg, vector<uint32> stuff);
+};
diff --git a/tools/fuchsia/fidlgen_js/third_party/__init__.py b/tools/fuchsia/fidlgen_js/third_party/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/fuchsia/fidlgen_js/third_party/__init__.py
diff --git a/tools/fuchsia/fidlgen_js/third_party/enum34/LICENSE b/tools/fuchsia/fidlgen_js/third_party/enum34/LICENSE
new file mode 100644
index 0000000..9003b885
--- /dev/null
+++ b/tools/fuchsia/fidlgen_js/third_party/enum34/LICENSE
@@ -0,0 +1,32 @@
+Copyright (c) 2013, Ethan Furman.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+    Redistributions of source code must retain the above
+    copyright notice, this list of conditions and the
+    following disclaimer.
+
+    Redistributions in binary form must reproduce the above
+    copyright notice, this list of conditions and the following
+    disclaimer in the documentation and/or other materials
+    provided with the distribution.
+
+    Neither the name Ethan Furman nor the names of any
+    contributors may be used to endorse or promote products
+    derived from this software without specific prior written
+    permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/tools/fuchsia/fidlgen_js/third_party/enum34/README.chromium b/tools/fuchsia/fidlgen_js/third_party/enum34/README.chromium
new file mode 100644
index 0000000..4d0ef07
--- /dev/null
+++ b/tools/fuchsia/fidlgen_js/third_party/enum34/README.chromium
@@ -0,0 +1,15 @@
+Name: enum34
+Short Name: enum34
+URL: https://bitbucket.org/stoneleaf/enum34
+License: BSD
+License File: LICENSE
+Revision: f24487b
+Security Critical: no
+
+
+Description:
+
+'Enum' backported from Python 3.4 to earlier Python versions. Only LICENSE and
+__init__.py are taken, other packaging files, documentation, etc. removed.
+
+Only used at build time.
diff --git a/tools/fuchsia/fidlgen_js/third_party/enum34/__init__.py b/tools/fuchsia/fidlgen_js/third_party/enum34/__init__.py
new file mode 100644
index 0000000..d6ffb3a
--- /dev/null
+++ b/tools/fuchsia/fidlgen_js/third_party/enum34/__init__.py
@@ -0,0 +1,837 @@
+"""Python Enumerations"""
+
+import sys as _sys
+
+__all__ = ['Enum', 'IntEnum', 'unique']
+
+version = 1, 1, 6
+
+pyver = float('%s.%s' % _sys.version_info[:2])
+
+try:
+    any
+except NameError:
+    def any(iterable):
+        for element in iterable:
+            if element:
+                return True
+        return False
+
+try:
+    from collections import OrderedDict
+except ImportError:
+    OrderedDict = None
+
+try:
+    basestring
+except NameError:
+    # In Python 2 basestring is the ancestor of both str and unicode
+    # in Python 3 it's just str, but was missing in 3.1
+    basestring = str
+
+try:
+    unicode
+except NameError:
+    # In Python 3 unicode no longer exists (it's just str)
+    unicode = str
+
+class _RouteClassAttributeToGetattr(object):
+    """Route attribute access on a class to __getattr__.
+
+    This is a descriptor, used to define attributes that act differently when
+    accessed through an instance and through a class.  Instance access remains
+    normal, but access to an attribute through a class will be routed to the
+    class's __getattr__ method; this is done by raising AttributeError.
+
+    """
+    def __init__(self, fget=None):
+        self.fget = fget
+
+    def __get__(self, instance, ownerclass=None):
+        if instance is None:
+            raise AttributeError()
+        return self.fget(instance)
+
+    def __set__(self, instance, value):
+        raise AttributeError("can't set attribute")
+
+    def __delete__(self, instance):
+        raise AttributeError("can't delete attribute")
+
+
+def _is_descriptor(obj):
+    """Returns True if obj is a descriptor, False otherwise."""
+    return (
+            hasattr(obj, '__get__') or
+            hasattr(obj, '__set__') or
+            hasattr(obj, '__delete__'))
+
+
+def _is_dunder(name):
+    """Returns True if a __dunder__ name, False otherwise."""
+    return (name[:2] == name[-2:] == '__' and
+            name[2:3] != '_' and
+            name[-3:-2] != '_' and
+            len(name) > 4)
+
+
+def _is_sunder(name):
+    """Returns True if a _sunder_ name, False otherwise."""
+    return (name[0] == name[-1] == '_' and
+            name[1:2] != '_' and
+            name[-2:-1] != '_' and
+            len(name) > 2)
+
+
+def _make_class_unpicklable(cls):
+    """Make the given class un-picklable."""
+    def _break_on_call_reduce(self, protocol=None):
+        raise TypeError('%r cannot be pickled' % self)
+    cls.__reduce_ex__ = _break_on_call_reduce
+    cls.__module__ = '<unknown>'
+
+
+class _EnumDict(dict):
+    """Track enum member order and ensure member names are not reused.
+
+    EnumMeta will use the names found in self._member_names as the
+    enumeration member names.
+
+    """
+    def __init__(self):
+        super(_EnumDict, self).__init__()
+        self._member_names = []
+
+    def __setitem__(self, key, value):
+        """Changes anything not dundered or not a descriptor.
+
+        If a descriptor is added with the same name as an enum member, the name
+        is removed from _member_names (this may leave a hole in the numerical
+        sequence of values).
+
+        If an enum member name is used twice, an error is raised; duplicate
+        values are not checked for.
+
+        Single underscore (sunder) names are reserved.
+
+        Note:   in 3.x __order__ is simply discarded as a not necessary piece
+                leftover from 2.x
+
+        """
+        if pyver >= 3.0 and key in ('_order_', '__order__'):
+            return
+        elif key == '__order__':
+            key = '_order_'
+        if _is_sunder(key):
+            if key != '_order_':
+                raise ValueError('_names_ are reserved for future Enum use')
+        elif _is_dunder(key):
+            pass
+        elif key in self._member_names:
+            # descriptor overwriting an enum?
+            raise TypeError('Attempted to reuse key: %r' % key)
+        elif not _is_descriptor(value):
+            if key in self:
+                # enum overwriting a descriptor?
+                raise TypeError('Key already defined as: %r' % self[key])
+            self._member_names.append(key)
+        super(_EnumDict, self).__setitem__(key, value)
+
+
+# Dummy value for Enum as EnumMeta explicity checks for it, but of course until
+# EnumMeta finishes running the first time the Enum class doesn't exist.  This
+# is also why there are checks in EnumMeta like `if Enum is not None`
+Enum = None
+
+
+class EnumMeta(type):
+    """Metaclass for Enum"""
+    @classmethod
+    def __prepare__(metacls, cls, bases):
+        return _EnumDict()
+
+    def __new__(metacls, cls, bases, classdict):
+        # an Enum class is final once enumeration items have been defined; it
+        # cannot be mixed with other types (int, float, etc.) if it has an
+        # inherited __new__ unless a new __new__ is defined (or the resulting
+        # class will fail).
+        if type(classdict) is dict:
+            original_dict = classdict
+            classdict = _EnumDict()
+            for k, v in original_dict.items():
+                classdict[k] = v
+
+        member_type, first_enum = metacls._get_mixins_(bases)
+        __new__, save_new, use_args = metacls._find_new_(classdict, member_type,
+                                                        first_enum)
+        # save enum items into separate mapping so they don't get baked into
+        # the new class
+        members = dict((k, classdict[k]) for k in classdict._member_names)
+        for name in classdict._member_names:
+            del classdict[name]
+
+        # py2 support for definition order
+        _order_ = classdict.get('_order_')
+        if _order_ is None:
+            if pyver < 3.0:
+                try:
+                    _order_ = [name for (name, value) in sorted(members.items(), key=lambda item: item[1])]
+                except TypeError:
+                    _order_ = [name for name in sorted(members.keys())]
+            else:
+                _order_ = classdict._member_names
+        else:
+            del classdict['_order_']
+            if pyver < 3.0:
+                _order_ = _order_.replace(',', ' ').split()
+                aliases = [name for name in members if name not in _order_]
+                _order_ += aliases
+
+        # check for illegal enum names (any others?)
+        invalid_names = set(members) & set(['mro'])
+        if invalid_names:
+            raise ValueError('Invalid enum member name(s): %s' % (
+                ', '.join(invalid_names), ))
+
+        # save attributes from super classes so we know if we can take
+        # the shortcut of storing members in the class dict
+        base_attributes = set([a for b in bases for a in b.__dict__])
+        # create our new Enum type
+        enum_class = super(EnumMeta, metacls).__new__(metacls, cls, bases, classdict)
+        enum_class._member_names_ = []               # names in random order
+        if OrderedDict is not None:
+            enum_class._member_map_ = OrderedDict()
+        else:
+            enum_class._member_map_ = {}             # name->value map
+        enum_class._member_type_ = member_type
+
+        # Reverse value->name map for hashable values.
+        enum_class._value2member_map_ = {}
+
+        # instantiate them, checking for duplicates as we go
+        # we instantiate first instead of checking for duplicates first in case
+        # a custom __new__ is doing something funky with the values -- such as
+        # auto-numbering ;)
+        if __new__ is None:
+            __new__ = enum_class.__new__
+        for member_name in _order_:
+            value = members[member_name]
+            if not isinstance(value, tuple):
+                args = (value, )
+            else:
+                args = value
+            if member_type is tuple:   # special case for tuple enums
+                args = (args, )     # wrap it one more time
+            if not use_args or not args:
+                enum_member = __new__(enum_class)
+                if not hasattr(enum_member, '_value_'):
+                    enum_member._value_ = value
+            else:
+                enum_member = __new__(enum_class, *args)
+                if not hasattr(enum_member, '_value_'):
+                    enum_member._value_ = member_type(*args)
+            value = enum_member._value_
+            enum_member._name_ = member_name
+            enum_member.__objclass__ = enum_class
+            enum_member.__init__(*args)
+            # If another member with the same value was already defined, the
+            # new member becomes an alias to the existing one.
+            for name, canonical_member in enum_class._member_map_.items():
+                if canonical_member.value == enum_member._value_:
+                    enum_member = canonical_member
+                    break
+            else:
+                # Aliases don't appear in member names (only in __members__).
+                enum_class._member_names_.append(member_name)
+            # performance boost for any member that would not shadow
+            # a DynamicClassAttribute (aka _RouteClassAttributeToGetattr)
+            if member_name not in base_attributes:
+                setattr(enum_class, member_name, enum_member)
+            # now add to _member_map_
+            enum_class._member_map_[member_name] = enum_member
+            try:
+                # This may fail if value is not hashable. We can't add the value
+                # to the map, and by-value lookups for this value will be
+                # linear.
+                enum_class._value2member_map_[value] = enum_member
+            except TypeError:
+                pass
+
+
+        # If a custom type is mixed into the Enum, and it does not know how
+        # to pickle itself, pickle.dumps will succeed but pickle.loads will
+        # fail.  Rather than have the error show up later and possibly far
+        # from the source, sabotage the pickle protocol for this class so
+        # that pickle.dumps also fails.
+        #
+        # However, if the new class implements its own __reduce_ex__, do not
+        # sabotage -- it's on them to make sure it works correctly.  We use
+        # __reduce_ex__ instead of any of the others as it is preferred by
+        # pickle over __reduce__, and it handles all pickle protocols.
+        unpicklable = False
+        if '__reduce_ex__' not in classdict:
+            if member_type is not object:
+                methods = ('__getnewargs_ex__', '__getnewargs__',
+                        '__reduce_ex__', '__reduce__')
+                if not any(m in member_type.__dict__ for m in methods):
+                    _make_class_unpicklable(enum_class)
+                    unpicklable = True
+
+
+        # double check that repr and friends are not the mixin's or various
+        # things break (such as pickle)
+        for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
+            class_method = getattr(enum_class, name)
+            obj_method = getattr(member_type, name, None)
+            enum_method = getattr(first_enum, name, None)
+            if name not in classdict and class_method is not enum_method:
+                if name == '__reduce_ex__' and unpicklable:
+                    continue
+                setattr(enum_class, name, enum_method)
+
+        # method resolution and int's are not playing nice
+        # Python's less than 2.6 use __cmp__
+
+        if pyver < 2.6:
+
+            if issubclass(enum_class, int):
+                setattr(enum_class, '__cmp__', getattr(int, '__cmp__'))
+
+        elif pyver < 3.0:
+
+            if issubclass(enum_class, int):
+                for method in (
+                        '__le__',
+                        '__lt__',
+                        '__gt__',
+                        '__ge__',
+                        '__eq__',
+                        '__ne__',
+                        '__hash__',
+                        ):
+                    setattr(enum_class, method, getattr(int, method))
+
+        # replace any other __new__ with our own (as long as Enum is not None,
+        # anyway) -- again, this is to support pickle
+        if Enum is not None:
+            # if the user defined their own __new__, save it before it gets
+            # clobbered in case they subclass later
+            if save_new:
+                setattr(enum_class, '__member_new__', enum_class.__dict__['__new__'])
+            setattr(enum_class, '__new__', Enum.__dict__['__new__'])
+        return enum_class
+
+    def __bool__(cls):
+        """
+        classes/types should always be True.
+        """
+        return True
+
+    def __call__(cls, value, names=None, module=None, type=None, start=1):
+        """Either returns an existing member, or creates a new enum class.
+
+        This method is used both when an enum class is given a value to match
+        to an enumeration member (i.e. Color(3)) and for the functional API
+        (i.e. Color = Enum('Color', names='red green blue')).
+
+        When used for the functional API: `module`, if set, will be stored in
+        the new class' __module__ attribute; `type`, if set, will be mixed in
+        as the first base class.
+
+        Note: if `module` is not set this routine will attempt to discover the
+        calling module by walking the frame stack; if this is unsuccessful
+        the resulting class will not be pickleable.
+
+        """
+        if names is None:  # simple value lookup
+            return cls.__new__(cls, value)
+        # otherwise, functional API: we're creating a new Enum type
+        return cls._create_(value, names, module=module, type=type, start=start)
+
+    def __contains__(cls, member):
+        return isinstance(member, cls) and member.name in cls._member_map_
+
+    def __delattr__(cls, attr):
+        # nicer error message when someone tries to delete an attribute
+        # (see issue19025).
+        if attr in cls._member_map_:
+            raise AttributeError(
+                    "%s: cannot delete Enum member." % cls.__name__)
+        super(EnumMeta, cls).__delattr__(attr)
+
+    def __dir__(self):
+        return (['__class__', '__doc__', '__members__', '__module__'] +
+                self._member_names_)
+
+    @property
+    def __members__(cls):
+        """Returns a mapping of member name->value.
+
+        This mapping lists all enum members, including aliases. Note that this
+        is a copy of the internal mapping.
+
+        """
+        return cls._member_map_.copy()
+
+    def __getattr__(cls, name):
+        """Return the enum member matching `name`
+
+        We use __getattr__ instead of descriptors or inserting into the enum
+        class' __dict__ in order to support `name` and `value` being both
+        properties for enum members (which live in the class' __dict__) and
+        enum members themselves.
+
+        """
+        if _is_dunder(name):
+            raise AttributeError(name)
+        try:
+            return cls._member_map_[name]
+        except KeyError:
+            raise AttributeError(name)
+
+    def __getitem__(cls, name):
+        return cls._member_map_[name]
+
+    def __iter__(cls):
+        return (cls._member_map_[name] for name in cls._member_names_)
+
+    def __reversed__(cls):
+        return (cls._member_map_[name] for name in reversed(cls._member_names_))
+
+    def __len__(cls):
+        return len(cls._member_names_)
+
+    __nonzero__ = __bool__
+
+    def __repr__(cls):
+        return "<enum %r>" % cls.__name__
+
+    def __setattr__(cls, name, value):
+        """Block attempts to reassign Enum members.
+
+        A simple assignment to the class namespace only changes one of the
+        several possible ways to get an Enum member from the Enum class,
+        resulting in an inconsistent Enumeration.
+
+        """
+        member_map = cls.__dict__.get('_member_map_', {})
+        if name in member_map:
+            raise AttributeError('Cannot reassign members.')
+        super(EnumMeta, cls).__setattr__(name, value)
+
+    def _create_(cls, class_name, names=None, module=None, type=None, start=1):
+        """Convenience method to create a new Enum class.
+
+        `names` can be:
+
+        * A string containing member names, separated either with spaces or
+          commas.  Values are auto-numbered from 1.
+        * An iterable of member names.  Values are auto-numbered from 1.
+        * An iterable of (member name, value) pairs.
+        * A mapping of member name -> value.
+
+        """
+        if pyver < 3.0:
+            # if class_name is unicode, attempt a conversion to ASCII
+            if isinstance(class_name, unicode):
+                try:
+                    class_name = class_name.encode('ascii')
+                except UnicodeEncodeError:
+                    raise TypeError('%r is not representable in ASCII' % class_name)
+        metacls = cls.__class__
+        if type is None:
+            bases = (cls, )
+        else:
+            bases = (type, cls)
+        classdict = metacls.__prepare__(class_name, bases)
+        _order_ = []
+
+        # special processing needed for names?
+        if isinstance(names, basestring):
+            names = names.replace(',', ' ').split()
+        if isinstance(names, (tuple, list)) and isinstance(names[0], basestring):
+            names = [(e, i+start) for (i, e) in enumerate(names)]
+
+        # Here, names is either an iterable of (name, value) or a mapping.
+        item = None  # in case names is empty
+        for item in names:
+            if isinstance(item, basestring):
+                member_name, member_value = item, names[item]
+            else:
+                member_name, member_value = item
+            classdict[member_name] = member_value
+            _order_.append(member_name)
+        # only set _order_ in classdict if name/value was not from a mapping
+        if not isinstance(item, basestring):
+            classdict['_order_'] = ' '.join(_order_)
+        enum_class = metacls.__new__(metacls, class_name, bases, classdict)
+
+        # TODO: replace the frame hack if a blessed way to know the calling
+        # module is ever developed
+        if module is None:
+            try:
+                module = _sys._getframe(2).f_globals['__name__']
+            except (AttributeError, ValueError):
+                pass
+        if module is None:
+            _make_class_unpicklable(enum_class)
+        else:
+            enum_class.__module__ = module
+
+        return enum_class
+
+    @staticmethod
+    def _get_mixins_(bases):
+        """Returns the type for creating enum members, and the first inherited
+        enum class.
+
+        bases: the tuple of bases that was given to __new__
+
+        """
+        if not bases or Enum is None:
+            return object, Enum
+
+
+        # double check that we are not subclassing a class with existing
+        # enumeration members; while we're at it, see if any other data
+        # type has been mixed in so we can use the correct __new__
+        member_type = first_enum = None
+        for base in bases:
+            if  (base is not Enum and
+                    issubclass(base, Enum) and
+                    base._member_names_):
+                raise TypeError("Cannot extend enumerations")
+        # base is now the last base in bases
+        if not issubclass(base, Enum):
+            raise TypeError("new enumerations must be created as "
+                    "`ClassName([mixin_type,] enum_type)`")
+
+        # get correct mix-in type (either mix-in type of Enum subclass, or
+        # first base if last base is Enum)
+        if not issubclass(bases[0], Enum):
+            member_type = bases[0]     # first data type
+            first_enum = bases[-1]  # enum type
+        else:
+            for base in bases[0].__mro__:
+                # most common: (IntEnum, int, Enum, object)
+                # possible:    (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
+                #               <class 'int'>, <Enum 'Enum'>,
+                #               <class 'object'>)
+                if issubclass(base, Enum):
+                    if first_enum is None:
+                        first_enum = base
+                else:
+                    if member_type is None:
+                        member_type = base
+
+        return member_type, first_enum
+
+    if pyver < 3.0:
+        @staticmethod
+        def _find_new_(classdict, member_type, first_enum):
+            """Returns the __new__ to be used for creating the enum members.
+
+            classdict: the class dictionary given to __new__
+            member_type: the data type whose __new__ will be used by default
+            first_enum: enumeration to check for an overriding __new__
+
+            """
+            # now find the correct __new__, checking to see of one was defined
+            # by the user; also check earlier enum classes in case a __new__ was
+            # saved as __member_new__
+            __new__ = classdict.get('__new__', None)
+            if __new__:
+                return None, True, True      # __new__, save_new, use_args
+
+            N__new__ = getattr(None, '__new__')
+            O__new__ = getattr(object, '__new__')
+            if Enum is None:
+                E__new__ = N__new__
+            else:
+                E__new__ = Enum.__dict__['__new__']
+            # check all possibles for __member_new__ before falling back to
+            # __new__
+            for method in ('__member_new__', '__new__'):
+                for possible in (member_type, first_enum):
+                    try:
+                        target = possible.__dict__[method]
+                    except (AttributeError, KeyError):
+                        target = getattr(possible, method, None)
+                    if target not in [
+                            None,
+                            N__new__,
+                            O__new__,
+                            E__new__,
+                            ]:
+                        if method == '__member_new__':
+                            classdict['__new__'] = target
+                            return None, False, True
+                        if isinstance(target, staticmethod):
+                            target = target.__get__(member_type)
+                        __new__ = target
+                        break
+                if __new__ is not None:
+                    break
+            else:
+                __new__ = object.__new__
+
+            # if a non-object.__new__ is used then whatever value/tuple was
+            # assigned to the enum member name will be passed to __new__ and to the
+            # new enum member's __init__
+            if __new__ is object.__new__:
+                use_args = False
+            else:
+                use_args = True
+
+            return __new__, False, use_args
+    else:
+        @staticmethod
+        def _find_new_(classdict, member_type, first_enum):
+            """Returns the __new__ to be used for creating the enum members.
+
+            classdict: the class dictionary given to __new__
+            member_type: the data type whose __new__ will be used by default
+            first_enum: enumeration to check for an overriding __new__
+
+            """
+            # now find the correct __new__, checking to see of one was defined
+            # by the user; also check earlier enum classes in case a __new__ was
+            # saved as __member_new__
+            __new__ = classdict.get('__new__', None)
+
+            # should __new__ be saved as __member_new__ later?
+            save_new = __new__ is not None
+
+            if __new__ is None:
+                # check all possibles for __member_new__ before falling back to
+                # __new__
+                for method in ('__member_new__', '__new__'):
+                    for possible in (member_type, first_enum):
+                        target = getattr(possible, method, None)
+                        if target not in (
+                                None,
+                                None.__new__,
+                                object.__new__,
+                                Enum.__new__,
+                                ):
+                            __new__ = target
+                            break
+                    if __new__ is not None:
+                        break
+                else:
+                    __new__ = object.__new__
+
+            # if a non-object.__new__ is used then whatever value/tuple was
+            # assigned to the enum member name will be passed to __new__ and to the
+            # new enum member's __init__
+            if __new__ is object.__new__:
+                use_args = False
+            else:
+                use_args = True
+
+            return __new__, save_new, use_args
+
+
+########################################################
+# In order to support Python 2 and 3 with a single
+# codebase we have to create the Enum methods separately
+# and then use the `type(name, bases, dict)` method to
+# create the class.
+########################################################
+temp_enum_dict = {}
+temp_enum_dict['__doc__'] = "Generic enumeration.\n\n    Derive from this class to define new enumerations.\n\n"
+
+def __new__(cls, value):
+    # all enum instances are actually created during class construction
+    # without calling this method; this method is called by the metaclass'
+    # __call__ (i.e. Color(3) ), and by pickle
+    if type(value) is cls:
+        # For lookups like Color(Color.red)
+        value = value.value
+        #return value
+    # by-value search for a matching enum member
+    # see if it's in the reverse mapping (for hashable values)
+    try:
+        if value in cls._value2member_map_:
+            return cls._value2member_map_[value]
+    except TypeError:
+        # not there, now do long search -- O(n) behavior
+        for member in cls._member_map_.values():
+            if member.value == value:
+                return member
+    raise ValueError("%s is not a valid %s" % (value, cls.__name__))
+temp_enum_dict['__new__'] = __new__
+del __new__
+
+def __repr__(self):
+    return "<%s.%s: %r>" % (
+            self.__class__.__name__, self._name_, self._value_)
+temp_enum_dict['__repr__'] = __repr__
+del __repr__
+
+def __str__(self):
+    return "%s.%s" % (self.__class__.__name__, self._name_)
+temp_enum_dict['__str__'] = __str__
+del __str__
+
+if pyver >= 3.0:
+    def __dir__(self):
+        added_behavior = [
+                m
+                for cls in self.__class__.mro()
+                for m in cls.__dict__
+                if m[0] != '_' and m not in self._member_map_
+                ]
+        return (['__class__', '__doc__', '__module__', ] + added_behavior)
+    temp_enum_dict['__dir__'] = __dir__
+    del __dir__
+
+def __format__(self, format_spec):
+    # mixed-in Enums should use the mixed-in type's __format__, otherwise
+    # we can get strange results with the Enum name showing up instead of
+    # the value
+
+    # pure Enum branch
+    if self._member_type_ is object:
+        cls = str
+        val = str(self)
+    # mix-in branch
+    else:
+        cls = self._member_type_
+        val = self.value
+    return cls.__format__(val, format_spec)
+temp_enum_dict['__format__'] = __format__
+del __format__
+
+
+####################################
+# Python's less than 2.6 use __cmp__
+
+if pyver < 2.6:
+
+    def __cmp__(self, other):
+        if type(other) is self.__class__:
+            if self is other:
+                return 0
+            return -1
+        return NotImplemented
+        raise TypeError("unorderable types: %s() and %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__cmp__'] = __cmp__
+    del __cmp__
+
+else:
+
+    def __le__(self, other):
+        raise TypeError("unorderable types: %s() <= %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__le__'] = __le__
+    del __le__
+
+    def __lt__(self, other):
+        raise TypeError("unorderable types: %s() < %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__lt__'] = __lt__
+    del __lt__
+
+    def __ge__(self, other):
+        raise TypeError("unorderable types: %s() >= %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__ge__'] = __ge__
+    del __ge__
+
+    def __gt__(self, other):
+        raise TypeError("unorderable types: %s() > %s()" % (self.__class__.__name__, other.__class__.__name__))
+    temp_enum_dict['__gt__'] = __gt__
+    del __gt__
+
+
+def __eq__(self, other):
+    if type(other) is self.__class__:
+        return self is other
+    return NotImplemented
+temp_enum_dict['__eq__'] = __eq__
+del __eq__
+
+def __ne__(self, other):
+    if type(other) is self.__class__:
+        return self is not other
+    return NotImplemented
+temp_enum_dict['__ne__'] = __ne__
+del __ne__
+
+def __hash__(self):
+    return hash(self._name_)
+temp_enum_dict['__hash__'] = __hash__
+del __hash__
+
+def __reduce_ex__(self, proto):
+    return self.__class__, (self._value_, )
+temp_enum_dict['__reduce_ex__'] = __reduce_ex__
+del __reduce_ex__
+
+# _RouteClassAttributeToGetattr is used to provide access to the `name`
+# and `value` properties of enum members while keeping some measure of
+# protection from modification, while still allowing for an enumeration
+# to have members named `name` and `value`.  This works because enumeration
+# members are not set directly on the enum class -- __getattr__ is
+# used to look them up.
+
+@_RouteClassAttributeToGetattr
+def name(self):
+    return self._name_
+temp_enum_dict['name'] = name
+del name
+
+@_RouteClassAttributeToGetattr
+def value(self):
+    return self._value_
+temp_enum_dict['value'] = value
+del value
+
+@classmethod
+def _convert(cls, name, module, filter, source=None):
+    """
+    Create a new Enum subclass that replaces a collection of global constants
+    """
+    # convert all constants from source (or module) that pass filter() to
+    # a new Enum called name, and export the enum and its members back to
+    # module;
+    # also, replace the __reduce_ex__ method so unpickling works in
+    # previous Python versions
+    module_globals = vars(_sys.modules[module])
+    if source:
+        source = vars(source)
+    else:
+        source = module_globals
+    members = dict((name, value) for name, value in source.items() if filter(name))
+    cls = cls(name, members, module=module)
+    cls.__reduce_ex__ = _reduce_ex_by_name
+    module_globals.update(cls.__members__)
+    module_globals[name] = cls
+    return cls
+temp_enum_dict['_convert'] = _convert
+del _convert
+
+Enum = EnumMeta('Enum', (object, ), temp_enum_dict)
+del temp_enum_dict
+
+# Enum has now been created
+###########################
+
+class IntEnum(int, Enum):
+    """Enum where members are also (and must be) ints"""
+
+def _reduce_ex_by_name(self, proto):
+    return self.name
+
+def unique(enumeration):
+    """Class decorator that ensures only unique members exist in an enumeration."""
+    duplicates = []
+    for name, member in enumeration.__members__.items():
+        if name != member.name:
+            duplicates.append((name, member.name))
+    if duplicates:
+        duplicate_names = ', '.join(
+                ["%s -> %s" % (alias, name) for (alias, name) in duplicates]
+                )
+        raise ValueError('duplicate names found in %r: %s' %
+                (enumeration, duplicate_names)
+                )
+    return enumeration
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 4d159e3..c37b5c0 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -8885,6 +8885,8 @@
 <enum name="CrosDictationToggleDictationMethod">
   <int value="0" label="Search+D"/>
   <int value="1" label="Click onscreen button"/>
+  <int value="2" label="Select the button in the SwitchAccess context menu"/>
+  <int value="3" label="ChromeVox gesture"/>
 </enum>
 
 <enum name="CrosDisksArchiveType">
@@ -9845,6 +9847,9 @@
 </enum>
 
 <enum name="DataReductionProxyProtocolNotAcceptingTransformReason">
+  <obsolete>
+    Deprecated 10/2018.
+  </obsolete>
   <int value="0"
       label="Not accepting proxy transforms, transforms disabled on client"/>
   <int value="1"
@@ -16910,6 +16915,7 @@
   <int value="1281" label="AUTOTESTPRIVATE_BOOTSTRAPMACHINELEARNINGSERVICE"/>
   <int value="1282" label="AUTOTESTPRIVATE_RUNCROSTINIUNINSTALLER"/>
   <int value="1283" label="AUTOTESTPRIVATE_TAKESCREENSHOT"/>
+  <int value="1284" label="ACCESSIBILITY_PRIVATE_TOGGLEDICTATION"/>
 </enum>
 
 <enum name="ExtensionIconState">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 1021101..22ff1f7 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -17527,6 +17527,9 @@
 
 <histogram name="DataReductionProxy.Protocol.NotAcceptingTransform"
     enum="DataReductionProxyProtocolNotAcceptingTransformReason">
+  <obsolete>
+    Obsolete as of 10/2018.
+  </obsolete>
   <owner>dougarnett@chromium.org</owner>
   <summary>
     Records the reason that a page request is not accepting proxy server
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index b56731a..21dc50d 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -2937,6 +2937,12 @@
       meaningful input with longest queuing delay per navigation. In ms.
     </summary>
   </metric>
+  <metric name="Navigation.PageEndReason">
+    <summary>
+      The |page_load_metrics::PageEndReason| for the main frame navigation of
+      this page load.
+    </summary>
+  </metric>
   <metric name="Navigation.PageTransition">
     <summary>
       The |ui::PageTransition| for the main frame navigation of this page load.
diff --git a/tools/perf/contrib/blink_layoutng_perf/loading_layout_ng.py b/tools/perf/contrib/blink_layoutng_perf/loading_layout_ng.py
new file mode 100644
index 0000000..a85da250
--- /dev/null
+++ b/tools/perf/contrib/blink_layoutng_perf/loading_layout_ng.py
@@ -0,0 +1,34 @@
+# 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.
+from benchmarks import loading
+from telemetry import benchmark
+
+# pylint: disable=protected-access
+@benchmark.Info(emails=['cbiesinger@chromium.org'],
+                documentation_url='https://bit.ly/loading-benchmarks')
+class LoadingDesktopLayoutNg(loading.LoadingDesktop):
+  """A benchmark that runs loading.desktop with the layoutng flag."""
+
+  def SetExtraBrowserOptions(self, options):
+    super(LoadingDesktopLayoutNg, self).SetExtraBrowserOptions(options)
+    options.AppendExtraBrowserArgs('--enable-blink-features=LayoutNG')
+
+  @classmethod
+  def Name(cls):
+    return 'loading.desktop_layout_ng'
+
+
+# pylint: disable=protected-access
+@benchmark.Info(emails=['cbiesinger@chromium.org'],
+                documentation_url='https://bit.ly/loading-benchmarks')
+class LoadingMobileLayoutNg(loading.LoadingDesktop):
+  """A benchmark that runs loading.mobile with the layoutng flag."""
+
+  def SetExtraBrowserOptions(self, options):
+    super(LoadingMobileLayoutNg, self).SetExtraBrowserOptions(options)
+    options.AppendExtraBrowserArgs('--enable-blink-features=LayoutNG')
+
+  @classmethod
+  def Name(cls):
+    return 'loading.mobile_layout_ng'
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index 737d51c..89ec212 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -26,6 +26,9 @@
 crbug.com/853738 [ Android_Webview ] blink_perf.canvas/upload-video-to-sub-texture.html [ Skip ]
 crbug.com/853738 [ Android_Webview ] blink_perf.canvas/upload-video-to-texture.html [ Skip ]
 
+# Benchmark: blink_perf.css
+crbug.com/891878 [ Nexus5X_Webview ] blink_perf.css/CustomPropertiesVarAlias.html [ Skip ]
+
 # Benchmark: blink_perf.layout
 crbug.com/551950 [ Android_Svelte ] blink_perf.layout/* [ Skip ]
 crbug.com/832686 [ Nexus_5 ] blink_perf.layout/subtree-detaching.html [ Skip ]
diff --git a/ui/android/java/src/org/chromium/ui/DropdownPopupWindowImpl.java b/ui/android/java/src/org/chromium/ui/DropdownPopupWindowImpl.java
index 6e53e3f..2f0465f 100644
--- a/ui/android/java/src/org/chromium/ui/DropdownPopupWindowImpl.java
+++ b/ui/android/java/src/org/chromium/ui/DropdownPopupWindowImpl.java
@@ -9,7 +9,9 @@
 import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.MeasureSpec;
 import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.AdapterView;
 import android.widget.FrameLayout;
@@ -258,6 +260,16 @@
      */
     private int measureContentWidth() {
         assert mAdapter != null : "Set the adapter before showing the popup.";
-        return UiUtils.computeMaxWidthOfListAdapterItems(mAdapter);
+        int adapterWidth = UiUtils.computeMaxWidthOfListAdapterItems(mAdapter);
+        if (mFooterView.getChildCount() > 0) {
+            if (mFooterView.getLayoutParams() == null) {
+                mFooterView.setLayoutParams(new FrameLayout.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+            }
+            int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+            mFooterView.measure(measureSpec, measureSpec);
+            return Math.max(mFooterView.getMeasuredWidth(), adapterWidth);
+        }
+        return adapterWidth;
     }
 }
diff --git a/ui/aura/gestures/gesture_recognizer_unittest.cc b/ui/aura/gestures/gesture_recognizer_unittest.cc
index 86b08db..ee72e00 100644
--- a/ui/aura/gestures/gesture_recognizer_unittest.cc
+++ b/ui/aura/gestures/gesture_recognizer_unittest.cc
@@ -4705,8 +4705,7 @@
 
   // Transfer event sequence from previous window to the new window.
   aura::Env::GetInstance()->gesture_recognizer()->TransferEventsTo(
-      window_1.get(), window_2.get(),
-      ui::GestureRecognizer::ShouldCancelTouches::DontCancel);
+      window_1.get(), window_2.get(), ui::TransferTouchesBehavior::kDontCancel);
 
   delegate_1->Reset();
   delegate_1->ReceivedAck();
diff --git a/ui/events/BUILD.gn b/ui/events/BUILD.gn
index ee659a97..9a15d00 100644
--- a/ui/events/BUILD.gn
+++ b/ui/events/BUILD.gn
@@ -170,7 +170,9 @@
     "event_targeter.h",
     "event_utils.h",
     "events_export.h",
+    "gestures/gesture_recognizer.h",
     "gestures/gesture_recognizer_impl_mac.h",
+    "gestures/gesture_recognizer_observer.h",
     "gestures/gesture_types.h",
     "keyboard_hook.h",
     "null_event_targeter.h",
@@ -193,7 +195,9 @@
     "event_target.cc",
     "event_utils.cc",
     "events_stub.cc",
+    "gestures/gesture_recognizer.cc",
     "gestures/gesture_recognizer_impl_mac.cc",
+    "gestures/gesture_recognizer_observer.cc",
     "gestures/gesture_types.cc",
     "keyboard_hook_base.cc",
     "keyboard_hook_base.h",
@@ -270,7 +274,6 @@
   if (use_aura) {
     public += [
       "gestures/gesture_provider_aura.h",
-      "gestures/gesture_recognizer.h",
       "gestures/gesture_recognizer_impl.h",
       "gestures/motion_event_aura.h",
     ]
@@ -592,6 +595,7 @@
     if (use_aura) {
       sources += [
         "gestures/gesture_provider_aura_unittest.cc",
+        "gestures/gesture_recognizer_impl_unittest.cc",
         "gestures/motion_event_aura_unittest.cc",
       ]
     }
diff --git a/ui/events/gestures/gesture_recognizer.cc b/ui/events/gestures/gesture_recognizer.cc
new file mode 100644
index 0000000..533ab77
--- /dev/null
+++ b/ui/events/gestures/gesture_recognizer.cc
@@ -0,0 +1,22 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/events/gestures/gesture_recognizer.h"
+
+#include "ui/events/gestures/gesture_recognizer_observer.h"
+
+namespace ui {
+
+GestureRecognizer::GestureRecognizer() = default;
+GestureRecognizer::~GestureRecognizer() = default;
+
+void GestureRecognizer::AddObserver(GestureRecognizerObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void GestureRecognizer::RemoveObserver(GestureRecognizerObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+}  // namespace ui
diff --git a/ui/events/gestures/gesture_recognizer.h b/ui/events/gestures/gesture_recognizer.h
index f4da6a0..de966ee 100644
--- a/ui/events/gestures/gesture_recognizer.h
+++ b/ui/events/gestures/gesture_recognizer.h
@@ -10,19 +10,23 @@
 #include <memory>
 #include <vector>
 
+#include "base/observer_list.h"
 #include "ui/events/event_constants.h"
 #include "ui/events/events_export.h"
 #include "ui/events/gestures/gesture_types.h"
 #include "ui/gfx/geometry/point_f.h"
 
 namespace ui {
+class GestureRecognizerObserver;
+
 // A GestureRecognizer is an abstract base class for conversion of touch events
 // into gestures.
 class EVENTS_EXPORT GestureRecognizer {
  public:
   using Gestures = std::vector<std::unique_ptr<GestureEvent>>;
 
-  virtual ~GestureRecognizer() {}
+  GestureRecognizer();
+  virtual ~GestureRecognizer();
 
   // Invoked before event dispatch. If the event is invalid given the current
   // touch sequence, returns false.
@@ -57,15 +61,14 @@
   // |not_cancelled| == nullptr, cancels all touches.
   virtual void CancelActiveTouchesExcept(GestureConsumer* not_cancelled) = 0;
 
-  enum class ShouldCancelTouches { Cancel, DontCancel };
-
   // Transfer the gesture stream from the drag source (current_consumer) to the
-  // consumer used for dragging (new_consumer). If |should_cancel_touches| is
-  // Cancel, dispatches cancel events to |current_consumer| to ensure that its
-  // touch stream remains valid.
-  virtual void TransferEventsTo(GestureConsumer* current_consumer,
-                                GestureConsumer* new_consumer,
-                                ShouldCancelTouches should_cancel_touches) = 0;
+  // consumer used for dragging (new_consumer). If |transfer_touches_behavior|
+  // is kCancel, dispatches cancel events to |current_consumer| to ensure that
+  // its touch stream remains valid.
+  virtual void TransferEventsTo(
+      GestureConsumer* current_consumer,
+      GestureConsumer* new_consumer,
+      TransferTouchesBehavior transfer_touches_behavior) = 0;
 
   // If a gesture is underway for |consumer| |point| is set to the last touch
   // point and true is returned. If no touch events have been processed for
@@ -87,6 +90,19 @@
   // Since the GestureRecognizer does not own the |helper|, it is not deleted
   // and must be cleaned up appropriately by the caller.
   virtual void RemoveGestureEventHelper(GestureEventHelper* helper) = 0;
+
+  void AddObserver(GestureRecognizerObserver* observer);
+  void RemoveObserver(GestureRecognizerObserver* observer);
+
+ protected:
+  const base::ObserverList<GestureRecognizerObserver>& observers() {
+    return observers_;
+  }
+
+ private:
+  base::ObserverList<GestureRecognizerObserver> observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(GestureRecognizer);
 };
 
 }  // namespace ui
diff --git a/ui/events/gestures/gesture_recognizer_impl.cc b/ui/events/gestures/gesture_recognizer_impl.cc
index 1702be9..2b3925d4 100644
--- a/ui/events/gestures/gesture_recognizer_impl.cc
+++ b/ui/events/gestures/gesture_recognizer_impl.cc
@@ -19,6 +19,7 @@
 #include "ui/events/event_switches.h"
 #include "ui/events/event_utils.h"
 #include "ui/events/gesture_detection/gesture_configuration.h"
+#include "ui/events/gestures/gesture_recognizer_observer.h"
 #include "ui/events/gestures/gesture_types.h"
 
 namespace ui {
@@ -107,33 +108,21 @@
 
 void GestureRecognizerImpl::CancelActiveTouchesExcept(
     GestureConsumer* not_cancelled) {
-  // Do not iterate directly over |consumer_gesture_provider_| because canceling
-  // active touches may cause the consumer to be removed from
-  // |consumer_gesture_provider_|. See crbug.com/651258 for more info.
-  std::vector<GestureConsumer*> consumers(consumer_gesture_provider_.size());
-  for (const auto& entry : consumer_gesture_provider_) {
-    if (entry.first == not_cancelled)
-      continue;
-
-    consumers.push_back(entry.first);
-  }
-
-  for (auto* consumer : consumers)
-    CancelActiveTouches(consumer);
+  CancelActiveTouchesExceptImpl(not_cancelled, kNotifyObservers);
 }
 
 void GestureRecognizerImpl::TransferEventsTo(
     GestureConsumer* current_consumer,
     GestureConsumer* new_consumer,
-    ShouldCancelTouches should_cancel_touches) {
+    TransferTouchesBehavior transfer_touches_behavior) {
   // This method transfers the gesture stream from |current_consumer| to
-  // |new_consumer|. If |should_cancel_touches| is Cancel, it ensures that both
-  // consumers retain a touch event stream which is reasonably valid. In order
-  // to do this we
+  // |new_consumer|. If |transfer_touches_behavior| is kCancel, it ensures that
+  // both consumers retain a touch event stream which is reasonably valid. In
+  // order to do this we
   // - record what pointers are currently down on |current_consumer|
   // - cancel touches on consumers other than |current_consumer|
   // - move the gesture provider from |current_consumer| to |new_consumer|
-  // - if |should_cancel_touches|
+  // - if |transfer_touches_behavior| is kCancel
   //     - synchronize the state of the new gesture provider associated with
   //       current_consumer with with the touch state of the consumer itself via
   //       OnTouchEnter.
@@ -153,7 +142,7 @@
       touchids_targeted_at_current.push_back(touch_id_target.first);
   }
 
-  CancelActiveTouchesExcept(current_consumer);
+  CancelActiveTouchesExceptImpl(current_consumer, kDontNotifyObservers);
 
   std::vector<std::unique_ptr<TouchEvent>> cancelling_touches =
       GetEventPerPointForConsumer(current_consumer, ET_TOUCH_CANCELLED);
@@ -164,9 +153,7 @@
   // but has some pointers down which need cancelling. In order to ensure that
   // the GR sees a valid event stream, inform it of these pointers via
   // OnTouchEnter, and then synthesize a touch cancel per pointer.
-  if (should_cancel_touches ==
-          GestureRecognizer::ShouldCancelTouches::Cancel &&
-      helper) {
+  if (transfer_touches_behavior == TransferTouchesBehavior::kCancel && helper) {
     GestureProviderAura* gesture_provider =
         GetGestureProviderForConsumer(current_consumer);
 
@@ -179,6 +166,9 @@
 
   for (int touch_id : touchids_targeted_at_current)
     touch_id_target_[touch_id] = new_consumer;
+  for (GestureRecognizerObserver& observer : observers())
+    observer.OnEventsTransferred(current_consumer, new_consumer,
+                                 transfer_touches_behavior);
 }
 
 bool GestureRecognizerImpl::GetLastTouchPointForTarget(
@@ -224,21 +214,11 @@
 }
 
 bool GestureRecognizerImpl::CancelActiveTouches(GestureConsumer* consumer) {
-  GestureEventHelper* helper =
-      FindDispatchHelperForConsumer(consumer);
-
-  if (!helper)
-    return false;
-
-  std::vector<std::unique_ptr<TouchEvent>> cancelling_touches =
-      GetEventPerPointForConsumer(consumer, ET_TOUCH_CANCELLED);
-  for (const std::unique_ptr<TouchEvent>& cancelling_touch : cancelling_touches)
-    helper->DispatchSyntheticTouchEvent(cancelling_touch.get());
-  return cancelling_touches.size() > 0U;
+  return CancelActiveTouchesImpl(consumer, kNotifyObservers);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// GestureRecognizerImpl, private:
+// GestureRecognizerImpl, protected:
 
 GestureProviderAura* GestureRecognizerImpl::GetGestureProviderForConsumer(
     GestureConsumer* consumer) {
@@ -256,6 +236,22 @@
   return gesture_provider;
 }
 
+bool GestureRecognizerImpl::ProcessTouchEventPreDispatch(
+    TouchEvent* event,
+    GestureConsumer* consumer) {
+  SetupTargets(*event, consumer);
+
+  if (event->result() & ER_CONSUMED)
+    return false;
+
+  GestureProviderAura* gesture_provider =
+      GetGestureProviderForConsumer(consumer);
+  return gesture_provider->OnTouchEvent(event);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureRecognizerImpl, private:
+
 void GestureRecognizerImpl::SetupTargets(const TouchEvent& event,
                                          GestureConsumer* target) {
   event_to_gesture_provider_[event.unique_event_id()] =
@@ -279,19 +275,6 @@
   }
 }
 
-bool GestureRecognizerImpl::ProcessTouchEventPreDispatch(
-    TouchEvent* event,
-    GestureConsumer* consumer) {
-  SetupTargets(*event, consumer);
-
-  if (event->result() & ER_CONSUMED)
-    return false;
-
-  GestureProviderAura* gesture_provider =
-      GetGestureProviderForConsumer(consumer);
-  return gesture_provider->OnTouchEvent(event);
-}
-
 GestureRecognizer::Gestures GestureRecognizerImpl::AckTouchEvent(
     uint32_t unique_event_id,
     ui::EventResult result,
@@ -314,6 +297,48 @@
   return gesture_provider->GetAndResetPendingGestures();
 }
 
+void GestureRecognizerImpl::CancelActiveTouchesExceptImpl(
+    GestureConsumer* not_cancelled,
+    ShouldNotifyObservers should_notify) {
+  // Do not iterate directly over |consumer_gesture_provider_| because canceling
+  // active touches may cause the consumer to be removed from
+  // |consumer_gesture_provider_|. See https://crbug.com/651258 for more info.
+  std::vector<GestureConsumer*> consumers(consumer_gesture_provider_.size());
+  for (const auto& entry : consumer_gesture_provider_) {
+    if (entry.first == not_cancelled)
+      continue;
+
+    consumers.push_back(entry.first);
+  }
+
+  for (auto* consumer : consumers)
+    CancelActiveTouchesImpl(consumer, kDontNotifyObservers);
+
+  if (should_notify == kDontNotifyObservers)
+    return;
+  for (GestureRecognizerObserver& observer : observers())
+    observer.OnActiveTouchesCanceledExcept(not_cancelled);
+}
+
+bool GestureRecognizerImpl::CancelActiveTouchesImpl(
+    GestureConsumer* consumer,
+    ShouldNotifyObservers should_notify) {
+  GestureEventHelper* helper = FindDispatchHelperForConsumer(consumer);
+
+  if (!helper)
+    return false;
+
+  std::vector<std::unique_ptr<TouchEvent>> cancelling_touches =
+      GetEventPerPointForConsumer(consumer, ET_TOUCH_CANCELLED);
+  for (const std::unique_ptr<TouchEvent>& cancelling_touch : cancelling_touches)
+    helper->DispatchSyntheticTouchEvent(cancelling_touch.get());
+  if (should_notify == kNotifyObservers) {
+    for (GestureRecognizerObserver& observer : observers())
+      observer.OnActiveTouchesCanceled(consumer);
+  }
+  return !cancelling_touches.empty();
+}
+
 bool GestureRecognizerImpl::CleanupStateForConsumer(
     GestureConsumer* consumer) {
   bool state_cleaned_up = false;
diff --git a/ui/events/gestures/gesture_recognizer_impl.h b/ui/events/gestures/gesture_recognizer_impl.h
index ba6c416..36e8fc9 100644
--- a/ui/events/gestures/gesture_recognizer_impl.h
+++ b/ui/events/gestures/gesture_recognizer_impl.h
@@ -48,9 +48,10 @@
   GestureConsumer* GetTargetForLocation(const gfx::PointF& location,
                                         int source_device_id) override;
   void CancelActiveTouchesExcept(GestureConsumer* not_cancelled) override;
-  void TransferEventsTo(GestureConsumer* current_consumer,
-                        GestureConsumer* new_consumer,
-                        ShouldCancelTouches should_cancel_touches) override;
+  void TransferEventsTo(
+      GestureConsumer* current_consumer,
+      GestureConsumer* new_consumer,
+      TransferTouchesBehavior transfer_touches_behavior) override;
   bool GetLastTouchPointForTarget(GestureConsumer* consumer,
                                   gfx::PointF* point) override;
   bool CancelActiveTouches(GestureConsumer* consumer) override;
@@ -64,6 +65,8 @@
                                     GestureConsumer* consumer) override;
 
  private:
+  enum ShouldNotifyObservers { kNotifyObservers, kDontNotifyObservers };
+
   // Sets up the target consumer for gestures based on the touch-event.
   void SetupTargets(const TouchEvent& event, GestureConsumer* consumer);
 
@@ -75,6 +78,11 @@
                          bool is_source_touch_event_set_non_blocking,
                          GestureConsumer* consumer) override;
 
+  void CancelActiveTouchesExceptImpl(GestureConsumer* not_cancelled,
+                                     ShouldNotifyObservers should_notify);
+  bool CancelActiveTouchesImpl(GestureConsumer* consumer,
+                               ShouldNotifyObservers should_notify);
+
   bool CleanupStateForConsumer(GestureConsumer* consumer) override;
   void AddGestureEventHelper(GestureEventHelper* helper) override;
   void RemoveGestureEventHelper(GestureEventHelper* helper) override;
diff --git a/ui/events/gestures/gesture_recognizer_impl_mac.cc b/ui/events/gestures/gesture_recognizer_impl_mac.cc
index b52a8db..d66805df 100644
--- a/ui/events/gestures/gesture_recognizer_impl_mac.cc
+++ b/ui/events/gestures/gesture_recognizer_impl_mac.cc
@@ -45,7 +45,7 @@
 void GestureRecognizerImplMac::TransferEventsTo(
     GestureConsumer* current_consumer,
     GestureConsumer* new_consumer,
-    ShouldCancelTouches should_cancel_touches) {}
+    TransferTouchesBehavior transfer_touches_behavior) {}
 
 bool GestureRecognizerImplMac::GetLastTouchPointForTarget(
     GestureConsumer* consumer,
diff --git a/ui/events/gestures/gesture_recognizer_impl_mac.h b/ui/events/gestures/gesture_recognizer_impl_mac.h
index 391bd2b..f0c4615f 100644
--- a/ui/events/gestures/gesture_recognizer_impl_mac.h
+++ b/ui/events/gestures/gesture_recognizer_impl_mac.h
@@ -34,9 +34,10 @@
   GestureConsumer* GetTargetForLocation(const gfx::PointF& location,
                                         int source_device_id) override;
   void CancelActiveTouchesExcept(GestureConsumer* not_cancelled) override;
-  void TransferEventsTo(GestureConsumer* current_consumer,
-                        GestureConsumer* new_consumer,
-                        ShouldCancelTouches should_cancel_touches) override;
+  void TransferEventsTo(
+      GestureConsumer* current_consumer,
+      GestureConsumer* new_consumer,
+      TransferTouchesBehavior transfer_touches_behavior) override;
   bool GetLastTouchPointForTarget(GestureConsumer* consumer,
                                   gfx::PointF* point) override;
   bool CancelActiveTouches(GestureConsumer* consumer) override;
diff --git a/ui/events/gestures/gesture_recognizer_impl_unittest.cc b/ui/events/gestures/gesture_recognizer_impl_unittest.cc
new file mode 100644
index 0000000..6446df7
--- /dev/null
+++ b/ui/events/gestures/gesture_recognizer_impl_unittest.cc
@@ -0,0 +1,163 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/events/gestures/gesture_recognizer_impl.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/gestures/gesture_recognizer_observer.h"
+
+namespace ui {
+
+class TestGestureRecognizerObserver : public GestureRecognizerObserver {
+ public:
+  TestGestureRecognizerObserver(GestureRecognizer* gesture_recognizer)
+      : gesture_recognizer_(gesture_recognizer) {
+    gesture_recognizer_->AddObserver(this);
+  }
+
+  ~TestGestureRecognizerObserver() override {
+    gesture_recognizer_->RemoveObserver(this);
+  }
+
+  int active_touches_cancelled_except_call_count() const {
+    return active_touches_cancelled_except_call_count_;
+  }
+  int active_touches_cancelled_call_count() const {
+    return active_touches_cancelled_call_count_;
+  }
+  int events_transfer_call_count() const { return events_transfer_call_count_; }
+
+  const GestureConsumer* last_not_cancelled() const {
+    return last_not_cancelled_;
+  }
+  const GestureConsumer* last_cancelled() const {
+    return last_cancelled_consumer_;
+  }
+
+  const GestureConsumer* last_transferred_source() const {
+    return last_transferred_source_;
+  }
+  const GestureConsumer* last_transferred_destination() const {
+    return last_transferred_destination_;
+  }
+  TransferTouchesBehavior last_transfer_touches_behavior() const {
+    return last_transfer_touches_behavior_;
+  }
+
+ private:
+  // GestureRecognizerObserver:
+  void OnActiveTouchesCanceledExcept(GestureConsumer* not_cancelled) override {
+    active_touches_cancelled_except_call_count_++;
+    last_not_cancelled_ = not_cancelled;
+  }
+  void OnEventsTransferred(
+      GestureConsumer* current_consumer,
+      GestureConsumer* new_consumer,
+      TransferTouchesBehavior transfer_touches_behavior) override {
+    events_transfer_call_count_++;
+    last_transferred_source_ = current_consumer;
+    last_transferred_destination_ = new_consumer;
+    last_transfer_touches_behavior_ = transfer_touches_behavior;
+  }
+  void OnActiveTouchesCanceled(GestureConsumer* consumer) override {
+    active_touches_cancelled_call_count_++;
+    last_cancelled_consumer_ = consumer;
+  }
+
+  GestureRecognizer* gesture_recognizer_;
+
+  int active_touches_cancelled_except_call_count_ = 0;
+  int active_touches_cancelled_call_count_ = 0;
+  int events_transfer_call_count_ = 0;
+  GestureConsumer* last_not_cancelled_ = nullptr;
+  GestureConsumer* last_cancelled_consumer_ = nullptr;
+  GestureConsumer* last_transferred_source_ = nullptr;
+  GestureConsumer* last_transferred_destination_ = nullptr;
+  TransferTouchesBehavior last_transfer_touches_behavior_ =
+      TransferTouchesBehavior::kCancel;
+
+  DISALLOW_COPY_AND_ASSIGN(TestGestureRecognizerObserver);
+};
+
+class TestGestureEventHelper : public GestureEventHelper {
+ public:
+  TestGestureEventHelper() = default;
+  ~TestGestureEventHelper() override = default;
+
+ private:
+  // GestureEventHelper:
+  bool CanDispatchToConsumer(GestureConsumer* consumer) override {
+    return true;
+  }
+  void DispatchGestureEvent(GestureConsumer* raw_input_consumer,
+                            GestureEvent* event) override {}
+  void DispatchSyntheticTouchEvent(TouchEvent* event) override {}
+
+  DISALLOW_COPY_AND_ASSIGN(TestGestureEventHelper);
+};
+
+class GestureRecognizerImplTest : public testing::Test {
+ public:
+  GestureRecognizerImplTest() = default;
+  ~GestureRecognizerImplTest() override = default;
+
+  void SetUp() override {
+    gesture_recognizer_.helpers().push_back(&helper_);
+    observer_ =
+        std::make_unique<TestGestureRecognizerObserver>(&gesture_recognizer_);
+  }
+
+  void TearDown() override { observer_.reset(); }
+
+ protected:
+  GestureRecognizer* gesture_recognizer() { return &gesture_recognizer_; }
+  TestGestureRecognizerObserver* observer() { return observer_.get(); }
+
+ private:
+  GestureRecognizerImpl gesture_recognizer_;
+  TestGestureEventHelper helper_;
+  std::unique_ptr<TestGestureRecognizerObserver> observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(GestureRecognizerImplTest);
+};
+
+TEST_F(GestureRecognizerImplTest, CancelActiveTouchEvents) {
+  GestureConsumer consumer;
+  gesture_recognizer()->CancelActiveTouches(&consumer);
+
+  EXPECT_EQ(1, observer()->active_touches_cancelled_call_count());
+  EXPECT_EQ(&consumer, observer()->last_cancelled());
+}
+
+TEST_F(GestureRecognizerImplTest, CancelActiveTouchEventsExcept) {
+  GestureConsumer consumer;
+  gesture_recognizer()->CancelActiveTouchesExcept(&consumer);
+
+  // OnActiveTouchesCancelled() shouldn't occur.
+  EXPECT_EQ(0, observer()->active_touches_cancelled_call_count());
+  EXPECT_EQ(1, observer()->active_touches_cancelled_except_call_count());
+  EXPECT_EQ(&consumer, observer()->last_not_cancelled());
+}
+
+TEST_F(GestureRecognizerImplTest, CancelActiveTouchEventsExceptNullPtr) {
+  gesture_recognizer()->CancelActiveTouchesExcept(nullptr);
+
+  EXPECT_EQ(1, observer()->active_touches_cancelled_except_call_count());
+  EXPECT_FALSE(observer()->last_not_cancelled());
+}
+
+TEST_F(GestureRecognizerImplTest, TransferEventsTo) {
+  GestureConsumer consumer1;
+  GestureConsumer consumer2;
+
+  gesture_recognizer()->TransferEventsTo(&consumer1, &consumer2,
+                                         TransferTouchesBehavior::kDontCancel);
+  EXPECT_EQ(1, observer()->events_transfer_call_count());
+  EXPECT_EQ(&consumer1, observer()->last_transferred_source());
+  EXPECT_EQ(&consumer2, observer()->last_transferred_destination());
+  EXPECT_EQ(TransferTouchesBehavior::kDontCancel,
+            observer()->last_transfer_touches_behavior());
+}
+
+}  // namespace ui
diff --git a/ui/events/gestures/gesture_recognizer_observer.cc b/ui/events/gestures/gesture_recognizer_observer.cc
new file mode 100644
index 0000000..4430530
--- /dev/null
+++ b/ui/events/gestures/gesture_recognizer_observer.cc
@@ -0,0 +1,11 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/events/gestures/gesture_recognizer_observer.h"
+
+namespace ui {
+
+GestureRecognizerObserver::~GestureRecognizerObserver() = default;
+
+}  // namespace ui
diff --git a/ui/events/gestures/gesture_recognizer_observer.h b/ui/events/gestures/gesture_recognizer_observer.h
new file mode 100644
index 0000000..4826cda5
--- /dev/null
+++ b/ui/events/gestures/gesture_recognizer_observer.h
@@ -0,0 +1,30 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_EVENTS_GESTURES_GESTURE_RECOGNIZER_OBSERVER_H_
+#define UI_EVENTS_GESTURES_GESTURE_RECOGNIZER_OBSERVER_H_
+
+#include "base/observer_list_types.h"
+#include "ui/events/events_export.h"
+#include "ui/events/gestures/gesture_types.h"
+
+namespace ui {
+
+class EVENTS_EXPORT GestureRecognizerObserver : public base::CheckedObserver {
+ public:
+  virtual void OnActiveTouchesCanceledExcept(
+      GestureConsumer* not_cancelled) = 0;
+  virtual void OnEventsTransferred(
+      GestureConsumer* current_consumer,
+      GestureConsumer* new_consumer,
+      TransferTouchesBehavior transfer_touches_behavior) = 0;
+  virtual void OnActiveTouchesCanceled(GestureConsumer* consumer) = 0;
+
+ protected:
+  ~GestureRecognizerObserver() override;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURES_GESTURE_RECOGNIZER_OBSERVER_H_
diff --git a/ui/events/gestures/gesture_types.h b/ui/events/gestures/gesture_types.h
index 3d63c57..e233411 100644
--- a/ui/events/gestures/gesture_types.h
+++ b/ui/events/gestures/gesture_types.h
@@ -12,6 +12,17 @@
 class GestureEvent;
 class TouchEvent;
 
+// TransferTouchesBehavior customizes the behavior of
+// GestureRecognizer::TransferEventsTo.
+enum class TransferTouchesBehavior {
+  // Dispatches the cancel events to the current consumer on transfer to ensure
+  // its touch stream remains valid.
+  kCancel,
+
+  // Do not dispatch cancel events.
+  kDontCancel
+};
+
 // An abstract type for consumers of gesture-events created by the
 // gesture-recognizer.
 class EVENTS_EXPORT GestureConsumer {
diff --git a/ui/views/controls/menu/menu_host.cc b/ui/views/controls/menu/menu_host.cc
index 104ae4c6..780a0f9 100644
--- a/ui/views/controls/menu/menu_host.cc
+++ b/ui/views/controls/menu/menu_host.cc
@@ -86,7 +86,7 @@
 #else   // !defined(OS_MACOSX)
   source->GetGestureRecognizer()->TransferEventsTo(
       source->GetNativeView(), target->GetNativeView(),
-      ui::GestureRecognizer::ShouldCancelTouches::DontCancel);
+      ui::TransferTouchesBehavior::kDontCancel);
 #endif  // defined(OS_MACOSX)
 }
 
diff --git a/ui/views_bridge_mac/bridged_content_view.mm b/ui/views_bridge_mac/bridged_content_view.mm
index d4115a0..100d99e 100644
--- a/ui/views_bridge_mac/bridged_content_view.mm
+++ b/ui/views_bridge_mac/bridged_content_view.mm
@@ -195,6 +195,8 @@
     return ui::TextEditCommand::COPY;
   if (action == @selector(paste:))
     return ui::TextEditCommand::PASTE;
+  if (action == @selector(pasteAndMatchStyle:))
+    return ui::TextEditCommand::PASTE;
   if (action == @selector(selectAll:))
     return ui::TextEditCommand::SELECT_ALL;
   return ui::TextEditCommand::INVALID_COMMAND;
@@ -260,6 +262,7 @@
 - (void)cut:(id)sender;
 - (void)copy:(id)sender;
 - (void)paste:(id)sender;
+- (void)pasteAndMatchStyle:(id)sender;
 - (void)selectAll:(id)sender;
 
 @end
@@ -660,6 +663,13 @@
           eventFlags:ui::EF_CONTROL_DOWN];
 }
 
+- (void)pasteAndMatchStyle:(id)sender {
+  [self handleAction:ui::TextEditCommand::PASTE
+             keyCode:ui::VKEY_V
+             domCode:ui::DomCode::US_V
+          eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN];
+}
+
 - (void)selectAll:(id)sender {
   [self handleAction:ui::TextEditCommand::SELECT_ALL
              keyCode:ui::VKEY_A
@@ -855,6 +865,13 @@
 }
 
 - (void)flagsChanged:(NSEvent*)theEvent {
+  if (theEvent.keyCode == 0) {
+    // An event like this gets sent when sending some key commands via
+    // AppleScript. Since 0 is VKEY_A, we end up interpreting this as Cmd+A
+    // which is incorrect. The correct event for command up/down (keyCode = 55)
+    // is also sent, so we should drop this one. See https://crbug.com/889618
+    return;
+  }
   ui::KeyEvent event(theEvent);
   [self handleKeyEvent:&event];
 }