diff --git a/AUTHORS b/AUTHORS
index 03f2b4b..aec4fb2 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1029,6 +1029,7 @@
 NVIDIA Corporation <*@nvidia.com>
 Opera Software ASA <*@opera.com>
 Optical Tone Ltd <*@opticaltone.com>
+Pengutronix e.K. <*@pengutronix.de>
 Rakuten Kobo Inc. <*@kobo.com>
 Rakuten Kobo Inc. <*@rakuten.com>
 Seznam.cz, a.s. <*@firma.seznam.cz>
diff --git a/DEPS b/DEPS
index 6ccb663..6c2f029 100644
--- a/DEPS
+++ b/DEPS
@@ -121,11 +121,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'b7af275ebbf55b6c79cf361bcf87612b328ea50a',
+  'skia_revision': 'e9742a5a0db1e86f1b548d74c025404690eaed63',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'e0899ad9ee97543e6f45961fe13435882df25b8a',
+  'v8_revision': '7920be1f5a1fa8e0a6484e50a5e15bb4da2f29d9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -133,7 +133,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': '783809808ec09d2040047c252925b19f507b06c8',
+  'angle_revision': '794364ebd51c1c7d113fc4208857a15cd557b4db',
   # 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.
@@ -145,7 +145,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': 'f8940afa9d5f0810481a374a5282dfa4c774eb08',
+  'pdfium_revision': '96a7c58e4c7196b2f789b3e7177f580caed3417d',
   # 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.
@@ -179,6 +179,10 @@
   # and whatever else without interference from each other.
   'harfbuzz_revision': 'fe532923101586e316b300d419a337d357cd93da',
   # Three lines of non-changing comments so that
+  # the commit queue can handle CLs rolling Emoji Segmenter
+  # and whatever else without interference from each other.
+  'emoji_segmenter_revision': '9ba6d25d0d9313569665d4a9d2b34f0f39f9a50e',
+  # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
   'catapult_revision': '2061050c4d27b3b27bc3cfc7cf20fc99644f66f7',
@@ -197,7 +201,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': 'ed7c2ae304814efdb0e85cfbfe5205897b0d8d03',
+  'feed_revision': '25362a7395755d79755a83ebb5eb19ec6ad0af20',
   # 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.
@@ -690,7 +694,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '2e223a915e69248a5edc320d032731e16448d6fc',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'baf62c129325a527911fa12a625498ecb5cd39a8',
       'condition': 'checkout_linux',
   },
 
@@ -715,7 +719,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '1131ccb69495f362b169f663f276b92a6042d1a6',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '50b187ca83716db937923d9ed1ef031f6e071d2a',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -770,6 +774,9 @@
   'src/third_party/harfbuzz-ng/src':
     Var('chromium_git') + '/external/github.com/harfbuzz/harfbuzz.git' + '@' + Var('harfbuzz_revision'),
 
+  'src/third_party/emoji-segmenter/src/':
+    Var('chromium_git') + '/external/github.com/googlei18n/emoji-segmenter.git' + '@' + Var('emoji_segmenter_revision'),
+
   # Chrome OS touchpad gestures library.
   'src/third_party/gestures/gestures': {
       'url': Var('chromium_git') + '/chromiumos/platform/gestures.git' + '@' + '74f55100df966280d305d5d5ada824605f875839',
@@ -1251,7 +1258,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@c8eab41c449dab4ab19b8699b3fca179a40ec371',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@cad0b15c2af9195854eb6b0122d841c0deb66b5d',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/HeapProfilingTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/HeapProfilingTest.java
index 6f4f333..3057d796 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/HeapProfilingTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/HeapProfilingTest.java
@@ -36,35 +36,28 @@
     public void
     testModeBrowser() throws Exception {
         HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(shim.runTestForMode(
-                "browser", false, "native-include-thread-names", true, false, false));
+        Assert.assertTrue(
+                shim.runTestForMode("browser", false, "native-include-thread-names", false, false));
     }
 
     @Test
     @MediumTest
     public void testModeBrowserDynamicPseudo() throws Exception {
         HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", true, false, false));
+        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", false, false));
     }
 
     @Test
     @MediumTest
     public void testModeBrowserDynamicPseudoSampleEverything() throws Exception {
         HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", true, true, true));
+        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", true, true));
     }
 
     @Test
     @MediumTest
     public void testModeBrowserDynamicPseudoSamplePartial() throws Exception {
         HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", true, true, false));
-    }
-
-    @Test
-    @MediumTest
-    public void testModeBrowserDynamicPseudoSamplePartialNonStreaming() throws Exception {
-        HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", false, true, false));
+        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", true, false));
     }
 }
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/HeapProfilingTestShim.java b/android_webview/javatests/src/org/chromium/android_webview/test/HeapProfilingTestShim.java
index 6a3f052..7403f6b5 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/HeapProfilingTestShim.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/HeapProfilingTestShim.java
@@ -24,9 +24,9 @@
      *  rather than native stacks.
      */
     public boolean runTestForMode(String mode, boolean dynamicallyStartProfiling, String stackMode,
-            boolean streamSamples, boolean shouldSample, boolean sampleEverything) {
+            boolean shouldSample, boolean sampleEverything) {
         return nativeRunTestForMode(mNativeHeapProfilingTestShim, mode, dynamicallyStartProfiling,
-                stackMode, streamSamples, shouldSample, sampleEverything);
+                stackMode, shouldSample, sampleEverything);
     }
 
     /**
@@ -44,6 +44,6 @@
     private native long nativeInit();
     private native void nativeDestroy(long nativeHeapProfilingTestShim);
     private native boolean nativeRunTestForMode(long nativeHeapProfilingTestShim, String mode,
-            boolean dynamicallyStartProfiling, String stackMode, boolean streamSamples,
-            boolean shouldSample, boolean sampleEverything);
+            boolean dynamicallyStartProfiling, String stackMode, boolean shouldSample,
+            boolean sampleEverything);
 }
diff --git a/base/sampling_heap_profiler/poisson_allocation_sampler.cc b/base/sampling_heap_profiler/poisson_allocation_sampler.cc
index 14f20a5..eac17086 100644
--- a/base/sampling_heap_profiler/poisson_allocation_sampler.cc
+++ b/base/sampling_heap_profiler/poisson_allocation_sampler.cc
@@ -325,11 +325,6 @@
   TLSSetValue(g_internal_reentry_guard, false);
 }
 
-// static
-bool PoissonAllocationSampler::ScopedMuteThreadSamples::IsMuted() {
-  return TLSGetValue(g_internal_reentry_guard);
-}
-
 PoissonAllocationSampler* PoissonAllocationSampler::instance_;
 
 PoissonAllocationSampler::PoissonAllocationSampler() {
diff --git a/base/sampling_heap_profiler/poisson_allocation_sampler.h b/base/sampling_heap_profiler/poisson_allocation_sampler.h
index 0dfb8b2a8..a55fdea 100644
--- a/base/sampling_heap_profiler/poisson_allocation_sampler.h
+++ b/base/sampling_heap_profiler/poisson_allocation_sampler.h
@@ -54,8 +54,6 @@
    public:
     ScopedMuteThreadSamples();
     ~ScopedMuteThreadSamples();
-
-    static bool IsMuted();
   };
 
   // Must be called early during the process initialization. It creates and
diff --git a/chrome/VERSION b/chrome/VERSION
index f2692f14..7f4cf97 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=74
 MINOR=0
-BUILD=3689
+BUILD=3690
 PATCH=0
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java
index c17238e7..398085e3 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java
@@ -17,6 +17,7 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.ui.mojom.WindowOpenDisposition;
 
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -198,6 +199,11 @@
         nativeOnSpinnerShown(mNativeFeedLoggingBridge, timeShownMs);
     }
 
+    @Override
+    public void onPietFrameRenderingEvent(List<Integer> pietErrorCodes) {
+        // TODO(https://crbug.com/924739): Implementation.
+    }
+
     /**
      * Reports how long a user spends on the page.
      *
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
index 9ec78ee..eea9faee 100644
--- a/chrome/android/java/AndroidManifest.xml
+++ b/chrome/android/java/AndroidManifest.xml
@@ -740,16 +740,6 @@
         </activity>
         {% endfor %}
 
-        <activity android:name="org.chromium.chrome.browser.media.remote.ExpandedControllerActivity"
-            android:theme="@style/MainTheme"
-            android:label="Chrome.ExpandedControllerActivity"
-            android:hardwareAccelerated="true"
-            android:launchMode="singleTask"
-            android:noHistory="true"
-            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize"
-            android:excludeFromRecents="true">
-        </activity>
-
         <activity android:name="org.chromium.chrome.browser.media.router.caf.remoting.CafExpandedControllerActivity"
             android:theme="@style/MainTheme"
             android:label="Chrome.CafExpandedControllerActivity"
diff --git a/chrome/android/java/monochrome_public_apk.AndroidManifest.expected b/chrome/android/java/monochrome_public_apk.AndroidManifest.expected
index 0ce918f..0a4d36b 100644
--- a/chrome/android/java/monochrome_public_apk.AndroidManifest.expected
+++ b/chrome/android/java/monochrome_public_apk.AndroidManifest.expected
@@ -1333,16 +1333,6 @@
             </intent-filter>
         </activity>
         <activity
-            android:name="org.chromium.chrome.browser.media.remote.ExpandedControllerActivity"
-            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize"
-            android:excludeFromRecents="true"
-            android:hardwareAccelerated="true"
-            android:label="Chrome.ExpandedControllerActivity"
-            android:launchMode="singleTask"
-            android:noHistory="true"
-            android:theme="@style/MainTheme" >
-        </activity>
-        <activity
             android:name="org.chromium.chrome.browser.media.router.caf.remoting.CafExpandedControllerActivity"
             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize"
             android:excludeFromRecents="true"
diff --git a/chrome/android/java/res/layout/cast_controller_media_route_button.xml b/chrome/android/java/res/layout/cast_controller_media_route_button.xml
deleted file mode 100644
index 547361b..0000000
--- a/chrome/android/java/res/layout/cast_controller_media_route_button.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<org.chromium.chrome.browser.media.remote.FullscreenMediaRouteButton
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/cast_controller_media_route_button"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:visibility="visible"
-    android:layout_gravity = "start|top"
-    style="@style/CastMediaRouteButton" />
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index 6b85d55..29d16b6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -167,7 +167,6 @@
     public static final String AUTOFILL_ASSISTANT = "AutofillAssistant";
     public static final String AUTOFILL_REFRESH_STYLE_ANDROID = "AutofillRefreshStyleAndroid";
     public static final String AUTOFILL_KEYBOARD_ACCESSORY = "AutofillKeyboardAccessory";
-    public static final String CAF_MEDIA_ROUTER_IMPL = "CafMediaRouterImpl";
     public static final String CAPTIVE_PORTAL_CERTIFICATE_LIST = "CaptivePortalCertificateList";
     public static final String CCT_BACKGROUND_TAB = "CCTBackgroundTab";
     public static final String CCT_MODULE = "CCTModule";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/AbstractMediaRouteController.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/AbstractMediaRouteController.java
deleted file mode 100644
index bedcd3b958..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/AbstractMediaRouteController.java
+++ /dev/null
@@ -1,658 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.support.annotation.Nullable;
-import android.support.v7.media.MediaControlIntent;
-import android.support.v7.media.MediaItemStatus;
-import android.support.v7.media.MediaRouteSelector;
-import android.support.v7.media.MediaRouter;
-import android.support.v7.media.MediaRouter.RouteInfo;
-
-import com.google.android.gms.cast.CastMediaControlIntent;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.Log;
-import org.chromium.base.VisibleForTesting;
-import org.chromium.base.annotations.RemovableInRelease;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
-import org.chromium.ui.widget.Toast;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-/**
- * Class containing the common, connection type independent, code for all MediaRouteControllers.
- */
-public abstract class AbstractMediaRouteController implements MediaRouteController {
-
-    /**
-     * Callback class for monitoring whether any routes exist, and hence deciding whether to show
-     * the cast UI to users.
-     */
-    private class DeviceDiscoveryCallback extends MediaRouter.Callback {
-        @Override
-        public void onProviderAdded(MediaRouter router, MediaRouter.ProviderInfo provider) {
-            updateRouteAvailability();
-        }
-
-        @Override
-        public void onProviderChanged(
-                MediaRouter router, MediaRouter.ProviderInfo provider) {
-            updateRouteAvailability();
-        }
-
-        @Override
-        public void onProviderRemoved(
-                MediaRouter router, MediaRouter.ProviderInfo provider) {
-            updateRouteAvailability();
-        }
-
-        @Override
-        public void onRouteAdded(MediaRouter router, RouteInfo route) {
-            logRoute("Added route", route);
-            updateRouteAvailability();
-        }
-
-        @Override
-        public void onRouteRemoved(MediaRouter router, RouteInfo route) {
-            logRoute("Removed route", route);
-            updateRouteAvailability();
-        }
-
-        @Override
-        public void onRouteChanged(MediaRouter router, RouteInfo route) {
-            logRoute("Changed route", route);
-            updateRouteAvailability();
-        }
-
-        private void updateRouteAvailability() {
-            if (mediaRouterInitializationFailed()) return;
-
-            boolean routesAvailable = getMediaRouter().isRouteAvailable(mMediaRouteSelector,
-                    MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
-            if (routesAvailable != mRoutesAvailable) {
-                mRoutesAvailable = routesAvailable;
-                Log.d(TAG, "Remote media route availability changed, updating listeners");
-                for (MediaStateListener listener : mAvailableRouteListeners) {
-                    listener.onRouteAvailabilityChanged(routesAvailable);
-                }
-            }
-        }
-    }
-
-    /**
-     * Callback class for monitoring whether a route has been selected, and the state of the
-     * selected route.
-     */
-    private class DeviceSelectionCallback extends MediaRouter.Callback {
-        // Note that this doesn't use onRouteSelected, but instead casting is started directly
-        // by the selection dialog. It has to be done that way, since selecting the current route
-        // on a new video doesn't call onRouteSelected.
-
-        private Runnable mConnectionFailureNotifier = new Runnable() {
-                @Override
-            public void run() {
-                release();
-                mConnectionFailureNotifierQueued = false;
-            }
-        };
-
-        /** True if we are waiting for the MediaRouter route to connect or reconnect */
-        private boolean mConnectionFailureNotifierQueued;
-
-        private void clearConnectionFailureCallback() {
-            getHandler().removeCallbacks(mConnectionFailureNotifier);
-            mConnectionFailureNotifierQueued = false;
-        }
-
-        @Override
-        public void onRouteChanged(MediaRouter router, RouteInfo route) {
-            // We only care about changes to the current route.
-            if (!route.equals(getCurrentRoute())) return;
-            // When there is no wifi connection, this condition becomes true.
-            if (route.isConnecting()) {
-                // We don't want to post the same Runnable twice.
-                if (!mConnectionFailureNotifierQueued) {
-                    mConnectionFailureNotifierQueued = true;
-                    getHandler().postDelayed(mConnectionFailureNotifier,
-                            CONNECTION_FAILURE_NOTIFICATION_DELAY_MS);
-                }
-            } else {
-                // Only cancel the disconnect if we already posted the message. We can get into this
-                // situation if we swap the current route provider (for example, switching to a YT
-                // video while casting a non-YT video).
-                if (mConnectionFailureNotifierQueued) {
-                    // We have reconnected, cancel the delayed disconnect.
-                    getHandler().removeCallbacks(mConnectionFailureNotifier);
-                    mConnectionFailureNotifierQueued = false;
-                }
-            }
-        }
-
-        @Override
-        public void onRouteUnselected(MediaRouter router, RouteInfo route) {
-            onRouteUnselectedEvent(router, route);
-            if (getCurrentRoute() != null && !getCurrentRoute().isDefault()
-                    && route.getId().equals(getCurrentRoute().getId())) {
-                RecordCastAction.castEndedTimeRemaining(getDuration(),
-                        getDuration() - getPosition());
-                release();
-            }
-        }
-    }
-
-    /** Number of ms to wait for reconnection, after which we call the failure callbacks. */
-    protected static final long CONNECTION_FAILURE_NOTIFICATION_DELAY_MS = 10000L;
-    private static final long END_OF_VIDEO_THRESHOLD_MS = 500L;
-    private static final String TAG = "MediaFling";
-    private final Set<MediaStateListener> mAvailableRouteListeners;
-    private final Context mContext;
-    private RouteInfo mCurrentRoute;
-    private final DeviceDiscoveryCallback mDeviceDiscoveryCallback;;
-    private final DeviceSelectionCallback mDeviceSelectionCallback;
-
-    private final Handler mHandler;
-    private boolean mIsPrepared;
-
-    private final MediaRouter mMediaRouter;
-
-    private final MediaRouteSelector mMediaRouteSelector;
-    /**
-     * The media state listener connects to the web page that requested casting. It will be null if
-     * that page is no longer in a tab, but closing the page or tab should not stop cast. Cast can
-     * still be controlled through the notification even if the page is closed.
-     */
-    private MediaStateListener mMediaStateListener;
-
-    // There are times when the player state shown to user (e.g. just after pressing the pause
-    // button) should update before we receive an update from the Chromecast, so we have to track
-    // two player states.
-    private @PlayerState int mRemotePlayerState = PlayerState.FINISHED;
-    private @PlayerState int mDisplayedPlayerState = PlayerState.FINISHED;
-    private boolean mRoutesAvailable;
-    private final Set<UiListener> mUiListeners;
-    private boolean mWatchingRouteSelection;
-
-    private long mMediaElementAttachedTimestampMs;
-    private long mMediaElementDetachedTimestampMs;
-
-    protected AbstractMediaRouteController() {
-        mContext = ContextUtils.getApplicationContext();
-        assert (getContext() != null);
-
-        mHandler = new Handler();
-
-        mMediaRouteSelector = buildMediaRouteSelector();
-
-        MediaRouter mediaRouter;
-
-        try {
-            // Pre-MR1 versions of JB do not have the complete MediaRouter APIs,
-            // so getting the MediaRouter instance will throw an exception.
-            mediaRouter = MediaRouter.getInstance(getContext());
-        } catch (NoSuchMethodError e) {
-            Log.e(TAG, "Can't get an instance of MediaRouter, casting is not supported."
-                    + " Are you still on JB (JVP15S)?");
-            mediaRouter = null;
-        }
-        mMediaRouter = mediaRouter;
-
-        mAvailableRouteListeners = new HashSet<MediaStateListener>();
-        // TODO(aberent): I am unclear why this is accessed from multiple threads, but
-        // if I make it a HashSet then it gets ConcurrentModificationExceptions on some
-        // types of disconnect. Investigate and fix.
-        mUiListeners = new CopyOnWriteArraySet<UiListener>();
-
-        mDeviceDiscoveryCallback = new DeviceDiscoveryCallback();
-        mDeviceSelectionCallback = new DeviceSelectionCallback();
-    }
-
-    @Override
-    public void addMediaStateListener(MediaStateListener listener) {
-        if (mediaRouterInitializationFailed()) return;
-
-        if (mAvailableRouteListeners.isEmpty()) {
-            getMediaRouter().addCallback(mMediaRouteSelector, mDeviceDiscoveryCallback,
-                    MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
-            Log.d(TAG, "Started device discovery");
-
-            // Get the initial state
-            mRoutesAvailable = getMediaRouter().isRouteAvailable(
-                    mMediaRouteSelector, MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
-        }
-        mAvailableRouteListeners.add(listener);
-        // Send the current state to the listener.
-        listener.onRouteAvailabilityChanged(mRoutesAvailable);
-    }
-
-    @Override
-    public void addUiListener(UiListener listener) {
-        mUiListeners.add(listener);
-    }
-
-    protected void clearConnectionFailureCallback() {
-        mDeviceSelectionCallback.clearConnectionFailureCallback();
-    }
-
-    /**
-     * Clear the current playing item (if any) but not the associated session.
-     */
-    protected void clearItemState() {
-        mRemotePlayerState = PlayerState.FINISHED;
-        mDisplayedPlayerState = PlayerState.FINISHED;
-        updateTitle(null);
-    }
-
-    /**
-     * Reset the media route to the default
-     */
-    protected void clearMediaRoute() {
-        if (getMediaRouter() != null) {
-            getMediaRouter().getDefaultRoute().select();
-            registerRoute(getMediaRouter().getDefaultRoute());
-        }
-    }
-
-    @Override
-    public boolean currentRouteSupportsRemotePlayback() {
-        return mCurrentRoute != null && mCurrentRoute.supportsControlCategory(
-                MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
-    }
-
-    protected final Context getContext() {
-        return mContext;
-    }
-
-    protected final RouteInfo getCurrentRoute() {
-        return mCurrentRoute;
-    }
-
-    protected final Handler getHandler() {
-        return mHandler;
-    }
-
-    protected final MediaRouter getMediaRouter() {
-        return mMediaRouter;
-    }
-
-    @Override
-    public final MediaStateListener getMediaStateListener() {
-        return mMediaStateListener;
-    }
-
-    public final @PlayerState int getRemotePlayerState() {
-        return mRemotePlayerState;
-    }
-
-    @Override
-    public final @PlayerState int getDisplayedPlayerState() {
-        return mDisplayedPlayerState;
-    }
-
-    @Override
-    public final String getRouteName() {
-        return mCurrentRoute == null ? null : mCurrentRoute.getName();
-    }
-
-    protected final Set<UiListener> getUiListeners() {
-        return mUiListeners;
-    }
-
-    private final boolean isAtEndOfVideo(long positionMs, long videoLengthMs) {
-        return videoLengthMs - positionMs < END_OF_VIDEO_THRESHOLD_MS && videoLengthMs > 0;
-    }
-
-    @Override
-    public final boolean isBeingCast() {
-        return (mIsPrepared && mRemotePlayerState != PlayerState.INVALIDATED
-                && mRemotePlayerState != PlayerState.ERROR
-                && mRemotePlayerState != PlayerState.FINISHED);
-    }
-
-    @Override
-    public final boolean isPlaying() {
-        return mRemotePlayerState == PlayerState.PLAYING
-                || mRemotePlayerState == PlayerState.LOADING;
-    }
-
-    @Override
-    public final boolean isRemotePlaybackAvailable() {
-        if (mediaRouterInitializationFailed()) return false;
-
-        return getMediaRouter().getSelectedRoute().getPlaybackType()
-                == MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE || getMediaRouter().isRouteAvailable(
-                mMediaRouteSelector, MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
-    }
-
-    protected final boolean mediaRouterInitializationFailed() {
-        return getMediaRouter() == null;
-    }
-
-    protected final void notifyRouteSelected(RouteInfo route) {
-        for (UiListener listener : mUiListeners) {
-            listener.onRouteSelected(route.getName(), this);
-        }
-        if (!canCastMedia()) return;
-        if (mMediaStateListener == null) return;
-        mMediaStateListener.pauseLocal();
-        mMediaStateListener.onCastStarting(route.getName());
-        startCastingVideo();
-    }
-
-    // This exists for compatibility with old downstream code
-    // TODO(aberent) convert to abstract
-    protected void startCastingVideo() {
-        String url = mMediaStateListener.getSourceUrl();
-        Uri uri = url == null ? null : Uri.parse(url);
-        setDataSource(uri, mMediaStateListener.getCookies());
-        prepareAsync(
-                mMediaStateListener.getFrameUrl(), mMediaStateListener.getStartPositionMillis());
-    }
-
-    private boolean canCastMedia() {
-        return isRemotePlaybackAvailable() && !routeIsDefaultRoute()
-                && currentRouteSupportsRemotePlayback();
-    }
-
-    protected void onRouteAddedEvent(MediaRouter router, RouteInfo route) {
-    };
-
-    // TODO(aberent): Merge with onRouteSelected(). Needs two sided patch for downstream
-    // implementations
-    protected void onRouteSelectedEvent(MediaRouter router, RouteInfo route) {
-    }
-
-    @Override
-    public boolean shouldFilterOutRoute(RouteInfo route) {
-        return false;
-    }
-
-    @Override
-    public void onRouteSelected(MediaStateListener player, MediaRouter router, RouteInfo route) {
-        if (mMediaStateListener != null) mMediaStateListener.onCastStopping();
-        setMediaStateListener(player);
-        onRouteSelectedEvent(router, route);
-    }
-
-    protected abstract void onRouteUnselectedEvent(MediaRouter router, RouteInfo route);
-
-    @Override
-    public void prepareMediaRoute() {
-        startWatchingRouteSelection();
-    }
-
-    @Override
-    public void release() {
-        recordEndOfSessionUMA();
-    }
-
-    private void recordEndOfSessionUMA() {
-        long remotePlaybackStoppedTimestampMs = SystemClock.elapsedRealtime();
-
-        // There was no media element ever...
-        if (mMediaElementAttachedTimestampMs == 0) return;
-
-        long remotePlaybackIntervalMs =
-                remotePlaybackStoppedTimestampMs - mMediaElementAttachedTimestampMs;
-
-        if (mMediaElementDetachedTimestampMs == 0) {
-            mMediaElementDetachedTimestampMs = remotePlaybackStoppedTimestampMs;
-        }
-
-        int noElementRemotePlaybackTimePercentage =
-                (int) ((remotePlaybackStoppedTimestampMs - mMediaElementDetachedTimestampMs) * 100
-                        / remotePlaybackIntervalMs);
-        RecordCastAction.recordRemoteSessionTimeWithoutMediaElementPercentage(
-                noElementRemotePlaybackTimePercentage);
-        mMediaElementAttachedTimestampMs = 0;
-        mMediaElementDetachedTimestampMs = 0;
-    }
-
-    protected final void registerRoute(RouteInfo route) {
-        mCurrentRoute = route;
-        logRoute("Selected route", route);
-    }
-
-    @RemovableInRelease
-    private void logRoute(String message, RouteInfo route) {
-        if (route != null) {
-            Log.d(TAG, message + " " + route.getName() + " " + route.getId());
-        }
-    }
-
-    protected void removeAllListeners() {
-        mUiListeners.clear();
-    }
-
-    @Override
-    public void removeMediaStateListener(MediaStateListener listener) {
-        if (mediaRouterInitializationFailed()) return;
-
-        mAvailableRouteListeners.remove(listener);
-        if (mAvailableRouteListeners.isEmpty()) {
-            getMediaRouter().removeCallback(mDeviceDiscoveryCallback);
-            Log.d(TAG, "Stopped device discovery");
-        }
-    }
-
-    @Override
-    public void removeUiListener(UiListener listener) {
-        mUiListeners.remove(listener);
-    }
-
-    @Override
-    public boolean routeIsDefaultRoute() {
-        return mCurrentRoute != null && mCurrentRoute.isDefault();
-    }
-
-    protected void sendErrorToListeners(int error) {
-        String errorMessage =
-                getContext().getString(R.string.cast_error_playing_video, mCurrentRoute.getName());
-
-        for (UiListener listener : mUiListeners) {
-            listener.onError(error, errorMessage);
-        }
-
-        if (mMediaStateListener != null) mMediaStateListener.onError();
-    }
-
-    @Override
-    public void setMediaStateListener(MediaStateListener mediaStateListener) {
-        if (mMediaStateListener != null && mediaStateListener == null
-                    && mMediaElementAttachedTimestampMs != 0) {
-            mMediaElementDetachedTimestampMs = SystemClock.elapsedRealtime();
-        } else if (mMediaStateListener == null && mediaStateListener != null) {
-            // We're switching the videos so let's record the UMA for the previous one.
-            if (mMediaElementDetachedTimestampMs != 0) recordEndOfSessionUMA();
-
-            mMediaElementAttachedTimestampMs = SystemClock.elapsedRealtime();
-            mMediaElementDetachedTimestampMs = 0;
-        }
-
-        mMediaStateListener = mediaStateListener;
-    }
-
-    private void onCasting() {
-        if (!mIsPrepared) {
-            for (UiListener listener : mUiListeners) {
-                listener.onPrepared(this);
-            }
-            if (mMediaStateListener != null) {
-                if (mMediaStateListener.isPauseRequested()) pause();
-                if (mMediaStateListener.isSeekRequested()) {
-                    seekTo(mMediaStateListener.getSeekLocation());
-                } else {
-                    seekTo(mMediaStateListener.getLocalPosition());
-                }
-                mMediaStateListener.onCastStarted();
-            }
-            RecordCastAction.castDefaultPlayerResult(true);
-            mIsPrepared = true;
-        }
-    }
-
-    protected void setUnprepared() {
-        mIsPrepared = false;
-    }
-
-    protected void showCastError(String routeName) {
-        Toast toast = Toast.makeText(
-                getContext(),
-                getContext().getString(R.string.cast_error_playing_video, routeName),
-                Toast.LENGTH_SHORT);
-        toast.show();
-    }
-
-    private void startWatchingRouteSelection() {
-        if (mWatchingRouteSelection || mediaRouterInitializationFailed()) return;
-
-        mWatchingRouteSelection = true;
-        // Start listening
-        getMediaRouter().addCallback(mMediaRouteSelector, mDeviceSelectionCallback,
-                MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
-        Log.d(TAG, "Started route selection discovery");
-    }
-
-    protected void stopWatchingRouteSelection() {
-        mWatchingRouteSelection = false;
-        if (getMediaRouter() != null) {
-            getMediaRouter().removeCallback(mDeviceSelectionCallback);
-            Log.d(TAG, "Stopped route selection discovery");
-        }
-    }
-
-    @VisibleForTesting
-    void setPlayerStateForMediaItemState(int state) {
-        @PlayerState
-        int playerState = PlayerState.STOPPED;
-        switch (state) {
-            case MediaItemStatus.PLAYBACK_STATE_BUFFERING:
-                playerState = PlayerState.LOADING;
-                break;
-            case MediaItemStatus.PLAYBACK_STATE_CANCELED:
-                playerState = PlayerState.FINISHED;
-                break;
-            case MediaItemStatus.PLAYBACK_STATE_ERROR:
-                playerState = PlayerState.ERROR;
-                break;
-            case MediaItemStatus.PLAYBACK_STATE_FINISHED:
-                playerState = PlayerState.FINISHED;
-                break;
-            case MediaItemStatus.PLAYBACK_STATE_INVALIDATED:
-                playerState = PlayerState.INVALIDATED;
-                break;
-            case MediaItemStatus.PLAYBACK_STATE_PAUSED:
-                if (isAtEndOfVideo(getPosition(), getDuration())) {
-                    playerState = PlayerState.FINISHED;
-                } else {
-                    playerState = PlayerState.PAUSED;
-                }
-                break;
-            case MediaItemStatus.PLAYBACK_STATE_PENDING:
-                playerState = PlayerState.PAUSED;
-                break;
-            case MediaItemStatus.PLAYBACK_STATE_PLAYING:
-                playerState = PlayerState.PLAYING;
-                break;
-            default:
-                break;
-        }
-
-        mRemotePlayerState = playerState;
-    }
-
-    protected void updateState(int state) {
-        Log.d(TAG, "updateState oldState: %s player state: %s", mRemotePlayerState, state);
-
-        @PlayerState
-        int oldState = mRemotePlayerState;
-        setPlayerStateForMediaItemState(state);
-
-        Log.d(TAG, "updateState newState: %s", mRemotePlayerState);
-
-        if (oldState != mRemotePlayerState) {
-            setDisplayedPlayerState(mRemotePlayerState);
-            switch (mRemotePlayerState) {
-                case PlayerState.PLAYING:
-                    onCasting();
-                    break;
-                case PlayerState.PAUSED:
-                    onCasting();
-                    break;
-                case PlayerState.FINISHED:
-                    release();
-                    break;
-                case PlayerState.INVALIDATED:
-                    clearItemState();
-                    break;
-                case PlayerState.ERROR:
-                    sendErrorToListeners(CastMediaControlIntent.ERROR_CODE_REQUEST_FAILED);
-                    release();
-                    break;
-                default:
-                    break;
-            }
-        }
-    }
-
-    protected void setDisplayedPlayerState(@PlayerState int state) {
-        mDisplayedPlayerState = state;
-        for (UiListener listener : mUiListeners) {
-            listener.onPlaybackStateChanged(mDisplayedPlayerState);
-        }
-        if (mMediaStateListener != null) {
-            mMediaStateListener.onPlaybackStateChanged(mDisplayedPlayerState);
-        }
-    }
-
-    protected void updateTitle(@Nullable String newTitle) {
-        for (UiListener listener : mUiListeners) {
-            listener.onTitleChanged(newTitle);
-        }
-    }
-
-    @Override
-    public Bitmap getPoster() {
-        if (getMediaStateListener() == null) return null;
-        return getMediaStateListener().getPosterBitmap();
-    }
-
-    // This exists for compatibility with old downstream code
-    // TODO(aberent) remove
-    protected void prepareAsync(String frameUrl, long startPositionMillis){};
-
-    // This exists for compatibility with old downstream code
-    // TODO(aberent) remove
-    protected void setDataSource(Uri uri, String cookies){};
-
-    protected boolean reconnectAnyExistingRoute() {
-        // Temp version to avoid two sided patch while removing
-        return false;
-    };
-
-    @Override
-    public void checkIfPlayableRemotely(String sourceUrl, String frameUrl, String cookies,
-            String userAgent, MediaValidationCallback callback) {
-        callback.onResult(true, sourceUrl, frameUrl);
-    }
-
-    @Override
-    public String getUriPlaying() {
-        return null;
-    }
-
-    // Used by J
-    void setPreparedForTesting() {
-        mIsPrepared = true;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/CastNotificationControl.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/CastNotificationControl.java
deleted file mode 100644
index 106c67c..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/CastNotificationControl.java
+++ /dev/null
@@ -1,293 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.media.AudioManager;
-import android.support.annotation.Nullable;
-
-import org.chromium.base.ApplicationStatus;
-import org.chromium.base.Log;
-import org.chromium.base.VisibleForTesting;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ChromeTabbedActivity;
-import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
-import org.chromium.chrome.browser.media.ui.MediaNotificationInfo;
-import org.chromium.chrome.browser.media.ui.MediaNotificationListener;
-import org.chromium.chrome.browser.media.ui.MediaNotificationManager;
-import org.chromium.chrome.browser.metrics.MediaNotificationUma;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.components.url_formatter.UrlFormatter;
-import org.chromium.services.media_session.MediaMetadata;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-
-/**
- * Notification control controls the cast notification and lock screen, using
- * {@link MediaNotificationManager}
- */
-public class CastNotificationControl implements MediaRouteController.UiListener,
-        MediaNotificationListener, AudioManager.OnAudioFocusChangeListener {
-    private static final String TAG = "MediaFling";
-
-    @SuppressLint("StaticFieldLeak")
-    private static CastNotificationControl sInstance;
-
-    private Bitmap mPosterBitmap;
-    protected MediaRouteController mMediaRouteController;
-    private MediaNotificationInfo.Builder mNotificationBuilder;
-    private Context mContext;
-    private @PlayerState int mState;
-    private String mTitle = "";
-    private AudioManager mAudioManager;
-
-    /**
-     * Contains the origin of the tab containing the video when it's cast. The origin is formatted
-     * to be presented better from the security POV if the URL is parseable. Otherwise it's just the
-     * URL of the tab if the tab exists. Can be null.
-     */
-    @Nullable
-    private String mTabOrigin;
-
-    private boolean mIsShowing;
-
-    private static final Object LOCK = new Object();
-
-
-    private CastNotificationControl(Context context) {
-        mContext = context;
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-    }
-    /**
-     * Get the unique NotificationControl, creating it if necessary.
-     * @param context The context of the activity
-     * @param mediaRouteController The current mediaRouteController, if any.
-     * @return a {@code LockScreenTransportControl} based on the platform's SDK API or null if the
-     *         current platform's SDK API is not supported.
-     */
-    public static CastNotificationControl getOrCreate(Context context,
-            @Nullable MediaRouteController mediaRouteController) {
-        synchronized (LOCK) {
-            if (sInstance == null) {
-                sInstance = new CastNotificationControl(context);
-            }
-            sInstance.setRouteController(mediaRouteController);
-            return sInstance;
-        }
-    }
-
-    /**
-     * @return the poster bitmap previously assigned with {@link #setPosterBitmap(Bitmap)}, or
-     * {@code null} if the poster bitmap has not yet been assigned.
-     */
-    public final Bitmap getPosterBitmap() {
-        return mPosterBitmap;
-    }
-
-    /**
-     * Sets the poster bitmap to display on the TransportControl.
-     */
-    public final void setPosterBitmap(Bitmap posterBitmap) {
-        if (mPosterBitmap == posterBitmap
-                || (mPosterBitmap != null && mPosterBitmap.sameAs(posterBitmap))) {
-            return;
-        }
-        mPosterBitmap = posterBitmap;
-        if (mNotificationBuilder == null || mMediaRouteController == null) return;
-
-        updateNotificationBuilderIfPosterIsGoodEnough();
-        mNotificationBuilder.setNotificationLargeIcon(mMediaRouteController.getPoster());
-        updateNotification();
-    }
-
-    public void hide() {
-        mIsShowing = false;
-        MediaNotificationManager.hide(Tab.INVALID_TAB_ID, R.id.remote_notification);
-        mAudioManager.abandonAudioFocus(this);
-        mMediaRouteController.removeUiListener(this);
-    }
-
-    public void show(@PlayerState int initialState) {
-        mMediaRouteController.addUiListener(this);
-        // TODO(aberent): investigate why this is necessary, and whether we are handling
-        // it correctly. Also add code to restore it when Chrome is resumed.
-        mAudioManager.requestAudioFocus(this, AudioManager.USE_DEFAULT_STREAM_TYPE,
-                AudioManager.AUDIOFOCUS_GAIN);
-        Intent contentIntent = new Intent(mContext, ExpandedControllerActivity.class);
-        contentIntent.putExtra(
-                MediaNotificationUma.INTENT_EXTRA_NAME, MediaNotificationUma.Source.MEDIA_FLING);
-        mNotificationBuilder = new MediaNotificationInfo.Builder()
-                .setPaused(false)
-                .setPrivate(false)
-                .setNotificationSmallIcon(R.drawable.ic_notification_media_route)
-                .setContentIntent(contentIntent)
-                .setDefaultNotificationLargeIcon(R.drawable.cast_playing_square)
-                .setId(R.id.remote_notification)
-                .setListener(this);
-
-        updateNotificationBuilderIfPosterIsGoodEnough();
-        mState = initialState;
-
-        updateNotification();
-        mIsShowing = true;
-    }
-
-    public void setRouteController(MediaRouteController controller) {
-        if (mMediaRouteController != null) mMediaRouteController.removeUiListener(this);
-        mMediaRouteController = controller;
-        if (controller != null) controller.addUiListener(this);
-    }
-
-    private void updateNotification() {
-        // Nothing shown yet, nothing to update.
-        if (mNotificationBuilder == null) return;
-
-        mNotificationBuilder.setMetadata(new MediaMetadata(mTitle, "", ""));
-        if (mTabOrigin != null) mNotificationBuilder.setOrigin(mTabOrigin);
-
-        if (mState == PlayerState.PAUSED || mState == PlayerState.PLAYING) {
-            mNotificationBuilder.setPaused(mState != PlayerState.PLAYING);
-            mNotificationBuilder.setActions(MediaNotificationInfo.ACTION_STOP
-                    | MediaNotificationInfo.ACTION_PLAY_PAUSE);
-            MediaNotificationManager.show(mNotificationBuilder.build());
-        } else if (mState == PlayerState.LOADING) {
-            mNotificationBuilder.setActions(MediaNotificationInfo.ACTION_STOP);
-            MediaNotificationManager.show(mNotificationBuilder.build());
-        } else {
-            hide();
-        }
-    }
-
-    // TODO(aberent) at the moment this is only called from a test, but it should be called if the
-    // poster changes.
-    public void onPosterBitmapChanged() {
-        if (mNotificationBuilder == null || mMediaRouteController == null) return;
-        updateNotificationBuilderIfPosterIsGoodEnough();
-        updateNotification();
-    }
-
-    // MediaRouteController.UiListener implementation.
-    @Override
-    public void onPlaybackStateChanged(@PlayerState int newState) {
-        if (!mIsShowing
-                && (newState == PlayerState.PLAYING || newState == PlayerState.LOADING
-                        || newState == PlayerState.PAUSED)) {
-            show(newState);
-            return;
-        }
-
-        if (mState == newState
-                || mState == PlayerState.PAUSED && newState == PlayerState.LOADING && mIsShowing) {
-            return;
-        }
-
-        mState = newState;
-        updateNotification();
-    }
-
-    @Override
-    public void onRouteSelected(String name, MediaRouteController mediaRouteController) {
-        // The notification will be shown/updated later so don't update it in case it's still
-        // showing for the previous video.
-        mTabOrigin = getCurrentTabOrigin();
-    }
-
-    @Override
-    public void onRouteUnselected(MediaRouteController mediaRouteController) {
-        hide();
-    }
-
-    @Override
-    public void onPrepared(MediaRouteController mediaRouteController) {
-    }
-
-    @Override
-    public void onError(int errorType, String message) {
-    }
-
-    @Override
-    public void onDurationUpdated(long durationMillis) {
-    }
-
-    @Override
-    public void onPositionChanged(long positionMillis) {
-    }
-
-    @Override
-    public void onTitleChanged(String title) {
-        if (title == null || title.equals(mTitle)) return;
-
-        mTitle = title;
-        updateNotification();
-    }
-
-    // MediaNotificationListener methods
-    @Override
-    public void onPlay(int actionSource) {
-        mMediaRouteController.resume();
-    }
-
-    @Override
-    public void onPause(int actionSource) {
-        mMediaRouteController.pause();
-    }
-
-    @Override
-    public void onStop(int actionSource) {
-        mMediaRouteController.release();
-    }
-
-    @Override
-    public void onMediaSessionAction(int action) {}
-
-    // AudioManager.OnAudioFocusChangeListener methods
-    @Override
-    public void onAudioFocusChange(int focusChange) {
-        // Do nothing. The remote player should keep playing.
-    }
-
-    @VisibleForTesting
-    static CastNotificationControl getForTests() {
-        return sInstance;
-    }
-
-    @VisibleForTesting
-    boolean isShowingForTests() {
-        return mIsShowing;
-    }
-
-    private void updateNotificationBuilderIfPosterIsGoodEnough() {
-        Bitmap poster = mMediaRouteController.getPoster();
-        if (MediaNotificationManager.isBitmapSuitableAsMediaImage(poster)) {
-            mNotificationBuilder.setNotificationLargeIcon(poster);
-            mNotificationBuilder.setMediaSessionImage(poster);
-        }
-    }
-
-    private String getCurrentTabOrigin() {
-        Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
-
-        if (!(activity instanceof ChromeTabbedActivity)) return null;
-
-        Tab tab = ((ChromeTabbedActivity) activity).getActivityTab();
-        if (tab == null || !tab.isInitialized()) return null;
-
-        String url = tab.getUrl();
-        try {
-            URI uri = new URI(url);
-            return UrlFormatter.formatUrlForSecurityDisplay(url);
-        } catch (URISyntaxException | UnsatisfiedLinkError e) {
-            // UnstatisfiedLinkError can only happen in tests as the natives are not initialized
-            // yet.
-            Log.e(TAG, "Unable to parse the origin from the URL. Using the full URL instead.");
-            return url;
-        }
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/DefaultMediaRouteController.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/DefaultMediaRouteController.java
deleted file mode 100644
index dac7692a..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/DefaultMediaRouteController.java
+++ /dev/null
@@ -1,866 +0,0 @@
-// Copyright 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.
-
-package org.chromium.chrome.browser.media.remote;
-
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.support.annotation.Nullable;
-import android.support.v7.media.MediaControlIntent;
-import android.support.v7.media.MediaItemMetadata;
-import android.support.v7.media.MediaItemStatus;
-import android.support.v7.media.MediaRouteSelector;
-import android.support.v7.media.MediaRouter;
-import android.support.v7.media.MediaRouter.RouteInfo;
-import android.support.v7.media.MediaSessionStatus;
-
-import com.google.android.gms.cast.CastMediaControlIntent;
-
-import org.chromium.base.ApplicationState;
-import org.chromium.base.ApplicationStatus;
-import org.chromium.base.Log;
-import org.chromium.base.annotations.RemovableInRelease;
-import org.chromium.base.annotations.UsedByReflection;
-import org.chromium.base.task.AsyncTask;
-import org.chromium.chrome.browser.UrlConstants;
-import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-
-/**
- * Class that abstracts all communication to and from the Android MediaRoutes. This class is
- * responsible for connecting to the MRs as well as sending commands and receiving status updates
- * from the remote player.
- *
- *  We have two main scenarios for Cast:
- *
- *  - the first cast: user plays the first video on the Chromecast so we start a new session with
- * the player and fling the video
- *
- *  - the consequent cast: users plays another video while the previous one is still playing
- * remotely meaning that we don't have to start the session but to replace the current video with
- * the new one
- *
- *  Casting the first video takes two intents sent to the selected media route:
- * ACTION_START_SESSION and ACTION_PLAY. The first one is sent before anything else. We get the
- * session id from the result bundle of the intent but need to wait until the session becomes
- * active before sending the video URL via the ACTION_PLAY intent.
- *
- *  Casting the second video to the same target device only takes one ACTION_PLAY intent if
- * the session is still active. Otherwise, the scenario is the same as for the first video.
- */
-@UsedByReflection("RemoteMediaPlayerController.java")
-public class DefaultMediaRouteController extends AbstractMediaRouteController {
-
-    /**
-     * Interface for MediaRouter intents result handlers.
-     */
-    protected interface ResultBundleHandler {
-        void onResult(Bundle data);
-
-        void onError(String message, Bundle data);
-    }
-
-    private static final String TAG = "MediaFling";
-
-    private static final String ACTION_RECEIVE_SESSION_STATUS_UPDATE =
-            "com.google.android.apps.chrome.videofling.RECEIVE_SESSION_STATUS_UPDATE";
-    private static final String ACTION_RECEIVE_MEDIA_STATUS_UPDATE =
-            "com.google.android.apps.chrome.videofling.RECEIVE_MEDIA_STATUS_UPDATE";
-    private static final String MIME_TYPE = "video/mp4";
-    private String mCurrentSessionId;
-    private String mCurrentItemId;
-    private boolean mSeeking;
-    private final String mIntentCategory;
-    private PendingIntent mSessionStatusUpdateIntent;
-    private BroadcastReceiver mSessionStatusBroadcastReceiver;
-    private PendingIntent mMediaStatusUpdateIntent;
-    private BroadcastReceiver mMediaStatusBroadcastReceiver;
-
-    private String mPreferredTitle;
-    private long mStartPositionMillis;
-    private final PositionExtrapolator mPositionExtrapolator = new PositionExtrapolator();
-
-    private Uri mLocalVideoUri;
-
-    private int mSessionState = MediaSessionStatus.SESSION_STATE_INVALIDATED;
-
-    private final ApplicationStatus.ApplicationStateListener mApplicationStateListener =
-            new ApplicationStatus.ApplicationStateListener() {
-                @Override
-                public void onApplicationStateChange(int newState) {
-                    // HAS_DESTROYED_ACTIVITIES means all Chrome activities have been destroyed.
-                    if (newState == ApplicationState.HAS_DESTROYED_ACTIVITIES) {
-                        onActivitiesDestroyed();
-                    }
-                }
-            };
-
-    /**
-     * Default and only constructor.
-     */
-    public DefaultMediaRouteController() {
-        mIntentCategory = getContext().getPackageName();
-    }
-
-    @Override
-    public boolean initialize() {
-        if (mediaRouterInitializationFailed()) return false;
-
-        ApplicationStatus.registerApplicationStateListener(mApplicationStateListener);
-
-        if (mSessionStatusUpdateIntent == null) {
-            Intent sessionStatusUpdateIntent = new Intent(ACTION_RECEIVE_SESSION_STATUS_UPDATE);
-            sessionStatusUpdateIntent.addCategory(mIntentCategory);
-            mSessionStatusUpdateIntent = PendingIntent.getBroadcast(getContext(), 0,
-                    sessionStatusUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-        }
-
-        if (mMediaStatusUpdateIntent == null) {
-            Intent mediaStatusUpdateIntent = new Intent(ACTION_RECEIVE_MEDIA_STATUS_UPDATE);
-            mediaStatusUpdateIntent.addCategory(mIntentCategory);
-            mMediaStatusUpdateIntent = PendingIntent.getBroadcast(getContext(), 0,
-                    mediaStatusUpdateIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-        }
-
-        return true;
-    }
-
-    @Override
-    public boolean canPlayMedia(String sourceUrl, String frameUrl) {
-        if (mediaRouterInitializationFailed() || sourceUrl == null) return false;
-
-        try {
-            String scheme = new URI(sourceUrl).getScheme();
-            if (scheme == null) return false;
-            return scheme.equals(UrlConstants.HTTP_SCHEME)
-                    || scheme.equals(UrlConstants.HTTPS_SCHEME);
-        } catch (URISyntaxException e) {
-            return false;
-        }
-    }
-
-    @Override
-    public void setRemoteVolume(int delta) {
-        boolean canChangeRemoteVolume = (getCurrentRoute().getVolumeHandling()
-                == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE);
-        if (currentRouteSupportsRemotePlayback() && canChangeRemoteVolume) {
-            getCurrentRoute().requestUpdateVolume(delta);
-        }
-    }
-
-    @Override
-    public MediaRouteSelector buildMediaRouteSelector() {
-        return new MediaRouteSelector.Builder().addControlCategory(
-                CastMediaControlIntent.categoryForRemotePlayback(getCastReceiverId())).build();
-    }
-
-    protected String getCastReceiverId() {
-        return CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID;
-    }
-
-    @Override
-    public void resume() {
-        if (mCurrentItemId == null) return;
-
-        Intent intent = new Intent(MediaControlIntent.ACTION_RESUME);
-        intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
-        intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mCurrentSessionId);
-        sendIntentToRoute(intent, new ResultBundleHandler() {
-            @Override
-            public void onResult(Bundle data) {
-                processMediaStatusBundle(data);
-            }
-
-            @Override
-            public void onError(String message, Bundle data) {
-                release();
-            }
-        });
-
-        setDisplayedPlayerState(PlayerState.LOADING);
-    }
-
-    @Override
-    public void pause() {
-        if (mCurrentItemId == null) return;
-
-        Intent intent = new Intent(MediaControlIntent.ACTION_PAUSE);
-        intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
-        intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mCurrentSessionId);
-        sendIntentToRoute(intent, new ResultBundleHandler() {
-            @Override
-            public void onResult(Bundle data) {
-                processMediaStatusBundle(data);
-            }
-
-            @Override
-            public void onError(String message, Bundle data) {
-                // Do not release the player just because of a failed pause
-                // request. This can happen when pausing more than once for
-                // example.
-            }
-        });
-
-        // Update the last known position to the current one so that we don't
-        // jump back in time discarding whatever we extrapolated from the last
-        // time the position was updated.
-        mPositionExtrapolator.onPaused();
-        setDisplayedPlayerState(PlayerState.PAUSED);
-    }
-
-    /**
-     * Plays the given Uri on the currently selected player. This will replace any currently playing
-     * video
-     *
-     * @param preferredTitle the preferred title of the current playback session to display
-     * @param startPositionMillis from which to start playing.
-     */
-    private void playUri(@Nullable final String preferredTitle, final long startPositionMillis) {
-        RecordCastAction.castMediaType(MediaUrlResolver.getMediaType(mLocalVideoUri));
-        installBroadcastReceivers();
-
-        // If the session is already started (meaning we are casting a video already), we simply
-        // load the new URL with one ACTION_PLAY intent.
-        if (mCurrentSessionId != null) {
-            Log.d(TAG, "Playing a new url: %s", mLocalVideoUri);
-
-            // We keep the same session so only clear the playing item status.
-            clearItemState();
-            startPlayback(preferredTitle, startPositionMillis);
-            return;
-        }
-
-        Log.d(TAG, "Sending stream to app: %s", getCastReceiverId());
-        Log.d(TAG, "Url: %s", mLocalVideoUri);
-
-        startSession(true, null, new ResultBundleHandler() {
-            @Override
-            public void onResult(Bundle data) {
-                configureNewSession(data);
-
-                mPreferredTitle = preferredTitle;
-                updateTitle(mPreferredTitle);
-                mStartPositionMillis = startPositionMillis;
-                // Make sure we get a session status. If the session becomes active
-                // immediately then the broadcast session status can arrive before we have
-                // the session id, so this ensures we get it whatever happens.
-                getSessionStatus(mCurrentSessionId);
-            }
-
-            @Override
-            public void onError(String message, Bundle data) {
-                release();
-                RecordCastAction.castDefaultPlayerResult(false);
-            }
-        });
-    }
-
-    /**
-     * Send a start session intent.
-     *
-     * @param relaunch Whether we should relaunch the cast application.
-     * @param resultBundleHandler BundleHandler to handle reply.
-     */
-    private void startSession(boolean relaunch, String sessionId,
-            ResultBundleHandler resultBundleHandler) {
-        Intent intent = new Intent(MediaControlIntent.ACTION_START_SESSION);
-        intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
-
-        intent.putExtra(CastMediaControlIntent.EXTRA_CAST_STOP_APPLICATION_WHEN_SESSION_ENDS, true);
-        intent.putExtra(MediaControlIntent.EXTRA_SESSION_STATUS_UPDATE_RECEIVER,
-                mSessionStatusUpdateIntent);
-        intent.putExtra(CastMediaControlIntent.EXTRA_CAST_APPLICATION_ID, getCastReceiverId());
-        intent.putExtra(CastMediaControlIntent.EXTRA_CAST_RELAUNCH_APPLICATION, relaunch);
-        if (sessionId != null) intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sessionId);
-
-        addIntentExtraForDebugLogging(intent);
-        sendIntentToRoute(intent, resultBundleHandler);
-    }
-
-    @RemovableInRelease
-    private void addIntentExtraForDebugLogging(Intent intent) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            intent.putExtra(CastMediaControlIntent.EXTRA_DEBUG_LOGGING_ENABLED, true);
-        }
-    }
-
-    private void getSessionStatus(String sessionId) {
-        Intent intent = new Intent(MediaControlIntent.ACTION_GET_SESSION_STATUS);
-        intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
-
-        intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sessionId);
-
-        sendIntentToRoute(intent, new ResultBundleHandler() {
-            @Override
-            public void onResult(Bundle data) {
-                logBundle("getSessionStatus result :", data);
-                processSessionStatusBundle(data);
-            }
-
-            @Override
-            public void onError(String message, Bundle data) {
-                release();
-            }
-        });
-    }
-
-    private void startPlayback(
-            @Nullable final String preferredTitle, final long startPositionMillis) {
-        setUnprepared();
-        Intent intent = new Intent(MediaControlIntent.ACTION_PLAY);
-        intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
-        intent.setDataAndType(mLocalVideoUri, MIME_TYPE);
-        intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mCurrentSessionId);
-        intent.putExtra(MediaControlIntent.EXTRA_ITEM_STATUS_UPDATE_RECEIVER,
-                mMediaStatusUpdateIntent);
-        intent.putExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, startPositionMillis);
-
-        if (preferredTitle != null) {
-            Bundle metadata = new Bundle();
-            metadata.putString(MediaItemMetadata.KEY_TITLE, preferredTitle);
-            intent.putExtra(MediaControlIntent.EXTRA_ITEM_METADATA, metadata);
-        }
-
-        sendIntentToRoute(intent, new ResultBundleHandler() {
-            @Override
-            public void onResult(Bundle data) {
-                mCurrentItemId = data.getString(MediaControlIntent.EXTRA_ITEM_ID);
-                processMediaStatusBundle(data);
-                RecordCastAction.castDefaultPlayerResult(true);
-            }
-
-            @Override
-            public void onError(String message, Bundle data) {
-                release();
-                RecordCastAction.castDefaultPlayerResult(false);
-            }
-        });
-    }
-
-    @Override
-    public long getPosition() {
-        return mPositionExtrapolator.getPosition();
-    }
-
-    @Override
-    public long getDuration() {
-        return mPositionExtrapolator.getDuration();
-    }
-
-    @Override
-    public void seekTo(long msec) {
-        if (msec == getPosition()) return;
-        // Update the position now since the MRP will update it only once the video is playing
-        // remotely. In particular, if the video is paused, the MRP doesn't send the command until
-        // the video is resumed.
-        mPositionExtrapolator.onSeek(msec);
-        mSeeking = true;
-        Intent intent = new Intent(MediaControlIntent.ACTION_SEEK);
-        intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
-        intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mCurrentSessionId);
-        intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, mCurrentItemId);
-        intent.putExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, msec);
-        sendIntentToRoute(intent, new ResultBundleHandler() {
-            @Override
-            public void onResult(Bundle data) {
-                if (getMediaStateListener() != null) getMediaStateListener().onSeekCompleted();
-                processMediaStatusBundle(data);
-            }
-
-            @Override
-            public void onError(String message, Bundle data) {
-                release();
-            }
-        });
-    }
-
-    @Override
-    public void release() {
-        super.release();
-
-        for (UiListener listener : getUiListeners()) {
-            listener.onRouteUnselected(this);
-        }
-        if (getMediaStateListener() != null) getMediaStateListener().onRouteUnselected();
-        setMediaStateListener(null);
-
-        if (mediaRouterInitializationFailed()) return;
-        if (mCurrentSessionId == null) {
-            // This can happen if we disconnect after a failure (because the
-            // media could not be casted).
-            disconnect();
-            return;
-        }
-
-        Intent stopIntent = new Intent(MediaControlIntent.ACTION_STOP);
-        stopIntent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
-        stopIntent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mCurrentSessionId);
-
-        sendIntentToRoute(stopIntent, new ResultBundleHandler() {
-            @Override
-            public void onResult(Bundle data) {
-                processMediaStatusBundle(data);
-            }
-
-            @Override
-            public void onError(String message, Bundle data) {}
-        });
-
-        Intent endSessionIntent = new Intent(MediaControlIntent.ACTION_END_SESSION);
-        endSessionIntent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
-        endSessionIntent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mCurrentSessionId);
-
-        sendIntentToRoute(endSessionIntent, new ResultBundleHandler() {
-            @Override
-            public void onResult(Bundle data) {
-                logMediaSessionStatus(data);
-
-                for (UiListener listener : getUiListeners()) {
-                    listener.onPlaybackStateChanged(PlayerState.FINISHED);
-                }
-
-                if (getMediaStateListener() != null) {
-                    getMediaStateListener().onPlaybackStateChanged(PlayerState.FINISHED);
-                }
-                recordRemainingTimeUMA();
-                disconnect();
-            }
-
-            @Override
-            public void onError(String message, Bundle data) {
-                disconnect();
-            }
-        });
-    }
-
-    /**
-     * Disconnect from the remote screen without stopping the media playing. use release() for
-     * disconnect + stop.
-     */
-    private void disconnect() {
-        clearStreamState();
-        clearMediaRoute();
-
-        if (mSessionStatusBroadcastReceiver != null) {
-            getContext().unregisterReceiver(mSessionStatusBroadcastReceiver);
-            mSessionStatusBroadcastReceiver = null;
-        }
-        if (mMediaStatusBroadcastReceiver != null) {
-            getContext().unregisterReceiver(mMediaStatusBroadcastReceiver);
-            mMediaStatusBroadcastReceiver = null;
-        }
-        clearConnectionFailureCallback();
-
-        stopWatchingRouteSelection();
-        removeAllListeners();
-    }
-
-    @Override
-    protected void onRouteSelectedEvent(MediaRouter router, RouteInfo route) {
-        Log.d(TAG, "Selected route %s", route);
-        if (!route.isSelected()) return;
-
-        RecordCastAction.castPlayRequested();
-
-        RecordCastAction.remotePlaybackDeviceSelected(RecordCastAction.DeviceType.CAST_GENERIC);
-        installBroadcastReceivers();
-
-        if (getMediaStateListener() == null) {
-            showCastError(route.getName());
-            release();
-            return;
-        }
-
-        if (route != getCurrentRoute()) {
-            registerRoute(route);
-            clearStreamState();
-        }
-        mPositionExtrapolator.clear();
-
-        notifyRouteSelected(route);
-    }
-
-    /*
-     * Although our custom implementation of the disconnect button doesn't need this, it is needed
-     * when the route is released due to, for example, another application stealing the route, or
-     * when we switch to a YouTube video on the same device.
-     */
-    @Override
-    protected void onRouteUnselectedEvent(MediaRouter router, RouteInfo route) {
-        Log.d(TAG, "Unselected route %s", route);
-        // Preserve our best guess as to the final position; this is needed to reset the
-        // local position while switching back to local playback.
-        mPositionExtrapolator.onPaused();
-        if (getCurrentRoute() != null && route.getId().equals(getCurrentRoute().getId())) {
-            clearStreamState();
-        }
-    }
-
-    private void installBroadcastReceivers() {
-        if (mSessionStatusBroadcastReceiver == null) {
-            mSessionStatusBroadcastReceiver = new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    logIntent("Got a session broadcast intent from the MRP: ", intent);
-                    Bundle statusBundle = intent.getExtras();
-
-                    // Ignore null status bundles.
-                    if (statusBundle == null) return;
-
-                    // Ignore the status of old sessions.
-                    String sessionId = statusBundle.getString(MediaControlIntent.EXTRA_SESSION_ID);
-                    if (mCurrentSessionId == null || !mCurrentSessionId.equals(sessionId)) return;
-
-                    processSessionStatusBundle(statusBundle);
-                }
-            };
-            IntentFilter sessionBroadcastIntentFilter =
-                    new IntentFilter(ACTION_RECEIVE_SESSION_STATUS_UPDATE);
-            sessionBroadcastIntentFilter.addCategory(mIntentCategory);
-            getContext().registerReceiver(mSessionStatusBroadcastReceiver,
-                    sessionBroadcastIntentFilter);
-        }
-
-        if (mMediaStatusBroadcastReceiver == null) {
-            mMediaStatusBroadcastReceiver = new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    logIntent("Got a broadcast intent from the MRP: ", intent);
-
-                    processMediaStatusBundle(intent.getExtras());
-                }
-            };
-            IntentFilter mediaBroadcastIntentFilter =
-                    new IntentFilter(ACTION_RECEIVE_MEDIA_STATUS_UPDATE);
-            mediaBroadcastIntentFilter.addCategory(mIntentCategory);
-            getContext().registerReceiver(mMediaStatusBroadcastReceiver,
-                    mediaBroadcastIntentFilter);
-        }
-    }
-
-    /**
-     * Called when the main activity receives an onDestroy() call.
-     */
-    protected void onActivitiesDestroyed() {
-        ApplicationStatus.unregisterApplicationStateListener(mApplicationStateListener);
-        release();
-    }
-
-    /**
-     * Clear the session and the currently playing item (if any).
-     */
-    protected void clearStreamState() {
-        mLocalVideoUri = null;
-        mCurrentSessionId = null;
-        clearItemState();
-    }
-
-    @Override
-    protected void clearItemState() {
-        // Note: do not clear the stream position, since this is still needed so
-        // that we can reset the local stream position to match.
-        super.clearItemState();
-        mCurrentItemId = null;
-        mPositionExtrapolator.clear();
-        mSeeking = false;
-    }
-
-    private void processSessionStatusBundle(Bundle statusBundle) {
-        MediaSessionStatus status = MediaSessionStatus.fromBundle(
-                statusBundle.getBundle(MediaControlIntent.EXTRA_SESSION_STATUS));
-        int sessionState = status.getSessionState();
-
-        // If no change do nothing
-        if (sessionState == mSessionState) return;
-        mSessionState = sessionState;
-
-        switch (sessionState) {
-            case MediaSessionStatus.SESSION_STATE_ACTIVE:
-                if (mLocalVideoUri != null) {
-                    startPlayback(mPreferredTitle, mStartPositionMillis);
-                }
-                break;
-
-            case MediaSessionStatus.SESSION_STATE_ENDED:
-            case MediaSessionStatus.SESSION_STATE_INVALIDATED:
-                for (UiListener listener : getUiListeners()) {
-                    listener.onPlaybackStateChanged(PlayerState.INVALIDATED);
-                }
-                if (getMediaStateListener() != null) {
-                    getMediaStateListener().onPlaybackStateChanged(PlayerState.INVALIDATED);
-                }
-                // Record the remaining time UMA first, otherwise the playback state will be cleared
-                // in release().
-                recordRemainingTimeUMA();
-                // Set the current session id to null so we don't send the stop intent.
-                mCurrentSessionId = null;
-                release();
-                break;
-
-            default:
-                break;
-        }
-    }
-
-    private void processMediaStatusBundle(Bundle statusBundle) {
-        if (statusBundle == null) return;
-        logBundle("processMediaStatusBundle: ", statusBundle);
-
-        String itemId = statusBundle.getString(MediaControlIntent.EXTRA_ITEM_ID);
-        if (itemId == null || !itemId.equals(mCurrentItemId)) return;
-
-        // Extract item metadata, if available.
-        if (statusBundle.containsKey(MediaControlIntent.EXTRA_ITEM_METADATA)) {
-            Bundle metadataBundle =
-                    (Bundle) statusBundle.getParcelable(MediaControlIntent.EXTRA_ITEM_METADATA);
-            updateTitle(metadataBundle.getString(MediaItemMetadata.KEY_TITLE, mPreferredTitle));
-        }
-
-        // Extract the item status, if available.
-        if (statusBundle.containsKey(MediaControlIntent.EXTRA_ITEM_STATUS)) {
-            Bundle itemStatusBundle =
-                    (Bundle) statusBundle.getParcelable(MediaControlIntent.EXTRA_ITEM_STATUS);
-            MediaItemStatus itemStatus = MediaItemStatus.fromBundle(itemStatusBundle);
-
-            logBundle("Received item status: ", itemStatusBundle);
-
-            updateState(itemStatus.getPlaybackState());
-
-            // Update the PositionExtrapolator that the playback state has changed.
-            if (itemStatus.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) {
-                mPositionExtrapolator.onResumed();
-            } else if (itemStatus.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_FINISHED) {
-                mPositionExtrapolator.onFinished();
-            } else {
-                mPositionExtrapolator.onPaused();
-            }
-
-            if ((getRemotePlayerState() == PlayerState.PAUSED)
-                    || (getRemotePlayerState() == PlayerState.PLAYING)
-                    || (getRemotePlayerState() == PlayerState.LOADING)) {
-                this.mCurrentItemId = itemId;
-
-                // duration can possibly be -1 if it's unknown, so cap to 0
-                long duration = Math.max(itemStatus.getContentDuration(), 0);
-                // update the position using the remote player's position
-                // duration can possibly be -1 if it's unknown, so cap to 0
-                long position = Math.min(Math.max(itemStatus.getContentPosition(), 0), duration);
-                // TODO(zqzhang): The GMS core currently uses SystemClock.uptimeMillis() as
-                // timestamp, which does not conform to the MediaRouter support library docs. See
-                // b/28378525 and
-                // http://developer.android.com/reference/android/support/v7/media/MediaItemStatus.html#getTimestamp().
-                // Override the timestamp with elapsedRealtime() by assuming the delay between the
-                // GMS core produces the MediaItemStatus and the code reaches here is short enough.
-                // long timestamp = itemStatus.getTimestamp();
-                long timestamp = SystemClock.elapsedRealtime();
-                notifyDurationUpdated(duration);
-                notifyPositionUpdated(position);
-                mPositionExtrapolator.onPositionInfoUpdated(duration, position, timestamp);
-
-                if (mSeeking) {
-                    mSeeking = false;
-                    if (getMediaStateListener() != null) getMediaStateListener().onSeekCompleted();
-                }
-            }
-            logExtraHttpInfo(itemStatus.getExtras());
-        }
-    }
-
-    /**
-     * Send the given intent to the current route. The result will be returned in the given
-     * ResultBundleHandler. This function will also check to see if the current route can handle the
-     * intent before sending it.
-     *
-     * @param intent the intent to send to the current route.
-     * @param bundleHandler contains the result of sending the intent
-     */
-    private void sendIntentToRoute(final Intent intent, final ResultBundleHandler bundleHandler) {
-        if (getCurrentRoute() == null) {
-            logIntent("sendIntentToRoute ", intent);
-            Log.d(TAG, "The current route is null.");
-            if (bundleHandler != null) bundleHandler.onError(null, null);
-            return;
-        }
-
-        if (!getCurrentRoute().supportsControlRequest(intent)) {
-            logIntent("sendIntentToRoute ", intent);
-            Log.d(TAG, "The intent is not supported by the route: %s", getCurrentRoute());
-            if (bundleHandler != null) bundleHandler.onError(null, null);
-            return;
-        }
-
-        sendControlIntent(intent, bundleHandler);
-    }
-
-    private void sendControlIntent(final Intent intent, final ResultBundleHandler bundleHandler) {
-        Log.d(TAG, "Sending intent to %s %s", getCurrentRoute().getName(),
-                getCurrentRoute().getId());
-        logIntent("sendControlIntent ", intent);
-        if (getCurrentRoute().isDefault()) {
-            Log.d(TAG, "Route is default, not sending");
-            return;
-        }
-
-        getCurrentRoute().sendControlRequest(intent, new MediaRouter.ControlRequestCallback() {
-            @Override
-            public void onResult(Bundle data) {
-                if (data != null && bundleHandler != null) bundleHandler.onResult(data);
-            }
-
-            @Override
-            public void onError(String message, Bundle data) {
-                logControlRequestError(intent, message, data);
-                int errorCode = 0;
-                if (data != null) {
-                    errorCode = data.getInt(CastMediaControlIntent.EXTRA_ERROR_CODE);
-                }
-
-                sendErrorToListeners(errorCode);
-
-                if (bundleHandler != null) bundleHandler.onError(message, data);
-            }
-        });
-    }
-
-    private void notifyDurationUpdated(long durationMillis) {
-        for (UiListener listener : getUiListeners()) {
-            listener.onDurationUpdated(durationMillis);
-        }
-    }
-
-    private void notifyPositionUpdated(long position) {
-        for (UiListener listener : getUiListeners()) {
-            listener.onPositionChanged(position);
-        }
-    }
-
-    private void recordRemainingTimeUMA() {
-        long duration = getDuration();
-        long remainingTime = Math.max(0, duration - getPosition());
-        // Duration has already been cleared.
-        if (getDuration() <= 0) return;
-        RecordCastAction.castEndedTimeRemaining(duration, remainingTime);
-    }
-
-    private String bundleToString(Bundle bundle) {
-        if (bundle == null) return "";
-
-        StringBuilder extras = new StringBuilder();
-        extras.append("[");
-        for (String key : bundle.keySet()) {
-            Object value = bundle.get(key);
-            String valueText = value == null ? "null" : value.toString();
-            if (value instanceof Bundle) valueText = bundleToString((Bundle) value);
-            extras.append(key).append("=").append(valueText).append(",");
-        }
-        extras.append("]");
-        return extras.toString();
-    }
-
-    @Override
-    protected void startCastingVideo() {
-        MediaStateListener listener = getMediaStateListener();
-        if (listener == null) return;
-
-        String url = listener.getSourceUrl();
-
-        Log.d(TAG, "startCastingVideo called, url = %s", url);
-
-        // checkIfPlayableRemotely will have rejected null URLs.
-        assert url != null;
-
-        RecordCastAction.castDomainAndRegistry(listener.getFrameUrl().toString());
-
-        mLocalVideoUri = Uri.parse(url);
-        mStartPositionMillis = listener.getStartPositionMillis();
-        playUri(listener.getTitle(), mStartPositionMillis);
-    }
-
-    private void configureNewSession(Bundle data) {
-        mCurrentSessionId = data.getString(MediaControlIntent.EXTRA_SESSION_ID);
-        mSessionState = MediaSessionStatus.SESSION_STATE_INVALIDATED;
-        Log.d(TAG, "Got a session id: %s", mCurrentSessionId);
-    }
-
-    @Override
-    public void checkIfPlayableRemotely(final String sourceUrl, final String frameUrl,
-            final String cookies, String userAgent, final MediaValidationCallback callback) {
-        new MediaUrlResolver(new MediaUrlResolver.Delegate() {
-
-            @Override
-            public Uri getUri() {
-                return Uri.parse(sourceUrl);
-            }
-
-            @Override
-            public String getCookies() {
-                return cookies;
-            }
-
-            @Override
-            public void deliverResult(Uri uri, boolean playable) {
-                callback.onResult(playable, uri.toString(), frameUrl);
-            }
-
-            // Some webpages have >100 media files, which causes the THREAD_POOL_EXECUTOR to get
-            // flooded with requests and causes a crash. We use SERIAL_EXECUTOR instead, to allow
-            // for an unlimited number of tasks. See https://crbug.com/873941.
-        }, userAgent).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
-    }
-
-    @Override
-    public String getUriPlaying() {
-        if (mLocalVideoUri == null) return null;
-        return mLocalVideoUri.toString();
-    }
-
-    @RemovableInRelease
-    private void logBundle(String message, Bundle bundle) {
-        Log.d(TAG, message + bundleToString(bundle));
-    }
-
-    @RemovableInRelease
-    private void logControlRequestError(Intent intent, String message, Bundle data) {
-        // The intent may contain some PII so we don't want to log it in the released
-        // version by default.
-        Log.e(TAG, String.format(
-                "Error sending control request %s %s. Data: %s Error: %s", intent,
-                bundleToString(intent.getExtras()), bundleToString(data), message));
-    }
-
-    @RemovableInRelease
-    private void logExtraHttpInfo(Bundle extras) {
-        if (extras != null) {
-            if (extras.containsKey(MediaItemStatus.EXTRA_HTTP_STATUS_CODE)) {
-                int httpStatus = extras.getInt(MediaItemStatus.EXTRA_HTTP_STATUS_CODE);
-                Log.d(TAG, "HTTP status: %s", httpStatus);
-            }
-            if (extras.containsKey(MediaItemStatus.EXTRA_HTTP_RESPONSE_HEADERS)) {
-                Bundle headers = extras.getBundle(MediaItemStatus.EXTRA_HTTP_RESPONSE_HEADERS);
-                Log.d(TAG, "HTTP headers: %s", bundleToString(headers));
-            }
-        }
-    }
-
-    @RemovableInRelease
-    private void logIntent(String prefix, Intent intent) {
-        Log.d(TAG, prefix + intent + " extras: " + bundleToString(intent.getExtras()));
-    }
-
-    @RemovableInRelease
-    private void logMediaSessionStatus(Bundle data) {
-        MediaSessionStatus status = MediaSessionStatus.fromBundle(
-                data.getBundle(MediaControlIntent.EXTRA_SESSION_STATUS));
-        int sessionState = status.getSessionState();
-        Log.d(TAG, "Session state after ending session: %s", sessionState);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/ExpandedControllerActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/ExpandedControllerActivity.java
deleted file mode 100644
index 03398725..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/ExpandedControllerActivity.java
+++ /dev/null
@@ -1,364 +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.
-
-package org.chromium.chrome.browser.media.remote;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.text.TextUtils;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.google.android.gms.cast.CastMediaControlIntent;
-
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
-import org.chromium.chrome.browser.metrics.MediaNotificationUma;
-import org.chromium.third_party.android.media.MediaController;
-
-/**
- * The activity that's opened by clicking the video flinging (casting) notification.
- *
- * TODO(aberent): Refactor to merge some common logic with {@link CastNotificationControl}.
- */
-public class ExpandedControllerActivity
-        extends FragmentActivity implements MediaRouteController.UiListener {
-    private static final int PROGRESS_UPDATE_PERIOD_IN_MS = 1000;
-    // The alpha value for the poster/placeholder image, an integer between 0 and 256 (opaque).
-    private static final int POSTER_IMAGE_ALPHA = 200;
-
-    private Handler mHandler;
-    // We don't use the standard android.media.MediaController, but a custom one.
-    // See the class itself for details.
-    private MediaController mMediaController;
-    private FullscreenMediaRouteButton mMediaRouteButton;
-    private MediaRouteController mMediaRouteController;
-    private RemoteVideoInfo mVideoInfo;
-    private String mScreenName;
-
-    /**
-     * Handle actions from on-screen media controls.
-     */
-    private MediaController.Delegate mControllerDelegate = new MediaController.Delegate() {
-        @Override
-        public void play() {
-            if (mMediaRouteController == null) return;
-            mMediaRouteController.resume();
-            RecordCastAction.recordFullscreenControlsAction(
-                    RecordCastAction.FullScreenControls.RESUME,
-                    mMediaRouteController.getMediaStateListener() != null);
-        }
-
-        @Override
-        public void pause() {
-            if (mMediaRouteController == null) return;
-            mMediaRouteController.pause();
-            RecordCastAction.recordFullscreenControlsAction(
-                    RecordCastAction.FullScreenControls.PAUSE,
-                    mMediaRouteController.getMediaStateListener() != null);
-        }
-
-        @Override
-        public long getDuration() {
-            if (mMediaRouteController == null) return 0;
-            return mMediaRouteController.getDuration();
-        }
-
-        @Override
-        public long getPosition() {
-            if (mMediaRouteController == null) return 0;
-            return mMediaRouteController.getPosition();
-        }
-
-        @Override
-        public void seekTo(long pos) {
-            if (mMediaRouteController == null) return;
-            mMediaRouteController.seekTo(pos);
-            RecordCastAction.recordFullscreenControlsAction(
-                    RecordCastAction.FullScreenControls.SEEK,
-                    mMediaRouteController.getMediaStateListener() != null);
-        }
-
-        @Override
-        public boolean isPlaying() {
-            if (mMediaRouteController == null) return false;
-            return mMediaRouteController.isPlaying();
-        }
-
-        @Override
-        public long getActionFlags() {
-            long flags =
-                    PlaybackStateCompat.ACTION_REWIND | PlaybackStateCompat.ACTION_FAST_FORWARD;
-            if (mMediaRouteController != null && mMediaRouteController.isPlaying()) {
-                flags |= PlaybackStateCompat.ACTION_PAUSE;
-            } else {
-                flags |= PlaybackStateCompat.ACTION_PLAY;
-            }
-            return flags;
-        }
-    };
-
-    private Runnable mProgressUpdater = new Runnable() {
-        @Override
-        public void run() {
-            if (mMediaRouteController.isPlaying()) {
-                mMediaController.updateProgress();
-                mHandler.postDelayed(this, PROGRESS_UPDATE_PERIOD_IN_MS);
-            } else {
-                mHandler.removeCallbacks(this);
-            }
-        }
-    };
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        MediaNotificationUma.recordClickSource(getIntent());
-
-        mMediaRouteController =
-                RemoteMediaPlayerController.instance().getCurrentlyPlayingMediaRouteController();
-
-        if (mMediaRouteController == null || mMediaRouteController.routeIsDefaultRoute()) {
-            // We don't want to do anything for the default (local) route
-            finish();
-            return;
-        }
-
-        // Make the activity full screen.
-        requestWindowFeature(Window.FEATURE_NO_TITLE);
-        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
-                WindowManager.LayoutParams.FLAG_FULLSCREEN);
-
-        // requestWindowFeature must be called before adding content.
-        setContentView(R.layout.expanded_cast_controller);
-        mHandler = new Handler();
-
-        ViewGroup rootView = (ViewGroup) findViewById(android.R.id.content);
-        rootView.setBackgroundColor(Color.BLACK);
-
-        mMediaRouteController.addUiListener(this);
-
-        // Create and initialize the media control UI.
-        mMediaController = (MediaController) findViewById(R.id.cast_media_controller);
-        mMediaController.setDelegate(mControllerDelegate);
-
-        View button = getLayoutInflater().inflate(R.layout.cast_controller_media_route_button,
-                rootView, false);
-
-        if (button instanceof FullscreenMediaRouteButton) {
-            mMediaRouteButton = (FullscreenMediaRouteButton) button;
-            rootView.addView(mMediaRouteButton);
-            mMediaRouteButton.bringToFront();
-            mMediaRouteButton.initialize(mMediaRouteController);
-        } else {
-            mMediaRouteButton = null;
-        }
-
-        // Initialize the video info.
-        setVideoInfo(new RemoteVideoInfo(null, 0, RemoteVideoInfo.PlayerState.STOPPED, 0, null));
-
-        mMediaController.refresh();
-
-        scheduleProgressUpdate();
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        if (mVideoInfo.state == PlayerState.FINISHED) finish();
-        if (mMediaRouteController == null) return;
-
-        // Lifetime of the media element is bound to that of the {@link MediaStateListener}
-        // of the {@link MediaRouteController}.
-        RecordCastAction.recordFullscreenControlsShown(
-                mMediaRouteController.getMediaStateListener() != null);
-
-        mMediaRouteController.prepareMediaRoute();
-
-        ImageView iv = (ImageView) findViewById(R.id.cast_background_image);
-        if (iv == null) return;
-        Bitmap posterBitmap = mMediaRouteController.getPoster();
-        if (posterBitmap != null) iv.setImageBitmap(posterBitmap);
-        iv.setImageAlpha(POSTER_IMAGE_ALPHA);
-    }
-
-    @Override
-    protected void onDestroy() {
-        cleanup();
-        super.onDestroy();
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        int keyCode = event.getKeyCode();
-        if ((keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_UP)
-                || mVideoInfo.state == PlayerState.FINISHED) {
-            return super.dispatchKeyEvent(event);
-        }
-
-        return handleVolumeKeyEvent(mMediaRouteController, event);
-    }
-
-    private void cleanup() {
-        if (mHandler != null) mHandler.removeCallbacks(mProgressUpdater);
-        if (mMediaRouteController != null) mMediaRouteController.removeUiListener(this);
-        mMediaRouteController = null;
-        mProgressUpdater = null;
-    }
-
-    /**
-     * Sets the remote's video information to display.
-     */
-    private final void setVideoInfo(RemoteVideoInfo videoInfo) {
-        if ((mVideoInfo == null) ? (videoInfo == null) : mVideoInfo.equals(videoInfo)) return;
-
-        mVideoInfo = videoInfo;
-        onVideoInfoChanged();
-    }
-
-    private void scheduleProgressUpdate() {
-        mHandler.removeCallbacks(mProgressUpdater);
-        if (mMediaRouteController.isPlaying()) {
-            mHandler.post(mProgressUpdater);
-        }
-    }
-
-    /**
-     * Sets the name to display for the device.
-     */
-    private void setScreenName(String screenName) {
-        if (TextUtils.equals(mScreenName, screenName)) return;
-
-        mScreenName = screenName;
-        onScreenNameChanged();
-    }
-
-    private void onVideoInfoChanged() {
-        updateUi();
-    }
-
-    private void onScreenNameChanged() {
-        updateUi();
-    }
-
-    private void updateUi() {
-        if (mMediaController == null || mMediaRouteController == null) return;
-
-        String deviceName = mMediaRouteController.getRouteName();
-        String castText = "";
-        if (deviceName != null) {
-            castText = getResources().getString(R.string.cast_casting_video, deviceName);
-        }
-        TextView castTextView = (TextView) findViewById(R.id.cast_screen_title);
-        castTextView.setText(castText);
-
-        mMediaController.refresh();
-    }
-
-    @Override
-    public void onRouteSelected(String name, MediaRouteController mediaRouteController) {
-        setScreenName(name);
-    }
-
-    @Override
-    public void onRouteUnselected(MediaRouteController mediaRouteController) {
-        finish();
-    }
-
-    @Override
-    public void onPrepared(MediaRouteController mediaRouteController) {
-        // No implementation.
-    }
-
-    @Override
-    public void onError(int error, String message) {
-        if (error == CastMediaControlIntent.ERROR_CODE_SESSION_START_FAILED) finish();
-    }
-
-    @Override
-    public void onPlaybackStateChanged(@PlayerState int newState) {
-        RemoteVideoInfo videoInfo = new RemoteVideoInfo(mVideoInfo);
-        videoInfo.state = newState;
-        setVideoInfo(videoInfo);
-
-        scheduleProgressUpdate();
-
-        if (newState == PlayerState.FINISHED || newState == PlayerState.INVALIDATED) {
-            // If we are switching to a finished state, stop the notifications.
-            finish();
-        }
-    }
-
-    @Override
-    public void onDurationUpdated(long durationMillis) {
-        RemoteVideoInfo videoInfo = new RemoteVideoInfo(mVideoInfo);
-        videoInfo.durationMillis = durationMillis;
-        setVideoInfo(videoInfo);
-    }
-
-    @Override
-    public void onPositionChanged(long positionMillis) {
-        RemoteVideoInfo videoInfo = new RemoteVideoInfo(mVideoInfo);
-        videoInfo.currentTimeMillis = positionMillis;
-        setVideoInfo(videoInfo);
-    }
-
-    @Override
-    public void onTitleChanged(String title) {
-        RemoteVideoInfo videoInfo = new RemoteVideoInfo(mVideoInfo);
-        videoInfo.title = title;
-        setVideoInfo(videoInfo);
-    }
-
-    /**
-     * Modify remote volume by handling volume keys.
-     *
-     * @param controller The remote controller through which the volume will be modified.
-     * @param event The key event. Its keycode needs to be either {@code KEYCODE_VOLUME_DOWN} or
-     *              {@code KEYCODE_VOLUME_UP} otherwise this method will return false.
-     * @return True if the event is handled.
-     */
-    private boolean handleVolumeKeyEvent(MediaRouteController controller, KeyEvent event) {
-        if (!controller.isBeingCast()) return false;
-
-        int action = event.getAction();
-        int keyCode = event.getKeyCode();
-        // Intercept the volume keys to affect only remote volume.
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_VOLUME_DOWN:
-                if (action == KeyEvent.ACTION_DOWN) controller.setRemoteVolume(-1);
-                return true;
-            case KeyEvent.KEYCODE_VOLUME_UP:
-                if (action == KeyEvent.ACTION_DOWN) controller.setRemoteVolume(1);
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    /**
-     * Launches the ExpandedControllerActivity as a new task.
-     *
-     * @param context the Context to start this activity within.
-     */
-    public static void startActivity(Context context) {
-        if (context == null) return;
-
-        Intent intent = new Intent(context, ExpandedControllerActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        context.startActivity(intent);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/FullscreenMediaRouteButton.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/FullscreenMediaRouteButton.java
deleted file mode 100644
index 376859f5..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/FullscreenMediaRouteButton.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 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.
-
-package org.chromium.chrome.browser.media.remote;
-
-import android.content.Context;
-import android.support.v7.app.MediaRouteButton;
-import android.util.AttributeSet;
-import android.view.View;
-
-/**
- * Cast button that wraps around a MediaRouteButton. We show the button only if there are available
- * cast devices.
- */
-public class FullscreenMediaRouteButton extends MediaRouteButton {
-
-    // Are we in the time window when the button should become visible if there're routes?
-    private boolean mVisibilityRequested;
-
-    /**
-     * The constructor invoked when inflating the button.
-     */
-    public FullscreenMediaRouteButton(Context context, AttributeSet attributeSet) {
-        super(context, attributeSet);
-        mVisibilityRequested = false;
-    }
-
-    /**
-     * Set the necessary state for the button to work.
-     */
-    /**
-     * Set the necessary state for the button to work
-     * @param controller the MediaRouteController controlling the route
-     */
-    public void initialize(MediaRouteController controller) {
-        setRouteSelector(controller.buildMediaRouteSelector());
-        setDialogFactory(new MediaRouteControllerDialogFactory(controller.getMediaStateListener()));
-    }
-
-    @Override
-    public void setEnabled(boolean enabled) {
-        // TODO(aberent) not sure if this is still used, and in particular if mVisibilityRequest
-        // is still used.
-
-        // We need to check if the button was in the same state before to avoid doing anything,
-        // but we also need to update the current state for {@link #setButtonVisibility} to work.
-        boolean wasEnabled = isEnabled();
-        super.setEnabled(enabled);
-
-        if (wasEnabled == enabled) return;
-
-        if (enabled && mVisibilityRequested) {
-            setButtonVisibility(View.VISIBLE);
-        } else {
-            setVisibility(View.GONE);
-        }
-    }
-
-    private void setButtonVisibility(int visibility) {
-        // If the button is being set to visible, first make sure that it can even cast
-        // to anything before making it actually visible.
-        if (visibility == View.VISIBLE) {
-            setVisibility(isEnabled() ? View.VISIBLE : View.GONE);
-        } else {
-            setVisibility(visibility);
-        }
-    }
-
-}
-
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaRouteChooserDialogFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaRouteChooserDialogFactory.java
deleted file mode 100644
index ba95e81..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaRouteChooserDialogFactory.java
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v7.app.MediaRouteChooserDialog;
-import android.support.v7.app.MediaRouteChooserDialogFragment;
-import android.support.v7.app.MediaRouteDialogFactory;
-import android.support.v7.media.MediaRouter;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import org.chromium.chrome.browser.media.remote.MediaRouteController.MediaStateListener;
-
-/**
- * The Chrome implementation of the dialog factory so custom behavior can
- * be injected for the disconnect button.
- */
-public class MediaRouteChooserDialogFactory extends MediaRouteDialogFactory {
-
-    private final MediaRouteController mController;
-    private final Context mContext;
-    private final MediaStateListener mPlayer;
-
-    MediaRouteChooserDialogFactory(MediaStateListener player, MediaRouteController controller,
-            Context context) {
-        mPlayer = player;
-        mController = controller;
-        mContext = context;
-    }
-
-    private static class SystemVisibilitySaver {
-        private int mSystemVisibility;
-        private boolean mRestoreSystemVisibility;
-
-        void saveSystemVisibility(Activity activity) {
-            // If we are in fullscreen we may have also have hidden the system UI. This
-            // is overridden when we display the dialog. Save the system UI visibility
-            // state so we can restore it.
-            FrameLayout decor = (FrameLayout) activity.getWindow().getDecorView();
-            mSystemVisibility = decor.getSystemUiVisibility();
-            mRestoreSystemVisibility = (
-                    (mSystemVisibility & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0);
-        }
-
-        void restoreSystemVisibility(Activity activity) {
-            if (mRestoreSystemVisibility) {
-                FrameLayout decor = (FrameLayout) activity.getWindow().getDecorView();
-                // In some cases we come out of fullscreen before closing this dialog. In these
-                // cases we don't want to restore the system UI visibility state.
-                int systemVisibility = decor.getSystemUiVisibility();
-                if ((systemVisibility & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
-                    decor.setSystemUiVisibility(mSystemVisibility);
-                }
-            }
-        }
-    }
-
-    /**
-     * A dialog fragment for choosing a media route that saves system visibility for handling
-     * fullscreen state of Chrome correctly. Needs to be a named public static class, see
-     * https://crbug.com/618993.
-     */
-    public static final class Fragment extends MediaRouteChooserDialogFragment {
-        final Handler mHandler = new Handler();
-        final MediaRouteController mController;
-        final MediaStateListener mPlayer;
-        final SystemVisibilitySaver mVisibilitySaver = new SystemVisibilitySaver();
-        boolean mCancelled;
-        Context mContext;
-
-        // The class has to be a public static class with a zero-argument constructor.
-        // Since we can't pass any callbacks to the fragment easily, just close the dialog.
-        // See https://crbug.com/618993.
-        public Fragment() {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    Fragment.this.dismiss();
-                }
-            });
-            mController = null;
-            mPlayer = null;
-        }
-
-        @SuppressLint("ValidFragment")
-        Fragment(MediaRouteController controller, MediaStateListener player) {
-            mController = controller;
-            mPlayer = player;
-        }
-
-        @Override
-        public MediaRouteChooserDialog onCreateChooserDialog(
-                Context context, Bundle savedInstanceState) {
-            mVisibilitySaver.saveSystemVisibility(getActivity());
-            mContext = context;
-            return new MediaRouteChooserDialog(context) {
-                @Override
-                // Returns true if the route should be shown to the user.
-                public boolean onFilterRoute(MediaRouter.RouteInfo route) {
-                    return super.onFilterRoute(route) && !mController.shouldFilterOutRoute(route);
-                }
-            };
-        }
-
-        @Override
-        public void onStop() {
-            super.onStop();
-            mVisibilitySaver.restoreSystemVisibility(getActivity());
-        }
-
-        @Override
-        public void onCancel(DialogInterface dialog) {
-            mCancelled = true;
-
-            super.onCancel(dialog);
-        }
-
-        @Override
-        public void onDismiss(DialogInterface dialog) {
-            super.onDismiss(dialog);
-
-            if (mCancelled) {
-                if (mPlayer != null) mPlayer.onRouteDialogCancelled();
-                return;
-            }
-
-            if (mController != null) {
-                MediaRouter router = MediaRouter.getInstance(mContext);
-                mController.onRouteSelected(mPlayer, router, router.getSelectedRoute());
-            }
-        }
-    }
-
-    @Override
-    public MediaRouteChooserDialogFragment onCreateChooserDialogFragment() {
-        return new Fragment(mController, mPlayer);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaRouteController.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaRouteController.java
deleted file mode 100644
index e06071fc..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaRouteController.java
+++ /dev/null
@@ -1,370 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-
-import android.graphics.Bitmap;
-import android.support.v7.media.MediaRouteSelector;
-import android.support.v7.media.MediaRouter;
-import android.support.v7.media.MediaRouter.RouteInfo;
-
-import org.chromium.base.VisibleForTesting;
-import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
-
-/**
- * Each MediaRouteController controls the routes to devices which support remote playback of
- * particular categories of Media elements (e.g. all YouTube media elements, all media elements
- * with simple http source URLs). The MediaRouteController is responsible for configuring
- * and controlling remote playback of the media elements it supports.
- */
-public interface MediaRouteController {
-    /**
-     * Listener for events that are relevant to the state of the media and the media controls
-     */
-    public interface MediaStateListener {
-        /**
-         * Called when the first route becomes available, or the last route
-         * is removed.
-         * @param available whether routes are available.
-         */
-        void onRouteAvailabilityChanged(boolean available);
-
-        /**
-         * Called when the {@link MediaRouteChooserDialog} is closed with no device selected.
-         */
-        void onRouteDialogCancelled();
-
-        /**
-         * Called when an error is detected by the media route controller
-         */
-        void onError();
-
-        /**
-         * Called when a seek completes on the current route
-         */
-        void onSeekCompleted();
-
-        /**
-         * Called when the current route is unselected
-         */
-        void onRouteUnselected();
-
-        /**
-         * Called when the playback state changes (e.g. from Playing to Paused)
-         * @param newState the new playback state
-         */
-        void onPlaybackStateChanged(@PlayerState int newState);
-
-        String getTitle();
-
-        Bitmap getPosterBitmap();
-
-        void pauseLocal();
-
-        long getLocalPosition();
-
-        /**
-         * Tells the rest of Chrome that we are starting to cast, so that user inputs control cast
-         * in place of local playback
-         */
-        void onCastStarting(String routeName);
-
-        /**
-         * Tells the rest of Chrome that we're connected to the Cast device and about to resume
-         * playback.
-         */
-        void onCastStarted();
-
-        /**
-         * Tells the rest of Chrome that we are no longer casting the video.
-         */
-        void onCastStopping();
-
-        /**
-         * @return the source URL
-         */
-        String getSourceUrl();
-
-        /**
-         * @return the Cookies
-         */
-        String getCookies();
-
-        /**
-         * @return the frame URL
-         */
-        String getFrameUrl();
-
-        /**
-         * @return the start position
-         */
-        long getStartPositionMillis();
-
-        /**
-         * @return true if the user has pressed the pause button (or requested pause some other way)
-         */
-        boolean isPauseRequested();
-
-        /**
-         * @return true if the user has requested a seek
-         */
-        boolean isSeekRequested();
-
-        /**
-         * @return the requested seek location. Only meaningful if isSeekRequested is true.
-         */
-        long getSeekLocation();
-    }
-
-    /**
-     * Listener for events that are relevant to the Browser UI.
-     */
-    public interface UiListener {
-
-        /**
-         * Called when a new route is selected
-         * @param name the name of the new route
-         * @param mediaRouteController the controller that selected the route
-         */
-        void onRouteSelected(String name, MediaRouteController mediaRouteController);
-
-        /**
-         * Called when the current route is unselected
-         * @param mediaRouteController the controller that had the route.
-         */
-        void onRouteUnselected(MediaRouteController mediaRouteController);
-
-        /**
-         * Called when the current route is ready to be used
-         * @param mediaRouteController the controller that has the route.
-         */
-        void onPrepared(MediaRouteController mediaRouteController);
-
-        /**
-         * Called when an error is detected by the controller
-         * @param errorType One of the error types from CastMediaControlIntent
-         * @param message The message for the error
-         */
-        void onError(int errorType, String message);
-
-        /**
-         * Called when the Playback state has changed (e.g. from playing to paused)
-         * @param newState the new state
-         */
-        void onPlaybackStateChanged(@PlayerState int newState);
-
-        /**
-         * Called when the duration of the currently playing video changes.
-         * @param durationMillis the new duration in ms.
-         */
-        void onDurationUpdated(long durationMillis);
-
-        /**
-         * Called when the media route controller receives new information about the
-         * current position in the video.
-         * @param positionMillis the current position in the video in ms.
-         */
-        void onPositionChanged(long positionMillis);
-
-        /**
-         * Called if the title of the video changes
-         * @param title the new title
-         */
-        void onTitleChanged(String title);
-    }
-
-    /**
-     * Interface for returning the result of checking whether the media element is playable
-     * remotely.
-     */
-    static interface MediaValidationCallback {
-        /**
-         * Function to deliver the result
-         * @param isPlayable true if the media element is playable, false if not
-         * @param revisedSourceUrl The source url to send to the remote device
-         * @param revisedFrameUrl The frame url to send to the remote device
-         */
-        void onResult(boolean isPlayable, String revisedSourceUrl, String revisedFrameUrl);
-    }
-    /**
-     * Scan routes, and set up the MediaRouter object. This is called at every time we need to reset
-     * the state. Because of that, this function is idempotent. If that changes in the future, where
-     * this function gets called needs to be re-evaluated.
-     *
-     * @return false if device doesn't support cast, true otherwise.
-     */
-    boolean initialize();
-
-    /**
-     * Can this mediaRouteController handle a media element?
-     * @param sourceUrl the source
-     * @param frameUrl
-     * @return true if it can, false if it can't.
-     */
-    boolean canPlayMedia(String sourceUrl, String frameUrl);
-
-    /**
-     * @return A new MediaRouteSelector filtering the remote playback devices from all the routes.
-     */
-    MediaRouteSelector buildMediaRouteSelector();
-
-    /**
-     * @return Whether there're remote playback devices available.
-     */
-    boolean isRemotePlaybackAvailable();
-
-    /**
-     * @return Whether the currently selected device supports remote playback
-     */
-    boolean currentRouteSupportsRemotePlayback();
-
-    /**
-     * Setup this object to discover new routes and register the necessary players.
-     */
-    void prepareMediaRoute();
-
-    /**
-     * Add a Listener that will listen to events from this object
-     *
-     * @param listener the Listener that will receive the events
-     */
-    void addUiListener(UiListener listener);
-
-    /**
-     * Removes a Listener from this object
-     *
-     * @param listener the Listener to remove
-     */
-    void removeUiListener(UiListener listener);
-
-    /**
-     * @return The currently selected route's friendly name, or null if there is none selected
-     */
-    String getRouteName();
-
-    /**
-     * @return true if this is currently using the default route, false if not.
-     */
-    boolean routeIsDefaultRoute();
-
-    /**
-     * Sets the remote volume of the current route.
-     *
-     * @param delta The delta value in arbitrary "Android Volume Units".
-     */
-    void setRemoteVolume(int delta);
-
-    /**
-     * Resume paused playback of the current video.
-     */
-    void resume();
-
-    /**
-     * Pauses the currently playing video if any.
-     */
-    void pause();
-
-    /**
-     * Returns the current remote playback position. Estimates the current position by using the
-     * last known position and the current time.
-     *
-     *  TODO(avayvod): Send periodic status update requests to update the position once in several
-     * seconds or so.
-     *
-     * @return The current position of the remote playback in milliseconds.
-     */
-    long getPosition();
-
-    /**
-     * @return The stream duration in milliseconds.
-     */
-    long getDuration();
-
-    /**
-     * @return Whether the video is currently being played.
-     */
-    boolean isPlaying();
-
-    /**
-     * @return Whether the video is being cast (any of playing/paused/loading/stopped).
-     */
-    boolean isBeingCast();
-
-    /**
-     * Initiates a seek request for the remote playback device to the specified position.
-     *
-     * @param msec The position to seek to, in milliseconds.
-     */
-    void seekTo(long msec);
-
-    /**
-     * Stop the current remote playback completely and release all resources.
-     */
-    void release();
-
-    /**
-     * @param player - the current player using this media route controller.
-     */
-    void setMediaStateListener(MediaStateListener listener);
-
-    /**
-     * @return the current VideoStateListener
-     */
-    MediaStateListener getMediaStateListener();
-
-    @VisibleForTesting
-    @PlayerState
-    int getDisplayedPlayerState();
-
-    /**
-     * Remove an existing media state listener
-     * @param listener
-     */
-    void removeMediaStateListener(MediaStateListener listener);
-
-    /**
-     * Add a media state listener
-     * @param listener
-     */
-    void addMediaStateListener(MediaStateListener listener);
-
-    /**
-     * Get the poster for the video, if any
-     * @return the poster bitmap, or Null.
-     */
-    Bitmap getPoster();
-
-    /**
-     * Used to filter out routes in the MediaRouteChooserDialog.
-     * Overridden in some downstream code.
-     * @param route the route to be filtered.
-     * @return true if the route should be excluded, false if it should be shown to the user.
-     */
-    boolean shouldFilterOutRoute(MediaRouter.RouteInfo route);
-
-    /**
-     * Called when a new route has been selected
-     * @param player The player {@link MediaStateListener} that initiated the connection
-     * @param router The MediaRouter.
-     * @param route The selected route.
-     */
-    void onRouteSelected(MediaStateListener player, MediaRouter router, RouteInfo route);
-
-    /**
-     * Potentially asynchronous check of whether the media element is playable on remote players.
-     * @param sourceUrl the URL of the media element
-     * @param frameUrl the URL of the frame
-     * @param cookies the cookies for the media element
-     * @param userAgent the user agent
-     * @param callback the callback through which the result will be returned. The callback will be
-     *                 called either from within the call, or later on the UI thread.
-     */
-    void checkIfPlayableRemotely(String sourceUrl, String frameUrl, String cookies,
-            String userAgent, MediaValidationCallback callback);
-
-    /**
-     * @return The Uri of the currently playing video
-     */
-    @VisibleForTesting String getUriPlaying();
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaRouteControllerDialogFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaRouteControllerDialogFactory.java
deleted file mode 100644
index 2b9ac35..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaRouteControllerDialogFactory.java
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v7.app.MediaRouteControllerDialog;
-import android.support.v7.app.MediaRouteControllerDialogFragment;
-import android.support.v7.app.MediaRouteDialogFactory;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import org.chromium.base.Log;
-import org.chromium.chrome.browser.media.remote.MediaRouteController.MediaStateListener;
-
-/**
- * The Chrome implementation of the dialog factory so custom behavior can
- * be injected for the disconnect button.
- */
-public class MediaRouteControllerDialogFactory extends MediaRouteDialogFactory {
-    private static final String TAG = "MRCtrlDlg";
-
-    private final MediaStateListener mPlayer;
-
-    MediaRouteControllerDialogFactory(MediaStateListener player) {
-        mPlayer = player;
-    }
-
-    private static class SystemVisibilitySaver {
-        private int mSystemVisibility;
-        private boolean mRestoreSystemVisibility;
-
-        void saveSystemVisibility(Activity activity) {
-            // If we are in fullscreen we may have also have hidden the system UI. This
-            // is overridden when we display the dialog. Save the system UI visibility
-            // state so we can restore it.
-            FrameLayout decor = (FrameLayout) activity.getWindow().getDecorView();
-            mSystemVisibility = decor.getSystemUiVisibility();
-            mRestoreSystemVisibility = (
-                    (mSystemVisibility & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0);
-        }
-
-        void restoreSystemVisibility(Activity activity) {
-            if (mRestoreSystemVisibility) {
-                FrameLayout decor = (FrameLayout) activity.getWindow().getDecorView();
-                // In some cases we come out of fullscreen before closing this dialog. In these
-                // cases we don't want to restore the system UI visibility state.
-                int systemVisibility = decor.getSystemUiVisibility();
-                if ((systemVisibility & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
-                    decor.setSystemUiVisibility(mSystemVisibility);
-                }
-            }
-        }
-    }
-
-    /**
-     * A dialog fragment for controlling a media route that saves system visibility for
-     * handling fullscreen state of Chrome correctly. Needs to be a named public static class,
-     * see https://crbug.com/618993.
-     */
-    public static final class Fragment extends MediaRouteControllerDialogFragment {
-        final Handler mHandler = new Handler();
-        final SystemVisibilitySaver mVisibilitySaver = new SystemVisibilitySaver();
-        final MediaStateListener mPlayer;
-
-        // The class has to be a public static class with a zero-argument constructor.
-        // Since we can't pass any callbacks to the fragment easily, just close the dialog.
-        // See https://crbug.com/618993.
-        public Fragment() {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    Fragment.this.dismiss();
-                }
-            });
-            mPlayer = null;
-        }
-
-        @SuppressLint("ValidFragment")
-        Fragment(MediaStateListener player) {
-            mPlayer = player;
-        }
-
-        @Override
-        public Dialog onCreateDialog(Bundle saved) {
-            mVisibilitySaver.saveSystemVisibility(getActivity());
-            return new MediaRouteControllerDialog(getActivity());
-        }
-
-        @Override
-        public void onStop() {
-            super.onStop();
-            mVisibilitySaver.restoreSystemVisibility(getActivity());
-        }
-
-        @Override
-        public void onDismiss(DialogInterface dialog) {
-            Log.d(TAG, "onDismiss " + mPlayer);
-            super.onDismiss(dialog);
-            if (mPlayer != null) mPlayer.onRouteDialogCancelled();
-        }
-    }
-
-    @Override
-    public MediaRouteControllerDialogFragment onCreateControllerDialogFragment() {
-        return new Fragment(mPlayer);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaUrlResolver.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaUrlResolver.java
deleted file mode 100644
index ff91142..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaUrlResolver.java
+++ /dev/null
@@ -1,288 +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.
-
-package org.chromium.chrome.browser.media.remote;
-
-import android.net.Uri;
-import android.support.annotation.IntDef;
-import android.text.TextUtils;
-
-import org.chromium.base.Log;
-import org.chromium.base.VisibleForTesting;
-import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.base.task.AsyncTask;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.net.HttpURLConnection;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.net.URLStreamHandler;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-/**
- * Resolves the final URL if it's a redirect. Works asynchronously, uses HTTP
- * HEAD request to determine if the URL is redirected.
- */
-public class MediaUrlResolver extends AsyncTask<MediaUrlResolver.Result> {
-    // Cast.Sender.UrlResolveResult UMA histogram values; must match values of
-    // RemotePlaybackUrlResolveResult in histograms.xml. Do not change these values, as they are
-    // being used in UMA.
-    @IntDef({ResolveResult.SUCCESS, ResolveResult.MALFORMED_URL, ResolveResult.NO_CORS,
-            ResolveResult.INCOMPATIBLE_CORS, ResolveResult.SERVER_ERROR,
-            ResolveResult.NETWORK_ERROR, ResolveResult.UNSUPPORTED_MEDIA,
-            ResolveResult.HUC_EXCEPTION})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ResolveResult {
-        int SUCCESS = 0;
-        int MALFORMED_URL = 1;
-        int NO_CORS = 2;
-        int INCOMPATIBLE_CORS = 3;
-        int SERVER_ERROR = 4;
-        int NETWORK_ERROR = 5;
-        int UNSUPPORTED_MEDIA = 6;
-        int HUC_EXCEPTION = 7;
-        int NUM_ENTRIES = 8;
-    }
-
-    // Acceptal response codes for URL resolving request.
-    private static final Integer[] SUCCESS_RESPONSE_CODES = {
-        // Request succeeded.
-        HttpURLConnection.HTTP_OK,
-        HttpURLConnection.HTTP_PARTIAL,
-
-        // HttpURLConnection only follows up to 5 redirects, this response is unlikely but possible.
-        HttpURLConnection.HTTP_MOVED_PERM,
-        HttpURLConnection.HTTP_MOVED_TEMP,
-    };
-
-    /**
-     * The interface to get the initial URI with cookies from and pass the final
-     * URI to.
-     */
-    public interface Delegate {
-        /**
-         * @return the original URL to resolve.
-         */
-        Uri getUri();
-
-        /**
-         * @return the cookies to fetch the URL with.
-         */
-        String getCookies();
-
-        /**
-         * Passes the resolved URL to the delegate.
-         *
-         * @param uri the resolved URL.
-         */
-        void deliverResult(Uri uri, boolean palyable);
-    }
-
-
-    protected static final class Result {
-        private final Uri mUri;
-        private final boolean mPlayable;
-
-        public Result(Uri uri, boolean playable) {
-            mUri = uri;
-            mPlayable = playable;
-        }
-
-        public Uri getUri() {
-            return mUri;
-        }
-
-        public boolean isPlayable() {
-            return mPlayable;
-        }
-    }
-
-    private static final String TAG = "MediaFling";
-
-    private static final String COOKIES_HEADER_NAME = "Cookies";
-    private static final String USER_AGENT_HEADER_NAME = "User-Agent";
-    private static final String ORIGIN_HEADER_NAME = "Origin";
-    private static final String RANGE_HEADER_NAME = "Range";
-    private static final String CORS_HEADER_NAME = "Access-Control-Allow-Origin";
-
-    private static final String CHROMECAST_ORIGIN = "https://www.gstatic.com";
-
-    // Media types supported for cast, see
-    // media/base/container_names.h for the actual enum where these are defined.
-    // See https://developers.google.com/cast/docs/media#media-container-formats for the formats
-    // supported by Cast devices.
-    @IntDef({MediaType.UNKNOWN, MediaType.AAC, MediaType.HLS, MediaType.MP3, MediaType.MPEG4,
-            MediaType.OGG, MediaType.WAV, MediaType.WEBM, MediaType.DASH, MediaType.SMOOTHSTREAM})
-    @Retention(RetentionPolicy.SOURCE)
-    private @interface MediaType {
-        int UNKNOWN = 0;
-        int AAC = 1;
-        int HLS = 22;
-        int MP3 = 26;
-        int MPEG4 = 29;
-        int OGG = 30;
-        int WAV = 35;
-        int WEBM = 36;
-        int DASH = 38;
-        int SMOOTHSTREAM = 39;
-    }
-
-    // We don't want to necessarily fetch the whole video but we don't want to miss the CORS header.
-    // Assume that 64k should be more than enough to keep all the headers.
-    private static final String RANGE_HEADER_VALUE = "bytes=0-65536";
-
-    private final Delegate mDelegate;
-
-    private final String mUserAgent;
-    private final URLStreamHandler mStreamHandler;
-
-    /**
-     * The constructor
-     * @param delegate The customer for this URL resolver.
-     * @param userAgent The browser user agent
-     */
-    public MediaUrlResolver(Delegate delegate, String userAgent) {
-        this(delegate, userAgent, null);
-    }
-
-    @VisibleForTesting
-    MediaUrlResolver(Delegate delegate, String userAgent, URLStreamHandler streamHandler) {
-        mDelegate = delegate;
-        mUserAgent = userAgent;
-        mStreamHandler = streamHandler;
-    }
-
-    @Override
-    protected MediaUrlResolver.Result doInBackground() {
-        Uri uri = mDelegate.getUri();
-        if (uri == null || uri.equals(Uri.EMPTY)) {
-            return new MediaUrlResolver.Result(Uri.EMPTY, false);
-        }
-        String cookies = mDelegate.getCookies();
-
-        Map<String, List<String>> headers = null;
-        HttpURLConnection urlConnection = null;
-        try {
-            URL requestUrl = new URL(null, uri.toString(), mStreamHandler);
-            urlConnection = (HttpURLConnection) requestUrl.openConnection();
-            if (!TextUtils.isEmpty(cookies)) {
-                urlConnection.setRequestProperty(COOKIES_HEADER_NAME, cookies);
-            }
-
-            // Pretend that this is coming from the Chromecast.
-            urlConnection.setRequestProperty(ORIGIN_HEADER_NAME, CHROMECAST_ORIGIN);
-            urlConnection.setRequestProperty(USER_AGENT_HEADER_NAME, mUserAgent);
-            if (!isEnhancedMedia(uri)) {
-                // Manifest files are typically smaller than 64K so range request can fail.
-                urlConnection.setRequestProperty(RANGE_HEADER_NAME, RANGE_HEADER_VALUE);
-            }
-
-            // This triggers resolving the URL and receiving the headers.
-            headers = urlConnection.getHeaderFields();
-
-            uri = Uri.parse(urlConnection.getURL().toString());
-
-            // If server's response is not valid, don't try to fling the video.
-            int responseCode = urlConnection.getResponseCode();
-            if (!Arrays.asList(SUCCESS_RESPONSE_CODES).contains(responseCode)) {
-                recordResultHistogram(ResolveResult.SERVER_ERROR);
-                Log.e(TAG, "Server response is not valid: %d", responseCode);
-                uri = Uri.EMPTY;
-            }
-        } catch (IOException | IllegalArgumentException e) {
-            // IllegalArgumentException for SSL issue (https://b/78588631).
-            recordResultHistogram(ResolveResult.NETWORK_ERROR);
-            Log.e(TAG, "Failed to fetch the final url", e);
-            uri = Uri.EMPTY;
-        } catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
-            recordResultHistogram(ResolveResult.HUC_EXCEPTION);
-            Log.e(TAG, "Threading issue with HUC, see https://crbug.com/754480", e);
-            uri = Uri.EMPTY;
-        } catch (RuntimeException e) {
-            if (e.getCause() instanceof URISyntaxException) {
-                Log.e(TAG, "Invalid URL format", e);
-                uri = Uri.EMPTY;
-            } else {
-                throw e;
-            }
-        } finally {
-            if (urlConnection != null) urlConnection.disconnect();
-        }
-        return new MediaUrlResolver.Result(uri, canPlayMedia(uri, headers));
-    }
-
-    @Override
-    protected void onPostExecute(MediaUrlResolver.Result result) {
-        mDelegate.deliverResult(result.getUri(), result.isPlayable());
-    }
-
-    private boolean canPlayMedia(Uri uri, Map<String, List<String>> headers) {
-        if (uri == null || uri.equals(Uri.EMPTY)) {
-            recordResultHistogram(ResolveResult.MALFORMED_URL);
-            return false;
-        }
-
-        if (headers != null && headers.containsKey(CORS_HEADER_NAME)) {
-            // Check that the CORS data is valid for Chromecast
-            List<String> corsData = headers.get(CORS_HEADER_NAME);
-            if (corsData.isEmpty() || (!corsData.get(0).equals("*")
-                    && !corsData.get(0).equals(CHROMECAST_ORIGIN))) {
-                recordResultHistogram(ResolveResult.INCOMPATIBLE_CORS);
-                return false;
-            }
-        } else if (isEnhancedMedia(uri)) {
-            // HLS media requires CORS headers.
-            // TODO(avayvod): it actually requires CORS on the final video URLs vs the manifest.
-            // Clank assumes that if CORS is set for the manifest it's set for everything but
-            // it not necessary always true. See b/19138712
-            Log.d(TAG, "HLS stream without CORS header: %s", uri);
-            recordResultHistogram(ResolveResult.NO_CORS);
-            return false;
-        }
-
-        if (getMediaType(uri) == MediaType.UNKNOWN) {
-            Log.d(TAG, "Unsupported media container format: %s", uri);
-            recordResultHistogram(ResolveResult.UNSUPPORTED_MEDIA);
-            return false;
-        }
-
-        recordResultHistogram(ResolveResult.SUCCESS);
-        return true;
-    }
-
-    private boolean isEnhancedMedia(Uri uri) {
-        int mediaType = getMediaType(uri);
-        return mediaType == MediaType.HLS || mediaType == MediaType.DASH
-                || mediaType == MediaType.SMOOTHSTREAM;
-    }
-
-    @VisibleForTesting
-    void recordResultHistogram(@ResolveResult int result) {
-        RecordHistogram.recordEnumeratedHistogram(
-                "Cast.Sender.UrlResolveResult", result, ResolveResult.NUM_ENTRIES);
-    }
-
-    static @MediaType int getMediaType(Uri uri) {
-        String path = uri.getPath();
-
-        if (path == null) return MediaType.UNKNOWN;
-
-        path = path.toLowerCase(Locale.US);
-        if (path.endsWith(".m3u8")) return MediaType.HLS;
-        if (path.endsWith(".mp4")) return MediaType.MPEG4;
-        if (path.endsWith(".mpd")) return MediaType.DASH;
-        if (path.endsWith(".ism")) return MediaType.SMOOTHSTREAM;
-        if (path.endsWith(".m4a") || path.endsWith(".aac")) return MediaType.AAC;
-        if (path.endsWith(".mp3")) return MediaType.MP3;
-        if (path.endsWith(".wav")) return MediaType.WAV;
-        if (path.endsWith(".webm")) return MediaType.WEBM;
-        if (path.endsWith(".ogg")) return MediaType.OGG;
-        return MediaType.UNKNOWN;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/PositionExtrapolator.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/PositionExtrapolator.java
deleted file mode 100644
index 455401b..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/PositionExtrapolator.java
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-
-import android.os.SystemClock;
-
-/**
- * Class for extrapolating current playback position. The class occasionally receives updated
- * playback position information from the MediaRoute, and extrapolates the current playback
- * position.
- */
-public class PositionExtrapolator {
-    private static final String TAG = "MediaFling";
-
-    private long mDuration;
-    private long mLastKnownPosition;
-    private long mTimestamp;
-    private boolean mIsPlaying;
-
-    public PositionExtrapolator() {
-        mDuration = 0;
-        mLastKnownPosition = 0;
-        mTimestamp = 0;
-        mIsPlaying = false;
-    }
-
-    /**
-     * Clears the duration and timestamp.
-     */
-    public void clear() {
-        // Note: do not clear the stream position, since this is still needed so
-        // that we can reset the local stream position to match.
-        mDuration = 0;
-        mTimestamp = 0;
-    }
-
-    /**
-     * Update the extrapolator with the latest position info.
-     * @param duration The new duration.
-     * @param position The new playback position.
-     * @param timestamp The time stamp of this info, must be directly or indirectly aquired via
-     * {@link SystemClock.elapsedRealtime()}. The time stamp from the Cast receiver uses
-     * elapsedRealtime, so it can be used here. Don't use {@link SystemClock.uptimeMillis()} since
-     * it doesn't include the device sleep time.
-     */
-    public void onPositionInfoUpdated(
-            long duration, long position, long timestamp) {
-        mDuration = Math.max(duration, 0);
-        mLastKnownPosition = Math.min(mDuration, Math.max(position, 0));
-        mTimestamp = timestamp;
-    }
-
-    /**
-     * Must be called whenever the remote playback is paused. If the playback
-     * state changes from playing to paused, the last known stream position will be updated by the
-     * extrapolated value.
-     */
-    public void onPaused() {
-        if (!mIsPlaying) return;
-
-        long elapsedTime = SystemClock.elapsedRealtime();
-        onPositionInfoUpdated(mDuration, getPositionWithElapsedTime(elapsedTime), elapsedTime);
-
-        mIsPlaying = false;
-    }
-
-    /**
-     * Must be called whenever the remote playback resumes. If the playback state changes from
-     * paused to playing, the timestamp will be updated by the current {@link
-     * SystemClock.elapsedRealtime()}.
-     */
-    public void onResumed() {
-        if (mIsPlaying) return;
-
-        onPositionInfoUpdated(mDuration, mLastKnownPosition, SystemClock.elapsedRealtime());
-        mIsPlaying = true;
-    }
-
-    /**
-     * Must be called whenever the remote playback is finished. The last known position will be
-     * updated by the duration.
-     */
-    public void onFinished() {
-        onPositionInfoUpdated(mDuration, mDuration, SystemClock.elapsedRealtime());
-        mIsPlaying = false;
-    }
-
-    /**
-     * Must be called whenever the remote playback is seeking. The last known position will be
-     * updated by the position to be seeked to, and the extrapolator will assume the playback is
-     * paused during seeking.
-     * @param position The position to seek to.
-     */
-    public void onSeek(long position) {
-        onPositionInfoUpdated(mDuration, position, SystemClock.elapsedRealtime());
-        mIsPlaying = false;
-    }
-
-    /**
-     * @return The current playback position.
-     */
-    public long getPosition() {
-        return getPositionWithElapsedTime(SystemClock.elapsedRealtime());
-    }
-
-    /**
-     * @return The duration of the remote media.
-     */
-    public long getDuration() {
-        return mDuration;
-    }
-
-    private long getPositionWithElapsedTime(long elapsedTime) {
-        if ((mTimestamp == 0) || !mIsPlaying || mLastKnownPosition >= mDuration) {
-            return mLastKnownPosition;
-        }
-
-        return Math.min(mLastKnownPosition + (elapsedTime - mTimestamp), mDuration);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteMediaPlayerController.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteMediaPlayerController.java
deleted file mode 100644
index 2008e75..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteMediaPlayerController.java
+++ /dev/null
@@ -1,346 +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.
-
-package org.chromium.chrome.browser.media.remote;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.graphics.Bitmap;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-import android.support.v7.app.MediaRouteChooserDialogFragment;
-import android.support.v7.app.MediaRouteControllerDialogFragment;
-import android.support.v7.app.MediaRouteDialogFactory;
-
-import com.google.android.gms.cast.CastMediaControlIntent;
-
-import org.chromium.base.ApplicationStatus;
-import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.VisibleForTesting;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.media.remote.MediaRouteController.MediaStateListener;
-import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
-import org.chromium.ui.widget.Toast;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * The singleton responsible managing the global resources for remote media playback (cast)
- */
-public class RemoteMediaPlayerController implements MediaRouteController.UiListener {
-    // Singleton instance of the class. May only be accessed from UI thread.
-    @SuppressLint("StaticFieldLeak")
-    private static RemoteMediaPlayerController sInstance;
-
-    private static final String TAG = "MediaFling";
-
-    private static final String DEFAULT_CASTING_MESSAGE = "Casting to Chromecast";
-
-    private CastNotificationControl mNotificationControl;
-
-    private Context mCastContextApplicationContext;
-    // The Activity that was in the foreground when the video was cast.
-    private WeakReference<Activity> mChromeVideoActivity;
-
-    private List<MediaRouteController> mMediaRouteControllers;
-
-    // points to mDefaultRouteSelector, mYouTubeRouteSelector or null
-    private MediaRouteController mCurrentRouteController;
-
-    // This is a key for meta-data in the package manifest.
-    private static final String REMOTE_MEDIA_PLAYERS_KEY =
-            "org.chromium.content.browser.REMOTE_MEDIA_PLAYERS";
-
-    /**
-     * The private constructor to make sure the object is only created by the instance() method.
-     */
-    private RemoteMediaPlayerController() {
-        mChromeVideoActivity = new WeakReference<Activity>(null);
-        mMediaRouteControllers = new ArrayList<MediaRouteController>();
-    }
-
-    /**
-     * @return The poster image for the currently playing remote video, null if there's none.
-     */
-    public Bitmap getPoster() {
-        if (mCurrentRouteController == null) return null;
-        return mCurrentRouteController.getPoster();
-    }
-
-    /**
-     * The singleton instance access method for native objects. Must be called on the UI thread
-     * only.
-     */
-    public static RemoteMediaPlayerController instance() {
-        ThreadUtils.assertOnUiThread();
-
-        if (sInstance == null) sInstance = new RemoteMediaPlayerController();
-        if (sInstance.mChromeVideoActivity.get() == null) sInstance.linkToBrowserActivity();
-
-        return sInstance;
-    }
-
-    /**
-     * Gets the MediaRouteController for a video, creating it if necessary.
-     * @param frameUrl The Url of the frame containing the video
-     * @return the MediaRouteController, or null if none.
-     */
-    public MediaRouteController getMediaRouteController(String sourceUrl, String frameUrl) {
-        for (MediaRouteController controller: mMediaRouteControllers) {
-            if (controller.canPlayMedia(sourceUrl, frameUrl)) {
-                return controller;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Gets the default MediaRouteController, creating it if necessary.
-     * @return the default MediaRouteController.
-     */
-    public List<MediaRouteController> getMediaRouteControllers() {
-        return mMediaRouteControllers;
-    }
-
-    /**
-     * Links this object to the Activity that owns the video, if it exists.
-     *
-     */
-    private void linkToBrowserActivity() {
-
-        Activity currentActivity = ApplicationStatus.getLastTrackedFocusedActivity();
-        if (currentActivity != null) {
-            mChromeVideoActivity = new WeakReference<Activity>(currentActivity);
-
-            mCastContextApplicationContext = currentActivity.getApplicationContext();
-            createMediaRouteControllers(currentActivity);
-        }
-    }
-
-    /**
-     * Create the mediaRouteControllers
-     * @param context - the current Android Context
-     */
-    public void createMediaRouteControllers(Context context) {
-        // We only need to do this once
-        if (!mMediaRouteControllers.isEmpty()) return;
-        try {
-            ApplicationInfo ai = context.getPackageManager().getApplicationInfo(
-                    context.getPackageName(), PackageManager.GET_META_DATA);
-            Bundle bundle = ai.metaData;
-            String classNameString = bundle.getString(REMOTE_MEDIA_PLAYERS_KEY);
-
-            if (classNameString != null) {
-                String[] classNames = classNameString.split(",");
-                for (String className : classNames) {
-                    Log.d(TAG, "Adding remote media route controller %s", className.trim());
-                    Class<?> mediaRouteControllerClass = Class.forName(className.trim());
-                    Object mediaRouteController = mediaRouteControllerClass.newInstance();
-                    mMediaRouteControllers.add((MediaRouteController) mediaRouteController);
-                }
-            }
-        } catch (NameNotFoundException | ClassNotFoundException | SecurityException
-                | InstantiationException | IllegalAccessException | IllegalArgumentException e) {
-            // Should never happen, implies corrupt AndroidManifest
-            Log.e(TAG, "Couldn't instatiate MediaRouteControllers", e);
-            assert false;
-        }
-    }
-
-    private void onStateReset(MediaRouteController controller) {
-
-        if (!controller.initialize()) return;
-
-        mNotificationControl = CastNotificationControl.getOrCreate(
-                mChromeVideoActivity.get(), controller);
-        mNotificationControl.setPosterBitmap(getPoster());
-        controller.prepareMediaRoute();
-
-        controller.addUiListener(this);
-    }
-
-    /**
-     * Called when a lower layer requests that a video be cast. This will typically be a request
-     * from Blink when the cast button is pressed on the default video controls.
-     * @param player the player for which cast is being requested
-     * @param frameUrl the URL of the frame containing the video, needed for YouTube videos
-     */
-    public void requestRemotePlayback(
-            MediaRouteController.MediaStateListener player, MediaRouteController controller) {
-        Activity currentActivity = ApplicationStatus.getLastTrackedFocusedActivity();
-        mChromeVideoActivity = new WeakReference<Activity>(currentActivity);
-
-        if (mCurrentRouteController != null && controller != mCurrentRouteController) {
-            mCurrentRouteController.release();
-        }
-
-        onStateReset(controller);
-        showMediaRouteDialog(player, controller, currentActivity);
-
-    }
-
-    /**
-     * Called when a lower layer requests control of a video that is being cast.
-     * @param player The player for which remote playback control is being requested.
-     */
-    public void requestRemotePlaybackControl(MediaRouteController.MediaStateListener player) {
-        // Player should match currently remotely played item, but there
-        // can be a race between various
-        // ways that the a video can stop playing remotely. Check that the
-        // player is current, and ignore if not.
-
-        if (mCurrentRouteController == null) return;
-        if (mCurrentRouteController.getMediaStateListener() != player) return;
-
-        showMediaRouteControlDialog(player, ApplicationStatus.getLastTrackedFocusedActivity());
-    }
-
-    /**
-     * Called when a lower layer requests to stop casting the video.
-     * @param player The player to stop remote playback for.
-     */
-    public void requestRemotePlaybackStop(MediaRouteController.MediaStateListener player) {
-        if (mCurrentRouteController == null) return;
-        if (mCurrentRouteController.getMediaStateListener() != player) return;
-
-        mCurrentRouteController.release();
-    }
-
-    private void showMediaRouteDialog(MediaStateListener player, MediaRouteController controller,
-            Activity activity) {
-
-        FragmentManager fm = ((FragmentActivity) activity).getSupportFragmentManager();
-        if (fm == null) {
-            throw new IllegalStateException("The activity must be a subclass of FragmentActivity");
-        }
-
-        MediaRouteDialogFactory factory = new MediaRouteChooserDialogFactory(player, controller,
-                activity);
-
-        if (fm.findFragmentByTag(
-                "android.support.v7.mediarouter:MediaRouteChooserDialogFragment") != null) {
-            Log.w(TAG, "showDialog(): Route chooser dialog already showing!");
-            return;
-        }
-        MediaRouteChooserDialogFragment f = factory.onCreateChooserDialogFragment();
-
-        f.setRouteSelector(controller.buildMediaRouteSelector());
-        f.show(fm, "android.support.v7.mediarouter:MediaRouteChooserDialogFragment");
-    }
-
-    private void showMediaRouteControlDialog(MediaStateListener player, Activity activity) {
-        FragmentManager fm = ((FragmentActivity) activity).getSupportFragmentManager();
-        if (fm == null) {
-            throw new IllegalStateException("The activity must be a subclass of FragmentActivity");
-        }
-        MediaRouteDialogFactory factory = new MediaRouteControllerDialogFactory(player);
-
-        if (fm.findFragmentByTag(
-                "android.support.v7.mediarouter:MediaRouteControllerDialogFragment") != null) {
-            Log.w(TAG, "showDialog(): Route controller dialog already showing!");
-            return;
-        }
-        MediaRouteControllerDialogFragment f = factory.onCreateControllerDialogFragment();
-
-        f.show(fm, "android.support.v7.mediarouter:MediaRouteControllerDialogFragment");
-    }
-
-    /**
-     * @return the currently playing MediaRouteController
-     */
-    public MediaRouteController getCurrentlyPlayingMediaRouteController() {
-        return mCurrentRouteController;
-    }
-
-    /**
-     * Set the current MediaRouteController
-     * @param controller the controller
-     */
-    public void setCurrentMediaRouteController(MediaRouteController controller) {
-        mCurrentRouteController = controller;
-    }
-
-    private CastNotificationControl getNotificationControl() {
-        return mNotificationControl;
-    }
-
-    @Override
-    public void onPrepared(MediaRouteController mediaRouteController) {
-    }
-
-    @Override
-    public void onPlaybackStateChanged(@PlayerState int newState) {}
-
-    @Override
-    public void onError(int error, String errorMessage) {
-        if (error == CastMediaControlIntent.ERROR_CODE_SESSION_START_FAILED) {
-            showMessageToast(errorMessage);
-        }
-    }
-
-    @Override
-    public void onDurationUpdated(long durationMillis) {}
-
-    @Override
-    public void onPositionChanged(long positionMillis) {}
-
-    @Override
-    public void onTitleChanged(String title) {}
-
-    @Override
-    public void onRouteSelected(String routeName, MediaRouteController mediaRouteController) {
-        if (mCurrentRouteController != mediaRouteController) {
-            mCurrentRouteController = mediaRouteController;
-            resetPlayingVideo();
-        }
-    }
-
-    /**
-     * Gets some text to tell the user that the video is being cast.
-     * @param routeName The name of the route on which the video is being cast.
-     * @return A String to be shown to the user.
-     */
-    public String getCastingMessage(String routeName) {
-        String castingMessage = DEFAULT_CASTING_MESSAGE;
-        if (mCastContextApplicationContext != null) {
-            castingMessage = mCastContextApplicationContext.getString(
-                    R.string.cast_casting_video, routeName);
-        }
-        return castingMessage;
-    }
-
-    // Note that, after switching MediaRouteControllers onRouteUnselected may be called for
-    // the old media route controller, so this should not do anything to
-    // mCurrentRouteController
-    @Override
-    public void onRouteUnselected(MediaRouteController mediaRouteController) {
-        if (mediaRouteController == mCurrentRouteController) {
-            mCurrentRouteController = null;
-        }
-    }
-
-    private void showMessageToast(String message) {
-        Toast toast = Toast.makeText(mCastContextApplicationContext, message, Toast.LENGTH_SHORT);
-        toast.show();
-    }
-
-    private void resetPlayingVideo() {
-        if (mNotificationControl != null) {
-            mNotificationControl.setRouteController(mCurrentRouteController);
-        }
-    }
-
-    @VisibleForTesting
-    static RemoteMediaPlayerController getIfExists() {
-        return sInstance;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteMediaPlayerWrapper.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteMediaPlayerWrapper.java
deleted file mode 100644
index 3c1695be..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteMediaPlayerWrapper.java
+++ /dev/null
@@ -1,334 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-
-import com.google.android.gms.cast.Cast;
-import com.google.android.gms.cast.CastDevice;
-import com.google.android.gms.cast.MediaInfo;
-import com.google.android.gms.cast.MediaStatus;
-import com.google.android.gms.cast.RemoteMediaPlayer;
-import com.google.android.gms.cast.RemoteMediaPlayer.MediaChannelResult;
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.common.api.ResultCallback;
-import com.google.android.gms.common.api.Status;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.chromium.base.Log;
-import org.chromium.chrome.browser.media.router.CastSessionUtil;
-import org.chromium.chrome.browser.media.router.FlingingController;
-import org.chromium.chrome.browser.media.router.MediaController;
-import org.chromium.chrome.browser.media.router.MediaStatusBridge;
-import org.chromium.chrome.browser.media.router.MediaStatusObserver;
-import org.chromium.chrome.browser.media.ui.MediaNotificationInfo;
-import org.chromium.chrome.browser.media.ui.MediaNotificationManager;
-
-import java.util.Locale;
-import java.util.Random;
-
-/**
- * A wrapper around a RemoteMediaPlayer that exposes simple playback commands without the
- * the complexities of the GMS cast calls.
- */
-public class RemoteMediaPlayerWrapper implements RemoteMediaPlayer.OnMetadataUpdatedListener,
-                                                 RemoteMediaPlayer.OnStatusUpdatedListener,
-                                                 ResultCallback<MediaChannelResult>,
-                                                 MediaController, FlingingController {
-    private static final String TAG = "MediaRemoting";
-
-    private final CastDevice mCastDevice;
-    private final String mMediaUrl;
-
-    private GoogleApiClient mApiClient;
-    private RemoteMediaPlayer mMediaPlayer;
-    private MediaNotificationInfo.Builder mNotificationBuilder;
-    private MediaStatusObserver mMediaStatusObserver;
-
-    private Random mRequestIdGenerator = new Random();
-    private long mMediaSessionId;
-    private boolean mPendingSeek;
-    private long mPendingSeekTime;
-
-    private boolean mLoaded;
-
-    public RemoteMediaPlayerWrapper(GoogleApiClient apiClient,
-            MediaNotificationInfo.Builder notificationBuilder, CastDevice castDevice,
-            String mediaUrl) {
-        mApiClient = apiClient;
-        mCastDevice = castDevice;
-        mMediaUrl = mediaUrl;
-        mLoaded = false;
-        mNotificationBuilder = notificationBuilder;
-
-        mMediaPlayer = new RemoteMediaPlayer();
-        mMediaPlayer.setOnStatusUpdatedListener(this);
-        mMediaPlayer.setOnMetadataUpdatedListener(this);
-
-        updateNotificationMetadata();
-    }
-
-    private void updateNotificationMetadata() {
-        CastSessionUtil.setNotificationMetadata(mNotificationBuilder, mCastDevice, mMediaPlayer);
-        MediaNotificationManager.show(mNotificationBuilder.build());
-    }
-
-    private boolean canSendCommand() {
-        return mApiClient != null && mMediaPlayer != null && mApiClient.isConnected();
-    }
-
-    // RemoteMediaPlayer.OnStatusUpdatedListener implementation.
-    @Override
-    public void onStatusUpdated() {
-        MediaStatus mediaStatus = mMediaPlayer.getMediaStatus();
-        if (mediaStatus == null) return;
-
-        if (mMediaStatusObserver != null) {
-            mMediaStatusObserver.onMediaStatusUpdate(new MediaStatusBridge(mediaStatus));
-        }
-
-        int playerState = mediaStatus.getPlayerState();
-        if (playerState == MediaStatus.PLAYER_STATE_PAUSED
-                || playerState == MediaStatus.PLAYER_STATE_PLAYING) {
-            mNotificationBuilder.setPaused(playerState != MediaStatus.PLAYER_STATE_PLAYING);
-            mNotificationBuilder.setActions(
-                    MediaNotificationInfo.ACTION_STOP | MediaNotificationInfo.ACTION_PLAY_PAUSE);
-        } else {
-            mNotificationBuilder.setActions(MediaNotificationInfo.ACTION_STOP);
-        }
-        MediaNotificationManager.show(mNotificationBuilder.build());
-    }
-
-    // RemoteMediaPlayer.OnMetadataUpdatedListener implementation.
-    @Override
-    public void onMetadataUpdated() {
-        updateNotificationMetadata();
-    }
-
-    /**
-     *  Opportunistically tries to update |mMediaSessionId|.
-     *  TODO(https://crbug.com/918644): Remove this code when no longer needed.
-     */
-    private void updateMediaSessionId(String message) {
-        try {
-            JSONObject jsonMessage = new JSONObject(message);
-            String messageType = jsonMessage.getString("type");
-
-            if ("MEDIA_STATUS".equals(messageType)) {
-                JSONArray statusArray = jsonMessage.getJSONArray("status");
-                JSONObject status = statusArray.getJSONObject(0);
-
-                mMediaSessionId = status.getLong("mediaSessionId");
-            }
-        } catch (JSONException e) {
-            // Ignore the exception. We are only looking to opportunistically capture the
-            // mediaSessionId.
-        }
-    }
-
-    /**
-     * Forwards the message to the underlying RemoteMediaPlayer.
-     */
-    public void onMediaMessage(String message) {
-        if (mMediaPlayer == null) return;
-
-        // Needed to send manual seek messages.
-        updateMediaSessionId(message);
-
-        try {
-            mMediaPlayer.onMessageReceived(mCastDevice, CastSessionUtil.MEDIA_NAMESPACE, message);
-        } catch (IllegalStateException e) {
-            // GMS throws with "Result already set" when receiving responses from multiple API calls
-            // in a short amount of time, before results can be read. See https://crbug.com/853923.
-        }
-    }
-
-    /**
-     * Starts loading the media URL, from the given position.
-     */
-    public void load(long startTime) {
-        if (!canSendCommand()) return;
-
-        mLoaded = true;
-
-        MediaInfo.Builder mediaInfoBuilder =
-                new MediaInfo.Builder(mMediaUrl).setContentType("*/*").setStreamType(
-                        MediaInfo.STREAM_TYPE_BUFFERED);
-
-        mMediaPlayer.load(mApiClient, mediaInfoBuilder.build(), /* autoplay */ true, startTime)
-                .setResultCallback(this);
-    }
-
-    /**
-     * Starts playback. No-op if are not in a valid state.
-     * Doesn't verify the command's success/failure.
-     */
-    @Override
-    public void play() {
-        if (!canSendCommand()) return;
-
-        if (!mLoaded) {
-            load(/* startTime */ 0);
-            return;
-        }
-
-        try {
-            mMediaPlayer.play(mApiClient).setResultCallback(this);
-        } catch (IllegalStateException e) {
-            // GMS throws with message "Result already set" when making multiple API calls
-            // in a short amount of time, before results can be read. See https://crbug.com/853923.
-        }
-    }
-
-    /**
-     * Pauses playback. No-op if are not in a valid state.
-     * Doesn't verify the command's success/failure.
-     */
-    @Override
-    public void pause() {
-        if (!canSendCommand()) return;
-
-        try {
-            mMediaPlayer.pause(mApiClient).setResultCallback(this);
-        } catch (IllegalStateException e) {
-            // GMS throws with message "Result already set" when making multiple API calls
-            // in a short amount of time, before results can be read. See https://crbug.com/853923.
-        }
-    }
-
-    /**
-     * Sets the mute state. Does not affect the stream volume.
-     * No-op if are not in a valid state. Doesn't verify the command's success/failure.
-     */
-    @Override
-    public void setMute(boolean mute) {
-        if (!canSendCommand()) return;
-
-        try {
-            mMediaPlayer.setStreamMute(mApiClient, mute).setResultCallback(this);
-        } catch (IllegalStateException e) {
-            // GMS throws with message "Result already set" when making multiple API calls
-            // in a short amount of time, before results can be read. See https://crbug.com/853923.
-        }
-    }
-
-    /**
-     * Sets the stream volume. Does not affect the mute state.
-     * No-op if are not in a valid state. Doesn't verify the command's success/failure.
-     */
-    @Override
-    public void setVolume(double volume) {
-        if (!canSendCommand()) return;
-
-        try {
-            mMediaPlayer.setStreamVolume(mApiClient, volume).setResultCallback(this);
-        } catch (IllegalStateException e) {
-            // GMS throws with message "Result already set" when making multiple API calls
-            // in a short amount of time, before results can be read. See https://crbug.com/853923.
-        }
-    }
-
-    private String createManualSeekMessage(long seekTimeMillis) {
-        // The request ID does not matter for RemotePlayback.
-        int requestId = mRequestIdGenerator.nextInt(10000);
-        String message = String.format(Locale.ROOT,
-                "{\"requestId\":%d,\"type\":\"SEEK\",\"mediaSessionId\":%d,\"currentTime\":%.3f}",
-                requestId, mMediaSessionId, (double) seekTimeMillis / 1000);
-        return message;
-    }
-
-    // TODO(https://crbug.com/918644) Remove this code when it is no longer needed.
-    private void sendManualSeek(long position) {
-        mPendingSeekTime = position;
-        mPendingSeek = true;
-
-        final String message = createManualSeekMessage(position);
-        Cast.CastApi.sendMessage(mApiClient, CastSessionUtil.MEDIA_NAMESPACE, message)
-                .setResultCallback(new ResultCallback<Status>() {
-                    @Override
-                    public void onResult(Status result) {
-                        mPendingSeek = false;
-
-                        if (!result.isSuccess()) {
-                            Log.e(TAG, "Error when sending manual seek. Status code: %d",
-                                    result.getStatusCode());
-                        }
-                    }
-                });
-    }
-
-    /**
-     * Seeks to the given position (in milliseconds).
-     * No-op if are not in a valid state. Doesn't verify the command's success/failure.
-     */
-    @Override
-    public void seek(long position) {
-        if (!canSendCommand()) return;
-
-        if (!mLoaded) {
-            load(position);
-            return;
-        }
-
-        // We send a hand crafted message to the receiver rather than using the RemoteMediaPlayer,
-        // due to a crash in GMS code if ever a request times out. See https://crbug.com/876247 and
-        // https://crbug.com/914072.
-        try {
-            sendManualSeek(position);
-        } catch (IllegalStateException e) {
-            // GMS throws with message "Result already set" when making multiple API calls
-            // in a short amount of time, before results can be read. See https://crbug.com/853923.
-        }
-    }
-
-    /**
-     * Called when the session has stopped, and we should no longer send commands.
-     */
-    public void clearApiClient() {
-        mApiClient = null;
-    }
-
-    // ResultCallback<MediaChannelResult> implementation
-    @Override
-    public void onResult(MediaChannelResult result) {
-        // When multiple API calls are made in quick succession, "Results have already been set"
-        // IllegalStateExceptions might be thrown from GMS code. We prefer to catch the exception
-        // and noop it, than to crash. This might lead to some API calls never getting their
-        // onResult() called, so we should not rely on onResult() being called for every API call.
-        // See https://crbug.com/853923.
-        if (!result.getStatus().isSuccess()) {
-            Log.e(TAG, "Error when sending command. Status code: %d",
-                    result.getStatus().getStatusCode());
-        }
-    }
-
-    // FlingingController implementation
-    @Override
-    public MediaController getMediaController() {
-        return this;
-    }
-
-    @Override
-    public long getApproximateCurrentTime() {
-        // media::Pipeline's seek completes before we get a MediaStatus message with an
-        // updated timestamp. This gives the appearance of the media time jumping around right after
-        // a seek. Send |mPendingSeekTime| when seeking, to prevent media time from being clamped
-        // when seeking backwards.
-        return mPendingSeek ? mPendingSeekTime : mMediaPlayer.getApproximateStreamPosition();
-    }
-
-    @Override
-    public void setMediaStatusObserver(MediaStatusObserver observer) {
-        assert mMediaStatusObserver == null;
-        mMediaStatusObserver = observer;
-    }
-
-    @Override
-    public void clearMediaStatusObserver() {
-        assert mMediaStatusObserver != null;
-        mMediaStatusObserver = null;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteVideoInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteVideoInfo.java
deleted file mode 100644
index f4030cf..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/RemoteVideoInfo.java
+++ /dev/null
@@ -1,113 +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.
-
-package org.chromium.chrome.browser.media.remote;
-
-import android.support.annotation.IntDef;
-import android.text.TextUtils;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Exposes information about the current video to the external clients.
- */
-public class RemoteVideoInfo {
-    /**
-     * This lists all the states that the remote video can be in.
-     */
-    @IntDef({PlayerState.STOPPED, PlayerState.LOADING, PlayerState.PLAYING, PlayerState.PAUSED,
-            PlayerState.ERROR, PlayerState.INVALIDATED, PlayerState.FINISHED})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface PlayerState {
-        /** The remote player is currently stopped. */
-        int STOPPED = 0;
-        /** The remote player is buffering this video. */
-        int LOADING = 1;
-        /** The remote player is playing this video. */
-        int PLAYING = 2;
-        /** The remote player is paused. */
-        int PAUSED = 3;
-        /** The remote player is in an error state. */
-        int ERROR = 4;
-        /** The remote player has been replaced by another player (so the current session has
-         * finished) */
-        int INVALIDATED = 5;
-        /** The remote video has completed playing. */
-        int FINISHED = 6;
-    }
-
-    /**
-     * The title of the video
-     */
-    public String title;
-    /**
-     * The duration of the video
-     */
-    public long durationMillis;
-    /**
-     * The current state of the video
-     */
-    public @PlayerState int state;
-    /**
-     * The last known position in the video
-     */
-    public long currentTimeMillis;
-    /**
-     * The current error message, if any
-     */
-    // TODO(aberent) At present nothing sets this to anything other than Null.
-    public String errorMessage;
-
-    /**
-     * Create a new RemoteVideoInfo
-     * @param title
-     * @param durationMillis
-     * @param state
-     * @param currentTimeMillis
-     * @param errorMessage
-     */
-    public RemoteVideoInfo(String title, long durationMillis, @PlayerState int state,
-            long currentTimeMillis, String errorMessage) {
-        this.title = title;
-        this.durationMillis = durationMillis;
-        this.state = state;
-        this.currentTimeMillis = currentTimeMillis;
-        this.errorMessage = errorMessage;
-    }
-
-    /**
-     * Copy a remote video info
-     * @param other the source.
-     */
-    public RemoteVideoInfo(RemoteVideoInfo other) {
-        this(other.title, other.durationMillis, other.state, other.currentTimeMillis,
-                other.errorMessage);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) return true;
-        if (!(obj instanceof RemoteVideoInfo)) return false;
-
-        RemoteVideoInfo other = (RemoteVideoInfo) obj;
-        return durationMillis == other.durationMillis
-               && currentTimeMillis == other.currentTimeMillis
-               && state == other.state
-               && TextUtils.equals(title, other.title)
-               && TextUtils.equals(errorMessage, other.errorMessage);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = (int) durationMillis;
-        result = 31 * result + (int) (durationMillis >> 32);
-        result = 31 * result + (int) currentTimeMillis;
-        result = 31 * result + (int) (currentTimeMillis >> 32);
-        result = 31 * result + (title == null ? 0 : title.hashCode());
-        result = 31 * result + state;
-        result = 31 * result + (errorMessage == null ? 0 : errorMessage.hashCode());
-        return result;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouter.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouter.java
index 57872ee3..e7ae381 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouter.java
@@ -18,11 +18,8 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.chrome.browser.AppHooks;
-import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.media.router.caf.CafMediaRouteProvider;
 import org.chromium.chrome.browser.media.router.caf.remoting.CafRemotingMediaRouteProvider;
-import org.chromium.chrome.browser.media.router.cast.CastMediaRouteProvider;
-import org.chromium.chrome.browser.media.router.cast.remoting.RemotingMediaRouteProvider;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -43,33 +40,19 @@
             new MediaRouteProvider.Factory() {
                 @Override
                 public void addProviders(MediaRouteManager manager) {
-                    // Bypass feature check in tests as ChromeFeatureList might not be initialized
-                    // yet.
-                    if (ChromeFeatureList.isInitialized()
-                            && ChromeFeatureList.isEnabled(
-                                       ChromeFeatureList.CAF_MEDIA_ROUTER_IMPL)) {
-                        int googleApiAvailabilityResult =
-                                AppHooks.get().isGoogleApiAvailableWithMinApkVersion(
-                                        MIN_GOOGLE_PLAY_SERVICES_APK_VERSION);
-                        if (googleApiAvailabilityResult != ConnectionResult.SUCCESS) {
-                            GoogleApiAvailability.getInstance().showErrorNotification(
-                                    ContextUtils.getApplicationContext(),
-                                    googleApiAvailabilityResult);
-                            return;
-                        }
-                        MediaRouteProvider cafProvider = CafMediaRouteProvider.create(manager);
-                        manager.addMediaRouteProvider(cafProvider);
-                        MediaRouteProvider remotingProvider =
-                                CafRemotingMediaRouteProvider.create(manager);
-                        manager.addMediaRouteProvider(remotingProvider);
-                    } else {
-                        MediaRouteProvider castProvider = CastMediaRouteProvider.create(manager);
-                        manager.addMediaRouteProvider(castProvider);
-
-                        MediaRouteProvider remotingProvider =
-                                RemotingMediaRouteProvider.create(manager);
-                        manager.addMediaRouteProvider(remotingProvider);
+                    int googleApiAvailabilityResult =
+                            AppHooks.get().isGoogleApiAvailableWithMinApkVersion(
+                                    MIN_GOOGLE_PLAY_SERVICES_APK_VERSION);
+                    if (googleApiAvailabilityResult != ConnectionResult.SUCCESS) {
+                        GoogleApiAvailability.getInstance().showErrorNotification(
+                                ContextUtils.getApplicationContext(), googleApiAvailabilityResult);
+                        return;
                     }
+                    MediaRouteProvider cafProvider = CafMediaRouteProvider.create(manager);
+                    manager.addMediaRouteProvider(cafProvider);
+                    MediaRouteProvider remotingProvider =
+                            CafRemotingMediaRouteProvider.create(manager);
+                    manager.addMediaRouteProvider(remotingProvider);
                 }
             };
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouterDialogController.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouterDialogController.java
index 61f9462..420a541 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouterDialogController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouterDialogController.java
@@ -10,8 +10,8 @@
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
-import org.chromium.chrome.browser.media.router.cast.CastMediaSource;
-import org.chromium.chrome.browser.media.router.cast.remoting.RemotingMediaSource;
+import org.chromium.chrome.browser.media.router.caf.CastMediaSource;
+import org.chromium.chrome.browser.media.router.caf.remoting.RemotingMediaSource;
 
 /**
  * Implements the JNI interface called from the C++ Media Router dialog controller implementation
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/CafMediaRouteProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/CafMediaRouteProvider.java
index 9c1d501..2801659 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/CafMediaRouteProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/CafMediaRouteProvider.java
@@ -21,7 +21,6 @@
 import org.chromium.chrome.browser.media.router.MediaRouteProvider;
 import org.chromium.chrome.browser.media.router.MediaSink;
 import org.chromium.chrome.browser.media.router.MediaSource;
-import org.chromium.chrome.browser.media.router.cast.CastMediaSource;
 
 import java.util.HashMap;
 import java.util.Map;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/CafMessageHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/CafMessageHandler.java
index 2e8349e..e17f61a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/CafMessageHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/CafMessageHandler.java
@@ -25,7 +25,6 @@
 import org.chromium.chrome.browser.media.router.CastSessionUtil;
 import org.chromium.chrome.browser.media.router.ClientRecord;
 import org.chromium.chrome.browser.media.router.MediaSink;
-import org.chromium.chrome.browser.media.router.cast.CastMediaSource;
 
 import java.io.IOException;
 import java.util.ArrayDeque;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastMediaSource.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/CastMediaSource.java
similarity index 98%
rename from chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastMediaSource.java
rename to chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/CastMediaSource.java
index 2f3abda..3947f24 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastMediaSource.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/CastMediaSource.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.media.router.cast;
+package org.chromium.chrome.browser.media.router.caf;
 
 import android.net.Uri;
 import android.support.annotation.Nullable;
@@ -18,7 +18,6 @@
 /**
  * Abstracts parsing the Cast application id and other parameters from the source ID.
  */
-// Reused in CafMRP. No need to migrate. See https://crbug.com/711860.
 public class CastMediaSource implements MediaSource {
     public static final String AUTOJOIN_CUSTOM_CONTROLLER_SCOPED = "custom_controller_scoped";
     public static final String AUTOJOIN_TAB_AND_ORIGIN_SCOPED = "tab_and_origin_scoped";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/CafExpandedControllerActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/CafExpandedControllerActivity.java
index f45de12a..0954fd4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/CafExpandedControllerActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/CafExpandedControllerActivity.java
@@ -117,13 +117,10 @@
 
         // requestWindowFeature must be called before adding content.
         setContentView(R.layout.expanded_cast_controller);
-        // mHandler = new Handler();
 
         ViewGroup rootView = (ViewGroup) findViewById(android.R.id.content);
         rootView.setBackgroundColor(Color.BLACK);
 
-        // mMediaRouteController.addUiListener(this);
-
         // Create and initialize the media control UI.
         mMediaController = (MediaController) findViewById(R.id.cast_media_controller);
         mMediaController.setDelegate(mControllerDelegate);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/CafRemotingMediaRouteProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/CafRemotingMediaRouteProvider.java
index 4ac2698..15b46d4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/CafRemotingMediaRouteProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/CafRemotingMediaRouteProvider.java
@@ -15,7 +15,6 @@
 import org.chromium.chrome.browser.media.router.MediaSource;
 import org.chromium.chrome.browser.media.router.caf.BaseSessionController;
 import org.chromium.chrome.browser.media.router.caf.CafBaseMediaRouteProvider;
-import org.chromium.chrome.browser.media.router.cast.remoting.RemotingMediaSource;
 
 /** A {@link MediaRouteProvider} implementation for remoting, using Cast v3 API. */
 public class CafRemotingMediaRouteProvider extends CafBaseMediaRouteProvider {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingMediaSource.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/RemotingMediaSource.java
similarity index 98%
rename from chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingMediaSource.java
rename to chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/RemotingMediaSource.java
index 744add6..19a9d73 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingMediaSource.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/RemotingMediaSource.java
@@ -1,7 +1,7 @@
 // Copyright 2017 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-package org.chromium.chrome.browser.media.router.cast.remoting;
+package org.chromium.chrome.browser.media.router.caf.remoting;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/RemotingSessionController.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/RemotingSessionController.java
index f7d181c..ec3d6d72 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/RemotingSessionController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/router/caf/remoting/RemotingSessionController.java
@@ -11,7 +11,6 @@
 import org.chromium.chrome.browser.media.router.caf.BaseNotificationController;
 import org.chromium.chrome.browser.media.router.caf.BaseSessionController;
 import org.chromium.chrome.browser.media.router.caf.CafBaseMediaRouteProvider;
-import org.chromium.chrome.browser.media.router.cast.remoting.RemotingMediaSource;
 
 import java.lang.ref.WeakReference;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/BaseMediaRouteProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/BaseMediaRouteProvider.java
deleted file mode 100644
index f4513a8..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/BaseMediaRouteProvider.java
+++ /dev/null
@@ -1,257 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-package org.chromium.chrome.browser.media.router.cast;
-
-import android.os.Handler;
-import android.support.annotation.Nullable;
-import android.support.v7.media.MediaRouteSelector;
-import android.support.v7.media.MediaRouter;
-import android.support.v7.media.MediaRouter.RouteInfo;
-
-import org.chromium.base.Log;
-import org.chromium.chrome.browser.media.router.DiscoveryCallback;
-import org.chromium.chrome.browser.media.router.DiscoveryDelegate;
-import org.chromium.chrome.browser.media.router.FlingingController;
-import org.chromium.chrome.browser.media.router.MediaRoute;
-import org.chromium.chrome.browser.media.router.MediaRouteManager;
-import org.chromium.chrome.browser.media.router.MediaRouteProvider;
-import org.chromium.chrome.browser.media.router.MediaSink;
-import org.chromium.chrome.browser.media.router.MediaSource;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-
-/**
- * A {@link BaseMediaRouteProvider} common implementation for MediaRouteProviders.
- */
-public abstract class BaseMediaRouteProvider
-        implements MediaRouteProvider, DiscoveryDelegate,
-                   ChromeCastSessionManager.CastSessionManagerListener {
-    private static final String TAG = "MediaRouter";
-
-    protected static final List<MediaSink> NO_SINKS = Collections.emptyList();
-
-    protected final MediaRouter mAndroidMediaRouter;
-    protected final MediaRouteManager mManager;
-    protected final Map<String, DiscoveryCallback> mDiscoveryCallbacks =
-            new HashMap<String, DiscoveryCallback>();
-    protected final Map<String, MediaRoute> mRoutes = new HashMap<String, MediaRoute>();
-    protected Handler mHandler = new Handler();
-
-    // There can be only one Cast session at the same time on Android.
-    protected CastSession mSession;
-
-    protected BaseMediaRouteProvider(MediaRouter androidMediaRouter, MediaRouteManager manager) {
-        mAndroidMediaRouter = androidMediaRouter;
-        mManager = manager;
-    }
-
-    /**
-     * @return A MediaSource object constructed from |sourceId|, or null if the derived class does
-     * not support the source.
-     */
-    @Nullable
-    protected abstract MediaSource getSourceFromId(@Nonnull String sourceId);
-
-    /**
-     * @return A CastSessionLaunchRequest encapsulating a session launch request.
-     */
-    @Nullable
-    protected abstract ChromeCastSessionManager.CastSessionLaunchRequest createSessionLaunchRequest(
-            MediaSource source, MediaSink sink, String presentationId, String origin, int tabId,
-            boolean isIncognito, int nativeRequestId);
-
-    /**
-     * Forward the sinks back to the native counterpart.
-     */
-    // Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
-    protected void onSinksReceivedInternal(String sourceId, @Nonnull List<MediaSink> sinks) {
-        Log.d(TAG, "Reporting %d sinks for source: %s", sinks.size(), sourceId);
-        mManager.onSinksReceived(sourceId, this, sinks);
-    }
-
-    /**
-     * {@link DiscoveryDelegate} implementation.
-     */
-    // Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public void onSinksReceived(String sourceId, @Nonnull List<MediaSink> sinks) {
-        Log.d(TAG, "Received %d sinks for sourceId: %s", sinks.size(), sourceId);
-        mHandler.post(() -> { onSinksReceivedInternal(sourceId, sinks); });
-    }
-
-    /**
-     * {@link MediaRouteProvider} implementation.
-     */
-    // Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public boolean supportsSource(@Nonnull String sourceId) {
-        return getSourceFromId(sourceId) != null;
-    }
-
-    // Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public void startObservingMediaSinks(@Nonnull String sourceId) {
-        Log.d(TAG, "startObservingMediaSinks: " + sourceId);
-
-        if (mAndroidMediaRouter == null) {
-            // If the MediaRouter API is not available, report no devices so the page doesn't even
-            // try to cast.
-            onSinksReceived(sourceId, NO_SINKS);
-            return;
-        }
-
-        MediaSource source = getSourceFromId(sourceId);
-        if (source == null) {
-            // If the source is invalid or not supported by this provider, report no devices
-            // available.
-            onSinksReceived(sourceId, NO_SINKS);
-            return;
-        }
-
-        // No-op, if already monitoring the application for this source.
-        String applicationId = source.getApplicationId();
-        DiscoveryCallback callback = mDiscoveryCallbacks.get(applicationId);
-        if (callback != null) {
-            callback.addSourceUrn(sourceId);
-            return;
-        }
-
-        MediaRouteSelector routeSelector = source.buildRouteSelector();
-        if (routeSelector == null) {
-            // If the application invalid, report no devices available.
-            onSinksReceived(sourceId, NO_SINKS);
-            return;
-        }
-
-        List<MediaSink> knownSinks = new ArrayList<MediaSink>();
-        for (RouteInfo route : mAndroidMediaRouter.getRoutes()) {
-            if (route.matchesSelector(routeSelector)) {
-                knownSinks.add(MediaSink.fromRoute(route));
-            }
-        }
-
-        callback = new DiscoveryCallback(sourceId, knownSinks, this, routeSelector);
-        mAndroidMediaRouter.addCallback(
-                routeSelector, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
-        mDiscoveryCallbacks.put(applicationId, callback);
-    }
-
-    // Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public void stopObservingMediaSinks(@Nonnull String sourceId) {
-        Log.d(TAG, "stopObservingMediaSinks: " + sourceId);
-        if (mAndroidMediaRouter == null) return;
-
-        MediaSource source = getSourceFromId(sourceId);
-        if (source == null) return;
-
-        String applicationId = source.getApplicationId();
-        DiscoveryCallback callback = mDiscoveryCallbacks.get(applicationId);
-        if (callback == null) return;
-
-        callback.removeSourceUrn(sourceId);
-
-        if (callback.isEmpty()) {
-            mAndroidMediaRouter.removeCallback(callback);
-            mDiscoveryCallbacks.remove(applicationId);
-        }
-    }
-
-    // Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public void createRoute(String sourceId, String sinkId, String presentationId, String origin,
-            int tabId, boolean isIncognito, int nativeRequestId) {
-        if (mAndroidMediaRouter == null) {
-            mManager.onRouteRequestError("Not supported", nativeRequestId);
-            return;
-        }
-
-        MediaRouter.RouteInfo targetRouteInfo = null;
-        for (MediaRouter.RouteInfo routeInfo : mAndroidMediaRouter.getRoutes()) {
-            if (routeInfo.getId().equals(sinkId)) {
-                targetRouteInfo = routeInfo;
-                break;
-            }
-        }
-
-        if (targetRouteInfo == null) {
-            mManager.onRouteRequestError("No sink", nativeRequestId);
-            return;
-        }
-
-        MediaSink sink = MediaSink.fromRoute(targetRouteInfo);
-
-        MediaSource source = getSourceFromId(sourceId);
-        if (source == null) {
-            mManager.onRouteRequestError("Unsupported source URL", nativeRequestId);
-            return;
-        }
-
-        // When the user clicks a route on the MediaRouteChooserDialog, we intercept the click event
-        // and do not select the route. Instead the route selection is postponed to here. This will
-        // make sure the MediaRouteControllerDialog show up properly.
-        targetRouteInfo.select();
-
-        ChromeCastSessionManager.CastSessionLaunchRequest request = createSessionLaunchRequest(
-                source, sink, presentationId, origin, tabId, isIncognito, nativeRequestId);
-
-        ChromeCastSessionManager.get().requestSessionLaunch(request);
-    }
-
-    @Override
-    public abstract void joinRoute(
-            String sourceId, String presentationId, String origin, int tabId, int nativeRequestId);
-
-    @Override
-    public abstract void closeRoute(String routeId);
-
-    @Override
-    public abstract void detachRoute(String routeId);
-
-    @Override
-    public abstract void sendStringMessage(String routeId, String message);
-
-    // ChromeCastSessionObserver implementation.
-    @Override
-    public abstract void onSessionStarting(
-            ChromeCastSessionManager.CastSessionLaunchRequest originalRequest);
-
-    @Override
-    public abstract void onSessionEnded();
-
-    // Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public void onSessionStartFailed() {
-        for (String routeId : mRoutes.keySet()) {
-            mManager.onRouteClosed(routeId, "Launch error");
-        }
-        mRoutes.clear();
-    };
-
-    // Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public void onSessionStarted(CastSession session) {
-        mSession = session;
-    }
-
-    // Migrated to CafBaseMediaRouteProvider.endAllRoutes(). See https://crbug.com/711860.
-    @Override
-    public void onSessionStopAction() {
-        if (mSession == null) return;
-
-        for (String routeId : mRoutes.keySet()) closeRoute(routeId);
-    }
-
-    @Override
-    @Nullable
-    public FlingingController getFlingingController(String routeId) {
-        return null;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastMediaRouteProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastMediaRouteProvider.java
deleted file mode 100644
index 31cfdfb..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastMediaRouteProvider.java
+++ /dev/null
@@ -1,443 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.router.cast;
-
-import android.support.annotation.Nullable;
-import android.support.v7.media.MediaRouter;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.chromium.base.Log;
-import org.chromium.base.VisibleForTesting;
-import org.chromium.chrome.browser.media.router.ChromeMediaRouter;
-import org.chromium.chrome.browser.media.router.ClientRecord;
-import org.chromium.chrome.browser.media.router.MediaRoute;
-import org.chromium.chrome.browser.media.router.MediaRouteManager;
-import org.chromium.chrome.browser.media.router.MediaRouteProvider;
-import org.chromium.chrome.browser.media.router.MediaSink;
-import org.chromium.chrome.browser.media.router.MediaSource;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A {@link MediaRouteProvider} implementation for Cast devices and applications.
- */
-public class CastMediaRouteProvider extends BaseMediaRouteProvider {
-    private static final String TAG = "MediaRouter";
-
-    private static final String AUTO_JOIN_PRESENTATION_ID = "auto-join";
-    private static final String PRESENTATION_ID_SESSION_ID_PREFIX = "cast-session_";
-
-    private final CastMessageHandler mMessageHandler;
-    private ClientRecord mLastRemovedRouteRecord;
-    private final Map<String, ClientRecord> mClientRecords = new HashMap<String, ClientRecord>();
-
-    /**
-     * @return Initialized {@link CastMediaRouteProvider} object.
-     */
-    public static CastMediaRouteProvider create(MediaRouteManager manager) {
-        return new CastMediaRouteProvider(ChromeMediaRouter.getAndroidMediaRouter(), manager);
-    }
-
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public void onSessionStartFailed() {
-        super.onSessionStartFailed();
-        mClientRecords.clear();
-    }
-
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public void onSessionStarted(CastSession session) {
-        super.onSessionStarted(session);
-        mMessageHandler.onSessionCreated(mSession);
-    }
-
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public void onSessionEnded() {
-        if (mSession == null) return;
-
-        if (mClientRecords.isEmpty()) {
-            for (String routeId : mRoutes.keySet()) mManager.onRouteTerminated(routeId);
-            mRoutes.clear();
-        } else {
-            mLastRemovedRouteRecord = mClientRecords.values().iterator().next();
-            for (ClientRecord client : mClientRecords.values()) {
-                mManager.onRouteTerminated(client.routeId);
-
-                mRoutes.remove(client.routeId);
-            }
-            mClientRecords.clear();
-        }
-
-        mSession = null;
-
-        if (mAndroidMediaRouter != null) {
-            mAndroidMediaRouter.selectRoute(mAndroidMediaRouter.getDefaultRoute());
-        }
-    }
-
-    public void onMessage(String clientId, String message) {
-        ClientRecord clientRecord = mClientRecords.get(clientId);
-        if (clientRecord == null) return;
-
-        if (!clientRecord.isConnected) {
-            Log.d(TAG, "Queueing message to client %s: %s", clientId, message);
-            clientRecord.pendingMessages.add(message);
-            return;
-        }
-
-        Log.d(TAG, "Sending message to client %s: %s", clientId, message);
-        mManager.onMessage(clientRecord.routeId, message);
-    }
-
-    public CastMessageHandler getMessageHandler() {
-        return mMessageHandler;
-    }
-
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    public Set<String> getClients() {
-        return mClientRecords.keySet();
-    }
-
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    public Map<String, ClientRecord> getClientRecords() {
-        return mClientRecords;
-    }
-
-    @Override
-    protected MediaSource getSourceFromId(String sourceId) {
-        return CastMediaSource.from(sourceId);
-    }
-
-    @Override
-    protected ChromeCastSessionManager.CastSessionLaunchRequest createSessionLaunchRequest(
-            MediaSource source, MediaSink sink, String presentationId, String origin, int tabId,
-            boolean isIncognito, int nativeRequestId) {
-        return new CreateRouteRequest(source, sink, presentationId, origin, tabId, isIncognito,
-                nativeRequestId, this, CreateRouteRequest.RequestedCastSessionType.CAST,
-                mMessageHandler);
-    }
-
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public void onSessionStarting(
-            ChromeCastSessionManager.CastSessionLaunchRequest sessionLaunchRequest) {
-        CreateRouteRequest request = (CreateRouteRequest) sessionLaunchRequest;
-        MediaSink sink = request.getSink();
-        MediaSource source = request.getSource();
-
-        MediaRoute route =
-                new MediaRoute(sink.getId(), source.getSourceId(), request.getPresentationId());
-        addRoute(route, request.getOrigin(), request.getTabId());
-        mManager.onRouteCreated(route.id, route.sinkId, request.getNativeRequestId(), this, true);
-
-        String clientId = ((CastMediaSource) source).getClientId();
-
-        if (clientId != null) {
-            ClientRecord clientRecord = mClientRecords.get(clientId);
-            if (clientRecord != null) {
-                sendReceiverAction(clientRecord.routeId, sink, clientId, "cast");
-            }
-        }
-    }
-
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public void joinRoute(String sourceId, String presentationId, String origin, int tabId,
-            int nativeRequestId) {
-        CastMediaSource source = CastMediaSource.from(sourceId);
-        if (source == null || source.getClientId() == null) {
-            mManager.onRouteRequestError("Unsupported presentation URL", nativeRequestId);
-            return;
-        }
-
-        if (mSession == null) {
-            mManager.onRouteRequestError("No presentation", nativeRequestId);
-            return;
-        }
-
-        if (!canJoinExistingSession(presentationId, origin, tabId, source)) {
-            mManager.onRouteRequestError("No matching route", nativeRequestId);
-            return;
-        }
-
-        MediaRoute route = new MediaRoute(mSession.getSinkId(), sourceId, presentationId);
-        addRoute(route, origin, tabId);
-        mManager.onRouteCreated(route.id, route.sinkId, nativeRequestId, this, false);
-    }
-
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public void closeRoute(String routeId) {
-        MediaRoute route = mRoutes.get(routeId);
-        if (route == null) return;
-
-        if (mSession == null) {
-            mRoutes.remove(routeId);
-            mManager.onRouteTerminated(routeId);
-            return;
-        }
-
-        ClientRecord client = getClientRecordByRouteId(routeId);
-        if (client != null && mAndroidMediaRouter != null) {
-            MediaSink sink = MediaSink.fromSinkId(mSession.getSinkId(), mAndroidMediaRouter);
-            if (sink != null) sendReceiverAction(routeId, sink, client.clientId, "stop");
-        }
-
-        ChromeCastSessionManager.get().stopApplication();
-    }
-
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    @Override
-    public void detachRoute(String routeId) {
-        mRoutes.remove(routeId);
-
-        removeClient(getClientRecordByRouteId(routeId));
-    }
-
-    // Migrated to CafMessageHandler. See https://crbug.com/711860.
-    @Override
-    public void sendStringMessage(String routeId, String message) {
-        Log.d(TAG, "Received message from client: %s", message);
-
-        if (!mRoutes.containsKey(routeId)) {
-            return;
-        }
-
-        try {
-            JSONObject jsonMessage = new JSONObject(message);
-
-            String messageType = jsonMessage.getString("type");
-            // TODO(zqzhang): Move the handling of "client_connect", "client_disconnect" and
-            // "leave_session" from CastMRP to CastMessageHandler. Also, need to have a
-            // ClientManager for client managing.
-            if ("client_connect".equals(messageType)) {
-                handleClientConnectMessage(jsonMessage);
-            } else if ("client_disconnect".equals(messageType)) {
-                handleClientDisconnectMessage(jsonMessage);
-            } else if ("leave_session".equals(messageType)) {
-                handleLeaveSessionMessage(jsonMessage);
-            } else if (mSession != null) {
-                mMessageHandler.handleSessionMessage(jsonMessage);
-            }
-        } catch (JSONException e) {
-            Log.e(TAG, "JSONException while handling internal message: " + e);
-        }
-    }
-
-    // Migrated to CafMessageHandler. See https://crbug.com/711860.
-    private boolean handleClientConnectMessage(JSONObject jsonMessage) throws JSONException {
-        String clientId = jsonMessage.getString("clientId");
-        if (clientId == null) return false;
-
-        ClientRecord clientRecord = mClientRecords.get(clientId);
-        if (clientRecord == null) return false;
-
-        clientRecord.isConnected = true;
-        if (mSession != null) mSession.onClientConnected(clientId);
-
-        if (clientRecord.pendingMessages.size() == 0) return true;
-        for (String message : clientRecord.pendingMessages) {
-            Log.d(TAG, "Deqeueing message for client %s: %s", clientId, message);
-            mManager.onMessage(clientRecord.routeId, message);
-        }
-        clientRecord.pendingMessages.clear();
-
-        return true;
-    }
-
-    // Migrated to CafMessageHandler. See https://crbug.com/711860.
-    private boolean handleClientDisconnectMessage(JSONObject jsonMessage) throws JSONException {
-        String clientId = jsonMessage.getString("clientId");
-        if (clientId == null) return false;
-
-        ClientRecord client = mClientRecords.get(clientId);
-        if (client == null) return false;
-
-        mRoutes.remove(client.routeId);
-        removeClient(client);
-
-        mManager.onRouteTerminated(client.routeId);
-
-        return true;
-    }
-
-    // Migrated to CafMessageHandler. See https://crbug.com/711860.
-    private boolean handleLeaveSessionMessage(JSONObject jsonMessage) throws JSONException {
-        String clientId = jsonMessage.getString("clientId");
-        if (clientId == null || mSession == null) return false;
-
-        String sessionId = jsonMessage.getString("message");
-        if (!mSession.getSessionId().equals(sessionId)) return false;
-
-        ClientRecord leavingClient = mClientRecords.get(clientId);
-        if (leavingClient == null) return false;
-
-        int sequenceNumber = jsonMessage.optInt("sequenceNumber", -1);
-        onMessage(clientId, buildInternalMessage("leave_session", sequenceNumber, clientId, null));
-
-        // Send a "disconnect_session" message to all the clients that match with the leaving
-        // client's auto join policy.
-        for (ClientRecord client : mClientRecords.values()) {
-            if ((CastMediaSource.AUTOJOIN_TAB_AND_ORIGIN_SCOPED.equals(leavingClient.autoJoinPolicy)
-                        && isSameOrigin(client.origin, leavingClient.origin)
-                        && client.tabId == leavingClient.tabId)
-                    || (CastMediaSource.AUTOJOIN_ORIGIN_SCOPED.equals(leavingClient.autoJoinPolicy)
-                               && isSameOrigin(client.origin, leavingClient.origin))) {
-                onMessage(client.clientId,
-                        buildInternalMessage("disconnect_session", -1, client.clientId, sessionId));
-            }
-        }
-
-        return true;
-    }
-
-    // Migrated to CafMessageHandler. See https://crbug.com/711860.
-    private String buildInternalMessage(
-            String type, int sequenceNumber, String clientId, String message) throws JSONException {
-        JSONObject jsonMessage = new JSONObject();
-        jsonMessage.put("type", type);
-        jsonMessage.put("sequenceNumber", sequenceNumber);
-        jsonMessage.put("timeoutMillis", 0);
-        jsonMessage.put("clientId", clientId);
-        jsonMessage.put("message", message);
-        return jsonMessage.toString();
-    }
-
-    @VisibleForTesting
-    CastMediaRouteProvider(MediaRouter androidMediaRouter, MediaRouteManager manager) {
-        super(androidMediaRouter, manager);
-        mMessageHandler = new CastMessageHandler(this);
-    }
-
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    private boolean canAutoJoin(CastMediaSource source, String origin, int tabId) {
-        if (source.getAutoJoinPolicy().equals(CastMediaSource.AUTOJOIN_PAGE_SCOPED)) return false;
-
-        CastMediaSource currentSource = CastMediaSource.from(mSession.getSourceId());
-        if (!currentSource.getApplicationId().equals(source.getApplicationId())) return false;
-
-        ClientRecord client = null;
-        if (!mClientRecords.isEmpty()) {
-            client = mClientRecords.values().iterator().next();
-        } else if (mLastRemovedRouteRecord != null) {
-            client = mLastRemovedRouteRecord;
-            return isSameOrigin(origin, client.origin) && tabId == client.tabId;
-        }
-        if (client == null) return false;
-
-        boolean sameOrigin = isSameOrigin(origin, client.origin);
-        if (source.getAutoJoinPolicy().equals(CastMediaSource.AUTOJOIN_ORIGIN_SCOPED)) {
-            return sameOrigin;
-        } else if (source.getAutoJoinPolicy().equals(
-                           CastMediaSource.AUTOJOIN_TAB_AND_ORIGIN_SCOPED)) {
-            return sameOrigin && tabId == client.tabId;
-        }
-
-        return false;
-    }
-
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    private boolean canJoinExistingSession(
-            String presentationId, String origin, int tabId, CastMediaSource source) {
-        if (AUTO_JOIN_PRESENTATION_ID.equals(presentationId)) {
-            return canAutoJoin(source, origin, tabId);
-        } else if (presentationId.startsWith(PRESENTATION_ID_SESSION_ID_PREFIX)) {
-            String sessionId = presentationId.substring(PRESENTATION_ID_SESSION_ID_PREFIX.length());
-            if (mSession.getSessionId().equals(sessionId)) return true;
-        } else {
-            for (MediaRoute route : mRoutes.values()) {
-                if (route.presentationId.equals(presentationId)) return true;
-            }
-        }
-        return false;
-    }
-
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    @Nullable
-    private ClientRecord getClientRecordByRouteId(String routeId) {
-        for (ClientRecord record : mClientRecords.values()) {
-            if (record.routeId.equals(routeId)) return record;
-        }
-        return null;
-    }
-
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    @VisibleForTesting
-    void addRoute(MediaRoute route, String origin, int tabId) {
-        mRoutes.put(route.id, route);
-
-        CastMediaSource source = CastMediaSource.from(route.sourceId);
-        final String clientId = source.getClientId();
-
-        if (clientId == null || mClientRecords.get(clientId) != null) return;
-
-        mClientRecords.put(clientId,
-                new ClientRecord(
-                        route.id,
-                        clientId,
-                        source.getApplicationId(),
-                        source.getAutoJoinPolicy(),
-                        origin,
-                        tabId));
-    }
-
-    // Migrated to CastMessageHandler.sendReceiverActionToClient. See https://crbug.com/711860.
-    private void sendReceiverAction(
-            String routeId, MediaSink sink, String clientId, String action) {
-        try {
-            JSONObject jsonReceiver = new JSONObject();
-            jsonReceiver.put("label", sink.getId());
-            jsonReceiver.put("friendlyName", sink.getName());
-            jsonReceiver.put("capabilities", CastSessionImpl.getCapabilities(sink.getDevice()));
-            jsonReceiver.put("volume", null);
-            jsonReceiver.put("isActiveInput", null);
-            jsonReceiver.put("displayStatus", null);
-            jsonReceiver.put("receiverType", "cast");
-
-            JSONObject jsonReceiverAction = new JSONObject();
-            jsonReceiverAction.put("receiver", jsonReceiver);
-            jsonReceiverAction.put("action", action);
-
-            JSONObject json = new JSONObject();
-            json.put("type", "receiver_action");
-            json.put("sequenceNumber", -1);
-            json.put("timeoutMillis", 0);
-            json.put("clientId", clientId);
-            json.put("message", jsonReceiverAction);
-
-            onMessage(clientId, json.toString());
-        } catch (JSONException e) {
-            Log.e(TAG, "Failed to send receiver action message", e);
-        }
-    }
-
-    private void removeClient(@Nullable ClientRecord client) {
-        if (client == null) return;
-
-        mLastRemovedRouteRecord = client;
-        mClientRecords.remove(client.clientId);
-    }
-
-    /**
-     * Compares two origins. Empty origin strings correspond to unique origins in
-     * url::Origin.
-     *
-     * @param originA A URL origin.
-     * @param originB A URL origin.
-     * @return True if originA and originB represent the same origin, false otherwise.
-     */
-    // Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-    private static final boolean isSameOrigin(String originA, String originB) {
-        if (originA == null || originA.isEmpty() || originB == null || originB.isEmpty())
-            return false;
-        return originA.equals(originB);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastMessageHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastMessageHandler.java
deleted file mode 100644
index 0e2ea67..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastMessageHandler.java
+++ /dev/null
@@ -1,627 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.router.cast;
-
-import android.os.Handler;
-import android.support.v4.util.ArrayMap;
-import android.util.SparseArray;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.chromium.base.Log;
-import org.chromium.base.VisibleForTesting;
-import org.chromium.chrome.browser.media.router.CastRequestIdGenerator;
-import org.chromium.chrome.browser.media.router.CastSessionUtil;
-import org.chromium.chrome.browser.media.router.ClientRecord;
-
-import java.util.ArrayDeque;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-
-/**
- * The handler for cast messages. It receives events between the Cast SDK and the page, process and
- * dispatch the messages accordingly. The handler talks to the Cast SDK via CastSession, and
- * talks to the pages via the media router.
- */
-// Migrated to CafMessageHandler. See https://crbug.com/711860.
-public class CastMessageHandler {
-    private static final String TAG = "MediaRouter";
-
-    // Sequence number used when no sequence number is required or was initially passed.
-    static final int INVALID_SEQUENCE_NUMBER = -1;
-
-    private static final String MEDIA_MESSAGE_TYPES[] = {
-            "PLAY",
-            "LOAD",
-            "PAUSE",
-            "SEEK",
-            "STOP_MEDIA",
-            "MEDIA_SET_VOLUME",
-            "MEDIA_GET_STATUS",
-            "EDIT_TRACKS_INFO",
-            "QUEUE_LOAD",
-            "QUEUE_INSERT",
-            "QUEUE_UPDATE",
-            "QUEUE_REMOVE",
-            "QUEUE_REORDER",
-    };
-
-    private static final String MEDIA_SUPPORTED_COMMANDS[] = {
-            "pause",
-            "seek",
-            "stream_volume",
-            "stream_mute",
-    };
-
-    // Lock used to lazy initialize sMediaOverloadedMessageTypes.
-    private static final Object INIT_LOCK = new Object();
-
-    // Map associating types that have a different names outside of the media namespace and inside.
-    // In other words, some types are sent as MEDIA_FOO or FOO_MEDIA by the client by the Cast
-    // expect them to be named FOO. The reason being that FOO might exist in multiple namespaces
-    // but the client isn't aware of namespacing.
-    private static Map<String, String> sMediaOverloadedMessageTypes;
-
-    private SparseArray<RequestRecord> mRequests;
-    private ArrayMap<String, Queue<Integer>> mStopRequests;
-    private Queue<RequestRecord> mVolumeRequests;
-
-    // The reference to CastSession, only valid after calling {@link onSessionCreated}, and will be
-    // reset to null when calling {@link onApplicationStopped}.
-    private CastSession mSession;
-    private final CastMediaRouteProvider mRouteProvider;
-    private Handler mHandler;
-
-    /**
-     * The record for client requests. {@link CastMessageHandler} uses this class to manage the
-     * client requests and match responses to the requests.
-     */
-    static class RequestRecord {
-        public final String clientId;
-        public final int sequenceNumber;
-
-        public RequestRecord(String clientId, int sequenceNumber) {
-            this.clientId = clientId;
-            this.sequenceNumber = sequenceNumber;
-        }
-    }
-
-    /**
-     * Initializes a new {@link CastMessageHandler} instance.
-     * @param session  The {@link CastSession} for communicating with the Cast SDK.
-     * @param provider The {@link CastMediaRouteProvider} for communicating with the page.
-     */
-    public CastMessageHandler(CastMediaRouteProvider provider) {
-        mRouteProvider = provider;
-        mRequests = new SparseArray<RequestRecord>();
-        mStopRequests = new ArrayMap<String, Queue<Integer>>();
-        mVolumeRequests = new ArrayDeque<RequestRecord>();
-        mHandler = new Handler();
-
-        synchronized (INIT_LOCK) {
-            if (sMediaOverloadedMessageTypes == null) {
-                sMediaOverloadedMessageTypes = new HashMap<String, String>();
-                sMediaOverloadedMessageTypes.put("STOP_MEDIA", "STOP");
-                sMediaOverloadedMessageTypes.put("MEDIA_SET_VOLUME", "SET_VOLUME");
-                sMediaOverloadedMessageTypes.put("MEDIA_GET_STATUS", "GET_STATUS");
-            }
-        }
-    }
-
-    @VisibleForTesting
-    static String[] getMediaMessageTypesForTest() {
-        return MEDIA_MESSAGE_TYPES;
-    }
-
-    @VisibleForTesting
-    static Map<String, String> getMediaOverloadedMessageTypesForTest() {
-        return sMediaOverloadedMessageTypes;
-    }
-
-    @VisibleForTesting
-    SparseArray<RequestRecord> getRequestsForTest() {
-        return mRequests;
-    }
-
-    @VisibleForTesting
-    Queue<RequestRecord> getVolumeRequestsForTest() {
-        return mVolumeRequests;
-    }
-
-    @VisibleForTesting
-    Map<String, Queue<Integer>> getStopRequestsForTest() {
-        return mStopRequests;
-    }
-
-    /**
-     * Set the session when a session is created, and notify all clients that are not connected.
-     * @param session The newly created session.
-     */
-    public void onSessionCreated(CastSession session) {
-        mSession = session;
-        for (ClientRecord client : mRouteProvider.getClientRecords().values()) {
-            if (!client.isConnected) continue;
-
-            mSession.onClientConnected(client.clientId);
-        }
-    }
-
-    /////////////////////////////////////////////////////////////////////////////////////////////
-    // Functions for handling messages from the page to the Cast device.
-
-    /**
-     * Handles messages related to the cast session, i.e. messages happening on a established
-     * connection. All these messages are sent from the page to the Cast SDK.
-     * @param message The JSONObject message to be handled.
-     */
-    public boolean handleSessionMessage(JSONObject message) throws JSONException {
-        String messageType = message.getString("type");
-        if ("v2_message".equals(messageType)) {
-            return handleCastV2Message(message);
-        } else if ("app_message".equals(messageType)) {
-            return handleAppMessage(message);
-        } else {
-            Log.e(TAG, "Unsupported message: %s", message);
-            return false;
-        }
-    }
-
-    // An example of the Cast V2 message:
-    //    {
-    //        "type": "v2_message",
-    //        "message": {
-    //          "type": "...",
-    //          ...
-    //        },
-    //        "sequenceNumber": 0,
-    //        "timeoutMillis": 0,
-    //        "clientId": "144042901280235697"
-    //    }
-    @VisibleForTesting
-    boolean handleCastV2Message(JSONObject jsonMessage)
-            throws JSONException {
-        assert "v2_message".equals(jsonMessage.getString("type"));
-
-        final String clientId = jsonMessage.getString("clientId");
-        if (clientId == null || !mRouteProvider.getClients().contains(clientId)) return false;
-
-        JSONObject jsonCastMessage = jsonMessage.getJSONObject("message");
-        String messageType = jsonCastMessage.getString("type");
-        final int sequenceNumber = jsonMessage.optInt("sequenceNumber", INVALID_SEQUENCE_NUMBER);
-
-        if ("STOP".equals(messageType)) {
-            handleStopMessage(clientId, sequenceNumber);
-            return true;
-        }
-
-        if ("SET_VOLUME".equals(messageType)) {
-            CastSession.HandleVolumeMessageResult result = mSession.handleVolumeMessage(
-                    jsonCastMessage.getJSONObject("volume"), clientId, sequenceNumber);
-            if (!result.mSucceeded) return false;
-
-            // For each successful volume message we need to respond with an empty "v2_message" so
-            // the Cast Web SDK can call the success callback of the page.  If we expect the volume
-            // to change as the result of the command, we're relying on {@link
-            // Cast.CastListener#onVolumeChanged} to get called by the Android Cast SDK when the
-            // receiver status is updated. We keep the sequence number until then.  If the volume
-            // doesn't change as the result of the command, we won't get notified by the Android SDK
-            // when the status update is received so we respond to the volume message immediately.
-            if (result.mShouldWaitForVolumeChange) {
-                mVolumeRequests.add(new RequestRecord(clientId, sequenceNumber));
-            } else {
-                // It's usually bad to have request and response on the same call stack so post the
-                // response to the Android message loop.
-                mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            onVolumeChanged(clientId, sequenceNumber);
-                        }
-                    });
-            }
-            return true;
-        }
-
-        if (Arrays.asList(MEDIA_MESSAGE_TYPES).contains(messageType)) {
-            if (sMediaOverloadedMessageTypes.containsKey(messageType)) {
-                messageType = sMediaOverloadedMessageTypes.get(messageType);
-                jsonCastMessage.put("type", messageType);
-            }
-            return sendJsonCastMessage(
-                    jsonCastMessage, CastSessionUtil.MEDIA_NAMESPACE, clientId, sequenceNumber);
-        }
-
-        return true;
-    }
-
-    @VisibleForTesting
-    void handleStopMessage(String clientId, int sequenceNumber) {
-        Queue<Integer> sequenceNumbersForClient = mStopRequests.get(clientId);
-        if (sequenceNumbersForClient == null) {
-            sequenceNumbersForClient = new ArrayDeque<Integer>();
-            mStopRequests.put(clientId, sequenceNumbersForClient);
-        }
-        sequenceNumbersForClient.add(sequenceNumber);
-
-        mSession.stopApplication();
-    }
-
-    // An example of the Cast application message:
-    // {
-    //   "type":"app_message",
-    //   "message": {
-    //     "sessionId":"...",
-    //     "namespaceName":"...",
-    //     "message": ...
-    //   },
-    //   "sequenceNumber":0,
-    //   "timeoutMillis":3000,
-    //   "clientId":"14417311915272175"
-    // }
-    @VisibleForTesting
-    boolean handleAppMessage(JSONObject jsonMessage) throws JSONException {
-        assert "app_message".equals(jsonMessage.getString("type"));
-
-        String clientId = jsonMessage.getString("clientId");
-        if (clientId == null || !mRouteProvider.getClients().contains(clientId)) return false;
-
-        JSONObject jsonAppMessageWrapper = jsonMessage.getJSONObject("message");
-
-        if (!mSession.getSessionId().equals(jsonAppMessageWrapper.getString("sessionId"))) {
-            return false;
-        }
-
-        String namespaceName = jsonAppMessageWrapper.getString("namespaceName");
-        if (namespaceName == null || namespaceName.isEmpty()) return false;
-
-        if (!mSession.getNamespaces().contains(namespaceName)) return false;
-
-        int sequenceNumber = jsonMessage.optInt("sequenceNumber", INVALID_SEQUENCE_NUMBER);
-
-        Object actualMessageObject = jsonAppMessageWrapper.get("message");
-        if (actualMessageObject == null) return false;
-
-        if (actualMessageObject instanceof String) {
-            String actualMessage = jsonAppMessageWrapper.getString("message");
-            return mSession.sendStringCastMessage(
-                    actualMessage, namespaceName, clientId, sequenceNumber);
-        }
-
-        JSONObject actualMessage = jsonAppMessageWrapper.getJSONObject("message");
-        return sendJsonCastMessage(actualMessage, namespaceName, clientId, sequenceNumber);
-    }
-
-    @VisibleForTesting
-    boolean sendJsonCastMessage(
-            JSONObject message,
-            final String namespace,
-            final String clientId,
-            final int sequenceNumber) throws JSONException {
-        if (mSession.isApiClientInvalid()) return false;
-
-        removeNullFields(message);
-
-        // Map the request id to a valid sequence number only.
-        if (sequenceNumber != INVALID_SEQUENCE_NUMBER) {
-            // If for some reason, there is already a requestId other than 0, it
-            // is kept. Otherwise, one is generated. In all cases it's associated with the
-            // sequenceNumber passed by the client.
-            int requestId = message.optInt("requestId", 0);
-            if (requestId == 0) {
-                requestId = CastRequestIdGenerator.getNextRequestId();
-                message.put("requestId", requestId);
-            }
-            mRequests.append(requestId, new RequestRecord(clientId, sequenceNumber));
-        }
-
-        return mSession.sendStringCastMessage(
-                message.toString(), namespace, clientId, sequenceNumber);
-    }
-
-    /////////////////////////////////////////////////////////////////////////////////////////////
-    // Functions for handling messages from the Cast device to the pages.
-
-    /**
-     * Forwards the messages from the Cast device to the clients, and perform proper actions if it
-     * is media message.
-     * @param namespace The application specific namespace this message belongs to.
-     * @param message The message within the namespace that's being sent by the receiver
-     */
-    public void onMessageReceived(String namespace, String message) {
-        RequestRecord request = null;
-        try {
-            JSONObject jsonMessage = new JSONObject(message);
-            int requestId = jsonMessage.getInt("requestId");
-            if (mRequests.indexOfKey(requestId) >= 0) {
-                request = mRequests.get(requestId);
-                mRequests.delete(requestId);
-            }
-        } catch (JSONException e) {
-        }
-
-        if (CastSessionUtil.MEDIA_NAMESPACE.equals(namespace)) {
-            onMediaMessage(message, request);
-            return;
-        }
-
-        onAppMessage(message, namespace, request);
-    }
-
-    /**
-     * Forwards the media message to the page via the media router.
-     * The MEDIA_STATUS message needs to be sent to all the clients.
-     * @param message The media that's being send by the receiver.
-     * @param request The information about the client and the sequence number to respond with.
-     */
-    @VisibleForTesting
-    void onMediaMessage(String message, RequestRecord request) {
-        mSession.onMediaMessage(message);
-
-        if (isMediaStatusMessage(message)) {
-            // MEDIA_STATUS needs to be sent to all the clients.
-            for (String clientId : mRouteProvider.getClients()) {
-                if (request != null && clientId.equals(request.clientId)) continue;
-
-                sendClientMessageTo(
-                        clientId, "v2_message", message, INVALID_SEQUENCE_NUMBER);
-            }
-        }
-        if (request != null) {
-            sendClientMessageTo(
-                    request.clientId, "v2_message", message, request.sequenceNumber);
-        }
-    }
-
-    /**
-     * Forwards the application specific message to the page via the media router.
-     * @param message The message within the namespace that's being sent by the receiver.
-     * @param namespace The application specific namespace this message belongs to.
-     * @param request The information about the client and the sequence number to respond with.
-     */
-    @VisibleForTesting
-    void onAppMessage(String message, String namespace, RequestRecord request) {
-        try {
-            JSONObject jsonMessage = new JSONObject();
-            jsonMessage.put("sessionId", mSession.getSessionId());
-            jsonMessage.put("namespaceName", namespace);
-            jsonMessage.put("message", message);
-            if (request != null) {
-                sendClientMessageTo(request.clientId, "app_message",
-                        jsonMessage.toString(), request.sequenceNumber);
-            } else {
-                broadcastClientMessage("app_message", jsonMessage.toString());
-            }
-        } catch (JSONException e) {
-            Log.e(TAG, "Failed to create the message wrapper", e);
-        }
-    }
-
-    /**
-     * Notifies the application has stopped to all requesting clients.
-     */
-    public void onApplicationStopped() {
-        for (String clientId : mRouteProvider.getClients()) {
-            Queue<Integer> sequenceNumbersForClient = mStopRequests.get(clientId);
-            if (sequenceNumbersForClient == null) {
-                sendClientMessageTo(
-                        clientId, "remove_session", mSession.getSessionId(),
-                        INVALID_SEQUENCE_NUMBER);
-                continue;
-            }
-
-            for (int sequenceNumber : sequenceNumbersForClient) {
-                sendClientMessageTo(
-                        clientId, "remove_session", mSession.getSessionId(), sequenceNumber);
-            }
-            mStopRequests.remove(clientId);
-        }
-        mSession = null;
-    }
-
-    /**
-     * When the Cast device volume really changed, updates the session status and notify all
-     * requesting clients.
-     */
-    public void onVolumeChanged() {
-        mSession.updateSessionStatus();
-
-        if (mVolumeRequests.isEmpty()) return;
-
-        for (RequestRecord r : mVolumeRequests) onVolumeChanged(r.clientId, r.sequenceNumber);
-        mVolumeRequests.clear();
-    }
-
-    @VisibleForTesting
-    void onVolumeChanged(String clientId, int sequenceNumber) {
-        sendClientMessageTo(clientId, "v2_message", null, sequenceNumber);
-    }
-
-    /**
-     * Notifies a client that an app message has been sent.
-     * @param clientId The client id the message is sent from.
-     * @param sequenceNumber The sequence number of the message.
-     */
-    public void onAppMessageSent(String clientId, int sequenceNumber) {
-        sendClientMessageTo(clientId, "app_message", null, sequenceNumber);
-    }
-
-    /**
-     * Broadcasts the message to all clients.
-     * @param type    The type of the message.
-     * @param message The message to broadcast.
-     */
-    public void broadcastClientMessage(String type, String message) {
-        for (String clientId : mRouteProvider.getClients()) {
-            sendClientMessageTo(clientId, type, message, INVALID_SEQUENCE_NUMBER);
-        }
-    }
-
-    /**
-     * Sends a message to a specific client.
-     * @param clientId The id of the receiving client.
-     * @param type     The type of the message.
-     * @param message  The message to be sent.
-     * @param sequenceNumber The sequence number for matching requesting and responding messages.
-     */
-    public void sendClientMessageTo(
-            String clientId, String type, String message, int sequenceNumber) {
-        mRouteProvider.onMessage(clientId,
-                buildInternalMessage(type, message, clientId, sequenceNumber));
-    }
-
-    @VisibleForTesting
-    String buildInternalMessage(
-            String type, String message, String clientId, int sequenceNumber) {
-        JSONObject json = new JSONObject();
-        try {
-            json.put("type", type);
-            json.put("sequenceNumber", sequenceNumber);
-            json.put("timeoutMillis", 0);
-            json.put("clientId", clientId);
-
-            // TODO(mlamouri): we should have a more reliable way to handle string, null and Object
-            // messages.
-            if (message == null
-                    || "remove_session".equals(type)
-                    || "disconnect_session".equals(type)) {
-                json.put("message", message);
-            } else {
-                JSONObject jsonMessage = new JSONObject(message);
-                if ("v2_message".equals(type)
-                        && "MEDIA_STATUS".equals(jsonMessage.getString("type"))) {
-                    sanitizeMediaStatusMessage(jsonMessage);
-                }
-                json.put("message", jsonMessage);
-            }
-        } catch (JSONException e) {
-            Log.e(TAG, "Failed to build the reply: " + e);
-        }
-
-        return json.toString();
-    }
-
-    /**
-     * @return A message containing the information of the {@link CastSession}.
-     */
-    public String buildSessionMessage() {
-        if (mSession == null) return "{}";
-
-        CastSessionInfo sessionInfo = mSession.getSessionInfo();
-        if (sessionInfo == null) return "{}";
-
-        try {
-            // "volume" is a part of "receiver" initialized below.
-            JSONObject jsonVolume = new JSONObject();
-            jsonVolume.put("level", sessionInfo.receiver.volume.level);
-            jsonVolume.put("muted", sessionInfo.receiver.volume.muted);
-
-            // "receiver" is a part of "message" initialized below.
-            JSONObject jsonReceiver = new JSONObject();
-            jsonReceiver.put("label", sessionInfo.receiver.label);
-            jsonReceiver.put("friendlyName", sessionInfo.receiver.friendlyName);
-            jsonReceiver.put("capabilities", toJSONArray(sessionInfo.receiver.capabilities));
-            jsonReceiver.put("volume", jsonVolume);
-            jsonReceiver.put("isActiveInput", sessionInfo.receiver.isActiveInput);
-            jsonReceiver.put("displayStatus", sessionInfo.receiver.displayStatus);
-            jsonReceiver.put("receiverType", sessionInfo.receiver.receiverType);
-
-            JSONArray jsonNamespaces = new JSONArray();
-            for (String namespace : sessionInfo.namespaces) {
-                JSONObject jsonNamespace = new JSONObject();
-                jsonNamespace.put("name", namespace);
-                jsonNamespaces.put(jsonNamespace);
-            }
-
-            JSONObject jsonMessage = new JSONObject();
-            jsonMessage.put("sessionId", sessionInfo.sessionId);
-            jsonMessage.put("statusText", sessionInfo.statusText);
-            jsonMessage.put("receiver", jsonReceiver);
-            jsonMessage.put("namespaces", jsonNamespaces);
-            jsonMessage.put("media", toJSONArray(sessionInfo.media));
-            jsonMessage.put("status", sessionInfo.status);
-            jsonMessage.put("transportId", sessionInfo.transportId);
-            jsonMessage.put("appId", sessionInfo.appId);
-            jsonMessage.put("displayName", sessionInfo.displayName);
-
-            return jsonMessage.toString();
-        } catch (JSONException e) {
-            Log.w(TAG, "Building session message failed", e);
-            return "{}";
-        }
-    }
-
-    /////////////////////////////////////////////////////////////////////////////////////////////
-    // Utility functions
-
-    /**
-     * Modifies the received MediaStatus message to match the format expected by the client.
-     */
-    private void sanitizeMediaStatusMessage(JSONObject object) throws JSONException {
-        object.put("sessionId", mSession.getSessionId());
-
-        JSONArray mediaStatus = object.getJSONArray("status");
-        for (int i = 0; i < mediaStatus.length(); ++i) {
-            JSONObject status = mediaStatus.getJSONObject(i);
-            status.put("sessionId", mSession.getSessionId());
-            if (!status.has("supportedMediaCommands")) continue;
-
-            JSONArray commands = new JSONArray();
-            int bitfieldCommands = status.getInt("supportedMediaCommands");
-            for (int j = 0; j < 4; ++j) {
-                if ((bitfieldCommands & (1 << j)) != 0) {
-                    commands.put(MEDIA_SUPPORTED_COMMANDS[j]);
-                }
-            }
-
-            status.put("supportedMediaCommands", commands);  // Removes current entry.
-        }
-    }
-
-    /**
-     * Remove 'null' fields from a JSONObject. This method calls itself recursively until all the
-     * fields have been looked at.
-     * TODO(mlamouri): move to some util class?
-     */
-    private static void removeNullFields(Object object) throws JSONException {
-        if (object instanceof JSONArray) {
-            JSONArray array = (JSONArray) object;
-            for (int i = 0; i < array.length(); ++i) removeNullFields(array.get(i));
-        } else if (object instanceof JSONObject) {
-            JSONObject json = (JSONObject) object;
-            JSONArray names = json.names();
-            if (names == null) return;
-            for (int i = 0; i < names.length(); ++i) {
-                String key = names.getString(i);
-                if (json.isNull(key)) {
-                    json.remove(key);
-                } else {
-                    removeNullFields(json.get(key));
-                }
-            }
-        }
-    }
-
-    @VisibleForTesting
-    boolean isMediaStatusMessage(String message) {
-        try {
-            JSONObject jsonMessage = new JSONObject(message);
-            return "MEDIA_STATUS".equals(jsonMessage.getString("type"));
-        } catch (JSONException e) {
-            return false;
-        }
-    }
-
-    private JSONArray toJSONArray(List<String> from) throws JSONException {
-        JSONArray result = new JSONArray();
-        for (String entry : from) {
-            result.put(entry);
-        }
-        return result;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSession.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSession.java
deleted file mode 100644
index d08f4df..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSession.java
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.router.cast;
-
-import android.support.annotation.Nullable;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.chromium.chrome.browser.media.router.FlingingController;
-
-import java.util.Set;
-
-/**
- * The interface for talking to the GMS core. We need to isolate this
- * interface from its implementation so that we can mock it with out
- * dependencies on the GMS core. Otherwise junit tests will fail on
- * the bots since they don't support GMS core for junit tests. For
- * example, see: https://crbug.com/588758
- */
-public interface CastSession {
-    /**
-     * The return type for {@link handleVolumeMessage}.
-     */
-    static class HandleVolumeMessageResult {
-        public final boolean mSucceeded;
-        public final boolean mShouldWaitForVolumeChange;
-
-        /**
-         * Initializes a {@link HandleVolumeMessageResult}.
-         */
-        public HandleVolumeMessageResult(boolean succeeded, boolean shouldWaitForVolumeChange) {
-            mSucceeded = succeeded;
-            mShouldWaitForVolumeChange = shouldWaitForVolumeChange;
-        }
-    }
-
-    /**
-     * @return If the gms core api client is invalid.
-     */
-    boolean isApiClientInvalid();
-
-    /**
-     * @return The sink id of the CastSession.
-     */
-    String getSinkId();
-
-    /**
-     * @return The source id of the CastSession.
-     */
-    String getSourceId();
-
-    /**
-     * @return The id of the CastSession.
-     */
-    String getSessionId();
-
-    /**
-     * @return The namespaces supported by the CastSession.
-     */
-    Set<String> getNamespaces();
-
-    /**
-     * @return The message handler of the CastSession.
-     */
-    CastMessageHandler getMessageHandler();
-
-    /**
-     * @return The session information.
-     */
-    CastSessionInfo getSessionInfo();
-
-    /**
-     * Sends the string message to the Cast device through the Cast SDK.
-     * @param message        The message to send.
-     * @param namespace      The namespace of the message.
-     * @param clientId       The id of the client sending the message.
-     * @param sequenceNumber The sequence number of the message, which is used for matching
-     *                       responses to requests.
-     */
-    boolean sendStringCastMessage(
-            String message, String namespace, String clientId, int sequenceNumber);
-
-    /**
-     * Handles SET_VOLUME messages, and sets the volume of the Cast device through the Cast SDK.
-     * @param volume         A JSONObject containing the volume information.
-     *                       Example:
-     *                       {
-     *                         "volume" {
-     *                           "level": 0.9,
-     *                           "muted": null
-     *                         }
-     *                       }
-     * @param clientId       The id of the client sending the message.
-     * @param sequenceNumber The sequence number of the message, which is used for matching
-     *                       responses to requests.
-     */
-    HandleVolumeMessageResult handleVolumeMessage(
-            JSONObject volume, String clientId, int sequenceNumber)
-            throws JSONException;
-
-    /**
-     * Stops the application. The methods tells the Cast SDK to stop the application and on
-     * response, it will notify all the clients through the message handler.
-     */
-    void stopApplication();
-
-    /**
-     * Perform proper actions when a client is connected to the session.
-     */
-    void onClientConnected(String clientId);
-
-    /**
-     * When a media message is received from the Cast device, forwards the message to the
-     * MediaPlayer.
-     * @param message The media message received from the Cast device.
-     */
-    void onMediaMessage(String message);
-
-    /**
-     * Perform proper actions when the Cast device volume has changed.
-     */
-    void onVolumeChanged();
-
-    /**
-     * Updates the session info when it changes and broadcast the change.
-     */
-    void updateSessionStatus();
-
-    /**
-     * Returns a controller for the media content.
-     */
-    @Nullable
-    FlingingController getFlingingController();
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionImpl.java
deleted file mode 100644
index 481069d8..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionImpl.java
+++ /dev/null
@@ -1,493 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.router.cast;
-
-import android.content.Intent;
-import android.support.annotation.Nullable;
-
-import com.google.android.gms.cast.ApplicationMetadata;
-import com.google.android.gms.cast.Cast;
-import com.google.android.gms.cast.CastDevice;
-import com.google.android.gms.cast.MediaStatus;
-import com.google.android.gms.cast.RemoteMediaPlayer;
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.common.api.ResultCallback;
-import com.google.android.gms.common.api.Status;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.chromium.base.Log;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.media.router.CastSessionUtil;
-import org.chromium.chrome.browser.media.router.FlingingController;
-import org.chromium.chrome.browser.media.router.MediaSource;
-import org.chromium.chrome.browser.media.ui.MediaNotificationInfo;
-import org.chromium.chrome.browser.media.ui.MediaNotificationListener;
-import org.chromium.chrome.browser.media.ui.MediaNotificationManager;
-import org.chromium.chrome.browser.metrics.MediaNotificationUma;
-import org.chromium.chrome.browser.tab.Tab;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * A wrapper around the established Cast application session.
- */
-public class CastSessionImpl implements MediaNotificationListener, CastSession {
-    private static final String TAG = "MediaRouter";
-
-    private static class CastMessagingChannel implements Cast.MessageReceivedCallback {
-        private final CastSession mSession;
-
-        public CastMessagingChannel(CastSessionImpl session) {
-            mSession = session;
-        }
-
-        @Override
-        public void onMessageReceived(CastDevice castDevice, String namespace, String message) {
-            Log.d(TAG, "Received message from Cast device: namespace=\"" + namespace
-                       + "\" message=\"" + message + "\"");
-            mSession.getMessageHandler().onMessageReceived(namespace, message);
-        }
-    }
-
-    private final CastMessagingChannel mMessageChannel;
-    private final CastDevice mCastDevice;
-    private final MediaSource mSource;
-    private final CastMessageHandler mMessageHandler;
-
-    private GoogleApiClient mApiClient;
-    private String mSessionId;
-    private String mApplicationStatus;
-    private ApplicationMetadata mApplicationMetadata;
-    private boolean mStoppingApplication;
-    private MediaNotificationInfo.Builder mNotificationBuilder;
-    private RemoteMediaPlayer mMediaPlayer;
-
-    private Set<String> mNamespaces = new HashSet<String>();
-
-    /**
-     * Initializes a new {@link CastSessionImpl} instance.
-     * @param apiClient The Google Play Services client used to create the session.
-     * @param sessionId The session identifier to use with the Cast SDK.
-     * @param origin The origin of the frame requesting the route.
-     * @param tabId The id of the tab containing the frame requesting the route.
-     * @param isIncognito Whether the route is beging requested from an Incognito profile.
-     * @param source The {@link MediaSource} corresponding to this session.
-     * @param routeProvider The {@link CastMediaRouteProvider} instance managing this session.
-     */
-    public CastSessionImpl(GoogleApiClient apiClient, String sessionId,
-            ApplicationMetadata metadata, String applicationStatus, CastDevice castDevice,
-            String origin, int tabId, boolean isIncognito, MediaSource source,
-            CastMessageHandler messageHandler) {
-        mSessionId = sessionId;
-        mApiClient = apiClient;
-        mSource = source;
-        mApplicationMetadata = metadata;
-        mApplicationStatus = applicationStatus;
-        mCastDevice = castDevice;
-        mMessageHandler = messageHandler;
-        mMessageChannel = new CastMessagingChannel(this);
-        updateNamespaces();
-
-        if (mNamespaces.contains(CastSessionUtil.MEDIA_NAMESPACE)) {
-            mMediaPlayer = new RemoteMediaPlayer();
-            mMediaPlayer.setOnStatusUpdatedListener(
-                    new RemoteMediaPlayer.OnStatusUpdatedListener() {
-                        @Override
-                        public void onStatusUpdated() {
-                            MediaStatus mediaStatus = mMediaPlayer.getMediaStatus();
-                            if (mediaStatus == null) return;
-
-                            int playerState = mediaStatus.getPlayerState();
-                            if (playerState == MediaStatus.PLAYER_STATE_PAUSED
-                                    || playerState == MediaStatus.PLAYER_STATE_PLAYING) {
-                                mNotificationBuilder.setPaused(
-                                        playerState != MediaStatus.PLAYER_STATE_PLAYING);
-                                mNotificationBuilder.setActions(MediaNotificationInfo.ACTION_STOP
-                                        | MediaNotificationInfo.ACTION_PLAY_PAUSE);
-                            } else {
-                                mNotificationBuilder.setActions(MediaNotificationInfo.ACTION_STOP);
-                            }
-                            MediaNotificationManager.show(mNotificationBuilder.build());
-                        }
-                    });
-            mMediaPlayer.setOnMetadataUpdatedListener(
-                    new RemoteMediaPlayer.OnMetadataUpdatedListener() {
-                        @Override
-                        public void onMetadataUpdated() {
-                            CastSessionUtil.setNotificationMetadata(
-                                    mNotificationBuilder, mCastDevice, mMediaPlayer);
-                            MediaNotificationManager.show(mNotificationBuilder.build());
-                        }
-                    });
-        }
-
-        Intent contentIntent = Tab.createBringTabToFrontIntent(tabId);
-        if (contentIntent != null) {
-            contentIntent.putExtra(MediaNotificationUma.INTENT_EXTRA_NAME,
-                    MediaNotificationUma.Source.PRESENTATION);
-        }
-        mNotificationBuilder =
-                new MediaNotificationInfo.Builder()
-                        .setPaused(false)
-                        .setOrigin(origin)
-                        // TODO(avayvod): the same session might have more than one tab id. Should
-                        // we track the last foreground alive tab and update the notification with
-                        // it?
-                        .setTabId(tabId)
-                        .setPrivate(isIncognito)
-                        .setActions(MediaNotificationInfo.ACTION_STOP)
-                        .setContentIntent(contentIntent)
-                        .setNotificationSmallIcon(R.drawable.ic_notification_media_route)
-                        .setDefaultNotificationLargeIcon(R.drawable.cast_playing_square)
-                        .setId(R.id.presentation_notification)
-                        .setListener(this);
-
-        CastSessionUtil.setNotificationMetadata(mNotificationBuilder, mCastDevice, mMediaPlayer);
-        MediaNotificationManager.show(mNotificationBuilder.build());
-    }
-
-    /////////////////////////////////////////////////////////////////////////////////////////////
-    // MediaNotificationListener implementation.
-
-    @Override
-    public void onPlay(int actionSource) {
-        if (mMediaPlayer == null || isApiClientInvalid()) return;
-
-        mMediaPlayer.play(mApiClient);
-    }
-
-    @Override
-    public void onPause(int actionSource) {
-        if (mMediaPlayer == null || isApiClientInvalid()) return;
-
-        mMediaPlayer.pause(mApiClient);
-    }
-
-    @Override
-    public void onStop(int actionSource) {
-        stopApplication();
-        ChromeCastSessionManager.get().onSessionStopAction();
-    }
-
-    @Override
-    public void onMediaSessionAction(int action) {}
-
-    /////////////////////////////////////////////////////////////////////////////////////////////
-    // Utility functions.
-
-    /**
-     * @param device The {@link CastDevice} queried for it's capabilities.
-     * @return The capabilities of the Cast device.
-     * TODO(zqzhang): move to a CastUtils class?
-     */
-    protected static List<String> getCapabilities(CastDevice device) {
-        List<String> capabilities = new ArrayList<String>();
-        if (device.hasCapability(CastDevice.CAPABILITY_AUDIO_IN)) {
-            capabilities.add("audio_in");
-        }
-        if (device.hasCapability(CastDevice.CAPABILITY_AUDIO_OUT)) {
-            capabilities.add("audio_out");
-        }
-        if (device.hasCapability(CastDevice.CAPABILITY_VIDEO_IN)) {
-            capabilities.add("video_in");
-        }
-        if (device.hasCapability(CastDevice.CAPABILITY_VIDEO_OUT)) {
-            capabilities.add("video_out");
-        }
-        return capabilities;
-    }
-
-    /////////////////////////////////////////////////////////////////////////////////////////////
-    // Namespace handling.
-
-    private void updateNamespaces() {
-        if (mApplicationMetadata == null) return;
-
-        List<String> newNamespaces = mApplicationMetadata.getSupportedNamespaces();
-
-        Set<String> toRemove = new HashSet<String>(mNamespaces);
-        toRemove.removeAll(newNamespaces);
-        for (String namespaceToRemove : toRemove) unregisterNamespace(namespaceToRemove);
-
-        for (String newNamespace : newNamespaces) {
-            if (!mNamespaces.contains(newNamespace)) addNamespace(newNamespace);
-        }
-    }
-
-    private void addNamespace(String namespace) {
-        assert !mNamespaces.contains(namespace);
-
-        if (isApiClientInvalid()) return;
-
-        // If application metadata is null, register the callback anyway.
-        if (mApplicationMetadata != null && !mApplicationMetadata.isNamespaceSupported(namespace)) {
-            return;
-        }
-
-        try {
-            Cast.CastApi.setMessageReceivedCallbacks(mApiClient, namespace, mMessageChannel);
-            mNamespaces.add(namespace);
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to register namespace listener for %s", namespace, e);
-        }
-    }
-
-    private void unregisterNamespace(String namespace) {
-        assert mNamespaces.contains(namespace);
-
-        if (isApiClientInvalid()) return;
-
-        try {
-            Cast.CastApi.removeMessageReceivedCallbacks(mApiClient, namespace);
-            mNamespaces.remove(namespace);
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to remove the namespace listener for %s", namespace, e);
-        }
-    }
-
-    /////////////////////////////////////////////////////////////////////////////////////////////
-    // CastSession implementations.
-
-    @Override
-    public boolean isApiClientInvalid() {
-        return mApiClient == null || !mApiClient.isConnected();
-    }
-
-    @Override
-    public String getSourceId() {
-        return mSource.getSourceId();
-    }
-
-    @Override
-    public String getSinkId() {
-        return mCastDevice.getDeviceId();
-    }
-
-    @Override
-    public String getSessionId() {
-        return mSessionId;
-    }
-
-    @Override
-    public Set<String> getNamespaces() {
-        return mNamespaces;
-    }
-
-    @Override
-    public CastMessageHandler getMessageHandler() {
-        return mMessageHandler;
-    }
-
-    @Override
-    public CastSessionInfo getSessionInfo() {
-        if (isApiClientInvalid()) return null;
-
-        // Due to a Google Play Services issue, the api client might still get disconnected so that
-        // calls like {@link Cast.CastApi#getVolume()} will throw an {@link IllegalStateException}.
-        // Until the issue is fixed, catch the exception and return null if it was thrown instead of
-        // crashing. See https://crbug.com/708964 for details.
-        try {
-            CastSessionInfo.VolumeInfo.Builder volumeBuilder =
-                    new CastSessionInfo.VolumeInfo.Builder()
-                            .setLevel(Cast.CastApi.getVolume(mApiClient))
-                            .setMuted(Cast.CastApi.isMute(mApiClient));
-
-            CastSessionInfo.ReceiverInfo.Builder receiverBuilder =
-                    new CastSessionInfo.ReceiverInfo.Builder()
-                            .setLabel(mCastDevice.getDeviceId())
-                            .setFriendlyName(mCastDevice.getFriendlyName())
-                            .setVolume(volumeBuilder.build())
-                            .setIsActiveInput(Cast.CastApi.getActiveInputState(mApiClient))
-                            .setDisplayStatus(null)
-                            .setReceiverType("cast")
-                            .addCapabilities(getCapabilities(mCastDevice));
-
-            CastSessionInfo.Builder sessionInfoBuilder =
-                    new CastSessionInfo.Builder()
-                            .setSessionId(mSessionId)
-                            .setStatusText(mApplicationStatus)
-                            .setReceiver(receiverBuilder.build())
-                            .setStatus("connected")
-                            .setTransportId("web-4")
-                            .addNamespaces(mNamespaces);
-
-            if (mApplicationMetadata != null) {
-                sessionInfoBuilder.setAppId(mApplicationMetadata.getApplicationId())
-                        .setDisplayName(mApplicationMetadata.getName());
-            } else {
-                sessionInfoBuilder.setAppId(mSource.getApplicationId())
-                        .setDisplayName(mCastDevice.getFriendlyName());
-            }
-
-            return sessionInfoBuilder.build();
-        } catch (IllegalStateException e) {
-            Log.e(TAG, "Couldn't get session info", e);
-            return null;
-        }
-    }
-
-    @Override
-    public boolean sendStringCastMessage(
-            final String message,
-            final String namespace,
-            final String clientId,
-            final int sequenceNumber) {
-        if (isApiClientInvalid()) return false;
-        Log.d(TAG, "Sending message to Cast device in namespace %s: %s", namespace, message);
-
-        try {
-            Cast.CastApi.sendMessage(mApiClient, namespace, message)
-                    .setResultCallback(
-                            new ResultCallback<Status>() {
-                                @Override
-                                public void onResult(Status result) {
-                                    if (!result.isSuccess()) {
-                                        // TODO(avayvod): should actually report back to the page.
-                                        // See https://crbug.com/550445.
-                                        Log.e(TAG, "Failed to send the message: " + result);
-                                        return;
-                                    }
-
-                                    // Media commands wait for the media status update as a result.
-                                    if (CastSessionUtil.MEDIA_NAMESPACE.equals(namespace)) return;
-
-                                    // App messages wait for the empty message with the sequence
-                                    // number.
-                                    mMessageHandler.onAppMessageSent(clientId, sequenceNumber);
-                                }
-                            });
-        } catch (Exception e) {
-            Log.e(TAG, "Exception while sending message", e);
-            return false;
-        }
-        return true;
-    }
-
-    // SET_VOLUME messages have a |level| and |muted| properties. One of them is
-    // |null| and the other one isn't. |muted| is expected to be a boolean while
-    // |level| is a float from 0.0 to 1.0.
-    // Example:
-    // {
-    //   "volume" {
-    //     "level": 0.9,
-    //     "muted": null
-    //   }
-    // }
-    @Override
-    public HandleVolumeMessageResult handleVolumeMessage(
-            JSONObject volume, final String clientId, final int sequenceNumber)
-            throws JSONException {
-        if (volume == null) return new HandleVolumeMessageResult(false, false);
-
-        if (isApiClientInvalid()) return new HandleVolumeMessageResult(false, false);
-
-        boolean waitForVolumeChange = false;
-        try {
-            if (!volume.isNull("muted")) {
-                boolean newMuted = volume.getBoolean("muted");
-                if (Cast.CastApi.isMute(mApiClient) != newMuted) {
-                    Cast.CastApi.setMute(mApiClient, newMuted);
-                    waitForVolumeChange = true;
-                }
-            }
-            if (!volume.isNull("level")) {
-                double newLevel = volume.getDouble("level");
-                double currentLevel = Cast.CastApi.getVolume(mApiClient);
-                if (!Double.isNaN(currentLevel)
-                        && Math.abs(currentLevel - newLevel)
-                                > CastSessionUtil.MIN_VOLUME_LEVEL_DELTA) {
-                    Cast.CastApi.setVolume(mApiClient, newLevel);
-                    waitForVolumeChange = true;
-                }
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to send volume command: " + e);
-            return new HandleVolumeMessageResult(false, false);
-        }
-
-        return new HandleVolumeMessageResult(true, waitForVolumeChange);
-    }
-
-    @Override
-    public void stopApplication() {
-        if (mStoppingApplication) return;
-
-        if (isApiClientInvalid()) return;
-
-        mStoppingApplication = true;
-        Cast.CastApi.stopApplication(mApiClient, mSessionId)
-                .setResultCallback(new ResultCallback<Status>() {
-                    @Override
-                    public void onResult(Status status) {
-                        mMessageHandler.onApplicationStopped();
-                        // TODO(avayvod): handle a failure to stop the application.
-                        // https://crbug.com/535577
-
-                        Set<String> namespaces = new HashSet<String>(mNamespaces);
-                        for (String namespace : namespaces) unregisterNamespace(namespace);
-                        mNamespaces.clear();
-
-                        mSessionId = null;
-                        mApiClient = null;
-
-                        ChromeCastSessionManager.get().onSessionEnded();
-                        mStoppingApplication = false;
-
-                        MediaNotificationManager.clear(R.id.presentation_notification);
-                    }
-                });
-    }
-
-    @Override
-    public void onMediaMessage(String message) {
-        if (mMediaPlayer != null) {
-            mMediaPlayer.onMessageReceived(mCastDevice, CastSessionUtil.MEDIA_NAMESPACE, message);
-        }
-    }
-
-    @Override
-    public void onVolumeChanged() {
-        mMessageHandler.onVolumeChanged();
-    }
-
-    @Override
-    public void updateSessionStatus() {
-        if (isApiClientInvalid()) return;
-
-        try {
-            mApplicationStatus = Cast.CastApi.getApplicationStatus(mApiClient);
-            mApplicationMetadata = Cast.CastApi.getApplicationMetadata(mApiClient);
-
-            updateNamespaces();
-
-            mMessageHandler.broadcastClientMessage(
-                    "update_session", mMessageHandler.buildSessionMessage());
-        } catch (IllegalStateException e) {
-            Log.e(TAG, "Can't get application status", e);
-        }
-    }
-
-    @Override
-    public void onClientConnected(String clientId) {
-        mMessageHandler.sendClientMessageTo(
-                clientId, "new_session", mMessageHandler.buildSessionMessage(),
-                CastMessageHandler.INVALID_SEQUENCE_NUMBER);
-
-        if (mMediaPlayer != null && !isApiClientInvalid()) mMediaPlayer.requestStatus(mApiClient);
-    }
-
-    @Override
-    @Nullable
-    public FlingingController getFlingingController() {
-        // FlingingController is not used with the CastSessionImpl.
-        return null;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionInfo.java
deleted file mode 100644
index 3ef11cf..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionInfo.java
+++ /dev/null
@@ -1,324 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.router.cast;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * The metadata information of {@link CastSession}.
- */
-public class CastSessionInfo {
-    /**
-     * The volume information of receivers.
-     */
-    public static class VolumeInfo {
-        /**
-         * The receiver volume.
-         */
-        public final double level;
-        /**
-         * Whether the receiver is muted.
-         */
-        public final boolean muted;
-
-        /**
-         * Use this class to construct an instance of {@link VolumeInfo}.
-         */
-        public static class Builder {
-            private double mLevel;
-            private boolean mMuted;
-
-            public Builder setLevel(double level) {
-                mLevel = level;
-                return this;
-            }
-
-            public Builder setMuted(boolean muted) {
-                mMuted = muted;
-                return this;
-            }
-
-            /**
-             * Initializes the builder with the default values.
-             */
-            public Builder() {
-            }
-
-            public VolumeInfo build() {
-                return new VolumeInfo(mLevel, mMuted);
-            }
-        }
-
-        private VolumeInfo(double level, boolean muted) {
-            this.level = level;
-            this.muted = muted;
-        }
-    }
-
-    /**
-     * The receiver information.
-     */
-    public static class ReceiverInfo {
-        /**
-         * Label of the receiver.
-         */
-        public final String label;
-        /**
-         * The friendly name of the receiver.
-         */
-        public final String friendlyName;
-        /**
-         * Receiver capabilities.
-         */
-        public final List<String> capabilities;
-        /**
-         * Receiver volume information.
-         */
-        public final VolumeInfo volume;
-        /**
-         * The receiver device's active-input state.
-         */
-        public final int isActiveInput;
-        /**
-         * The display status of the receiver.
-         */
-        public final String displayStatus;
-        /**
-         * The type of the receiver.
-         */
-        public final String receiverType;
-
-        /**
-         * Use this class to create an instance of {@link ReceiverInfo}.
-         */
-        public static class Builder {
-            private String mLabel = "";
-            private String mFriendlyName = "";
-            private List<String> mCapabilities = new ArrayList<String>();
-            private VolumeInfo mVolume;
-            private int mIsActiveInput;
-            private String mDisplayStatus = "";
-            private String mReceiverType = "";
-
-            public Builder setLabel(String label) {
-                mLabel = label;
-                return this;
-            }
-
-            public Builder setFriendlyName(String friendlyName) {
-                mFriendlyName = friendlyName;
-                return this;
-            }
-
-            public Builder addCapability(String capability) {
-                mCapabilities.add(capability);
-                return this;
-            }
-
-            public Builder addCapabilities(Collection<String> capabilities) {
-                mCapabilities.addAll(capabilities);
-                return this;
-            }
-
-            public Builder setVolume(VolumeInfo volume) {
-                mVolume = volume;
-                return this;
-            }
-
-            public Builder setIsActiveInput(int isActiveInput) {
-                mIsActiveInput = isActiveInput;
-                return this;
-            }
-
-            public Builder setDisplayStatus(String displayStatus) {
-                mDisplayStatus = displayStatus;
-                return this;
-            }
-
-            public Builder setReceiverType(String receiverType) {
-                mReceiverType = receiverType;
-                return this;
-            }
-
-            /**
-             * Initializes the builder with the default values.
-             */
-            public Builder() {
-            }
-
-            public ReceiverInfo build() {
-                return new ReceiverInfo(
-                        mLabel,
-                        mFriendlyName,
-                        mCapabilities,
-                        mVolume,
-                        mIsActiveInput,
-                        mDisplayStatus,
-                        mReceiverType);
-            }
-        }
-
-        private ReceiverInfo(
-                String label,
-                String friendlyName,
-                List<String> capabilities,
-                VolumeInfo volume,
-                int isActiveInput,
-                String displayStatus,
-                String receiverType) {
-            this.label = label;
-            this.friendlyName = friendlyName;
-            this.capabilities = capabilities;
-            this.volume = volume;
-            this.isActiveInput = isActiveInput;
-            this.displayStatus = displayStatus;
-            this.receiverType = receiverType;
-        }
-    }
-
-    /**
-     * The id of the {@link CastSession}.
-     */
-    public final String sessionId;
-    /**
-     * The application status (in Cast SDK) of the {@link CastSession}.
-     */
-    public final String statusText;
-    /**
-     * The receiver information of the {@link CastSession}.
-     */
-    public final ReceiverInfo receiver;
-    /**
-     * The namespaces registered in the {@link CastSession}.
-     */
-    public final List<String> namespaces;
-    /**
-     * The media in the {@link CastSession}.
-     */
-    public final List<String> media;
-    /**
-     * The status of the {@link CastSession}.
-     */
-    public final String status;
-    /**
-     * The tranport id of the {@link CastSession}.
-     */
-    public final String transportId;
-    /**
-     * The app id of the {@link CastSession}.
-     */
-    public final String appId;
-    /**
-     * The display name of the {@link CastSession}.
-     */
-    public final String displayName;
-
-    /**
-     * Use this class to create an instance of {@link CastSessionInfo}.
-     */
-    public static class Builder {
-        private String mSessionId = "";
-        private String mStatusText = "";
-        private ReceiverInfo mReceiver;
-        private List<String> mNamespaces = new ArrayList<String>();
-        private List<String> mMedia = new ArrayList<String>();
-        private String mStatus = "";
-        private String mTransportId = "";
-        private String mAppId = "";
-        private String mDisplayName = "";
-
-        public Builder setSessionId(String sessionId) {
-            mSessionId = sessionId;
-            return this;
-        }
-
-        public Builder setStatusText(String statusText) {
-            mStatusText = statusText;
-            return this;
-        }
-
-        public Builder setReceiver(ReceiverInfo receiver) {
-            mReceiver = receiver;
-            return this;
-        }
-
-        public Builder addNamespace(String namespace) {
-            mNamespaces.add(namespace);
-            return this;
-        }
-
-        public Builder addNamespaces(Collection<String> namespaces) {
-            mNamespaces.addAll(namespaces);
-            return this;
-        }
-
-        public Builder addMedia(String namespace) {
-            mMedia.add(namespace);
-            return this;
-        }
-
-        public Builder setStatus(String status) {
-            mStatus = status;
-            return this;
-        }
-
-        public Builder setTransportId(String transportId) {
-            mTransportId = transportId;
-            return this;
-        }
-
-        public Builder setAppId(String appId) {
-            mAppId = appId;
-            return this;
-        }
-
-        public Builder setDisplayName(String displayName) {
-            mDisplayName = displayName;
-            return this;
-        }
-
-        /**
-         * Initializes the builder with the default values.
-         */
-        public Builder() {
-        }
-
-        public CastSessionInfo build() {
-            return new CastSessionInfo(
-                    mSessionId,
-                    mStatusText,
-                    mReceiver,
-                    mNamespaces,
-                    mMedia,
-                    mStatus,
-                    mTransportId,
-                    mAppId,
-                    mDisplayName);
-        }
-    }
-
-    private CastSessionInfo(
-            String sessionId,
-            String statusText,
-            ReceiverInfo receiver,
-            List<String> namespaces,
-            List<String> media,
-            String status,
-            String transportId,
-            String appId,
-            String displayName) {
-        this.sessionId = sessionId;
-        this.statusText = statusText;
-        this.receiver = receiver;
-        this.namespaces = namespaces;
-        this.media = media;
-        this.status = status;
-        this.transportId = transportId;
-        this.appId = appId;
-        this.displayName = displayName;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/ChromeCastSessionManager.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/ChromeCastSessionManager.java
deleted file mode 100644
index 8131a74..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/ChromeCastSessionManager.java
+++ /dev/null
@@ -1,231 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.router.cast;
-
-import android.annotation.SuppressLint;
-
-import com.google.android.gms.cast.ApplicationMetadata;
-import com.google.android.gms.cast.Cast;
-import com.google.android.gms.cast.CastStatusCodes;
-
-import org.chromium.base.Log;
-
-/**
- * Manages the lifetime of the active CastSessions and broadcasts state changes to observers.
- */
-public class ChromeCastSessionManager {
-    private static final String TAG = "cr_CastSessionMgr";
-
-    // Singleton instance.
-    private static ChromeCastSessionManager sInstance;
-
-    /**
-     * Defines the events relevant to CastSession state changes.
-     * NOTE: This interface is modeled after Cast SDK V3's SessionManagerListener, but does not map
-     * exactly to it.
-     */
-    public interface CastSessionManagerListener {
-        // Called right before a session launch is started.
-        public void onSessionStarting(
-                ChromeCastSessionManager.CastSessionLaunchRequest originalRequest);
-
-        // Called after a successful session launch.
-        public void onSessionStarted(CastSession session);
-
-        // Called after a failed session launch.
-        public void onSessionStartFailed();
-
-        // Called when a session is shutting down (as a result of user action, or when a new
-        // session is being launched).
-        public void onSessionStopAction();
-
-        // Called after a session has shut down.
-        public void onSessionEnded();
-    }
-
-    /**
-     * Encapsulates a request to start a cast session. Mostly used to simplify testing.
-     */
-    public interface CastSessionLaunchRequest {
-        // Starts the request.
-        // Success or failure will be reported to the ChromeCastSessionManager via the
-        // onSessionStarted() and onSessionStartFailed() calls.
-        public void start(Cast.Listener listener);
-
-        // Gets the session observer that should be notified of the session launch.
-        public CastSessionManagerListener getSessionListener();
-    }
-
-    private class CastListener extends Cast.Listener {
-        private CastSession mSession;
-
-        CastListener() {}
-
-        void setSession(CastSession session) {
-            mSession = session;
-        }
-
-        @Override
-        public void onApplicationStatusChanged() {
-            if (mSession == null) return;
-
-            mSession.updateSessionStatus();
-        }
-
-        @Override
-        public void onApplicationMetadataChanged(ApplicationMetadata metadata) {
-            if (mSession == null) return;
-
-            mSession.updateSessionStatus();
-        }
-
-        // TODO(https://crbug.com/635567): Fix this properly.
-        @Override
-        @SuppressLint("DefaultLocale")
-        public void onApplicationDisconnected(int errorCode) {
-            if (errorCode != CastStatusCodes.SUCCESS) {
-                Log.e(TAG, String.format("Application disconnected with: %d", errorCode));
-            }
-
-            // This callback can be called more than once if the application is stopped from Chrome.
-            if (mSession == null) return;
-
-            mSession.stopApplication();
-            mSession = null;
-        }
-
-        @Override
-        public void onVolumeChanged() {
-            if (mSession == null) return;
-
-            mSession.onVolumeChanged();
-        }
-    }
-
-    // The current active session. There can only be one active session on Android.
-    // NOTE: This is a Chromium abstraction, different from the Cast SDK CastSession.
-    private CastSession mSession;
-
-    // Listener tied to |mSession|.
-    private CastListener mListener;
-
-    // Request to be started once |mSession| is stopped.
-    private CastSessionLaunchRequest mPendingSessionLaunchRequest;
-
-    // Object that initiated the current cast session and that should be notified
-    // of changes to the session.
-    private CastSessionManagerListener mCurrentSessionListener;
-
-    // Whether we are currently in the process of launching a session.
-    private boolean mSessionLaunching;
-
-    public static ChromeCastSessionManager get() {
-        if (sInstance == null) sInstance = new ChromeCastSessionManager();
-
-        return sInstance;
-    }
-
-    /**
-     * Should only be used for testing purposes.
-     */
-    public static void resetInstanceForTesting() {
-        sInstance = null;
-    }
-
-    private ChromeCastSessionManager() {}
-
-    /**
-     * Called after a session successfully launched and was created.
-     * @param session the newly created session.
-     */
-    public void onSessionStarted(CastSession session) {
-        assert mSession == null;
-
-        mSession = session;
-        mSessionLaunching = false;
-        mCurrentSessionListener.onSessionStarted(session);
-        mListener.setSession(session);
-    }
-
-    /**
-     * Called to initiate a new session launch. Will stop the current session if it exists.
-     * Calls back into onSessionStarting().
-     * Calling this method when we already have a session will stop that session first.
-     * Requests will be dropped if we are in the process of launching a new session.
-     * @param request the encapsulated information required to launch the session.
-     */
-    public void requestSessionLaunch(CastSessionLaunchRequest request) {
-        // Do not attempt to launch more than one session at a time.
-        if (mSessionLaunching) return;
-
-        // Since we only can only have one session, close it before starting a new one.
-        if (mSession != null) {
-            mPendingSessionLaunchRequest = request;
-            mSession.stopApplication();
-            return;
-        }
-
-        launchSession(request);
-    }
-
-    /**
-     * Stops the current session.
-     */
-    public void stopApplication() {
-        if (mSession != null) mSession.stopApplication();
-    }
-
-    /**
-     * Starts the session request.
-     * Will result in a call to onSessionStarted() or onSessionStartFailed() based on the
-     * success/failure of the launch.
-     */
-    private void launchSession(CastSessionLaunchRequest request) {
-        assert mSession == null;
-
-        mSessionLaunching = true;
-
-        mCurrentSessionListener = request.getSessionListener();
-        mCurrentSessionListener.onSessionStarting(request);
-
-        mListener = new CastListener();
-
-        request.start(mListener);
-    }
-
-    /**
-     * Called when the session has received a stop action.
-     */
-    public void onSessionStopAction() {
-        mCurrentSessionListener.onSessionStopAction();
-    }
-
-    /**
-     * Called when the session has ended. The session may no longer be valid at this point.
-     */
-    public void onSessionEnded() {
-        mCurrentSessionListener.onSessionEnded();
-
-        mSession = null;
-        mCurrentSessionListener = null;
-        mListener = null;
-
-        if (mPendingSessionLaunchRequest != null) {
-            launchSession(mPendingSessionLaunchRequest);
-            mPendingSessionLaunchRequest = null;
-        }
-    }
-
-    /**
-     * Called when the session has failed to launch.
-     */
-    public void onSessionStartFailed() {
-        mCurrentSessionListener.onSessionStartFailed();
-        mCurrentSessionListener = null;
-        mListener = null;
-
-        mSessionLaunching = false;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CreateRouteRequest.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CreateRouteRequest.java
deleted file mode 100644
index 3f00e9b43..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CreateRouteRequest.java
+++ /dev/null
@@ -1,286 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.router.cast;
-
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-import android.support.annotation.IntDef;
-import android.support.annotation.Nullable;
-
-import com.google.android.gms.cast.Cast;
-import com.google.android.gms.cast.LaunchOptions;
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.common.api.PendingResult;
-import com.google.android.gms.common.api.ResultCallback;
-import com.google.android.gms.common.api.Status;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.Log;
-import org.chromium.chrome.browser.media.router.ChromeMediaRouter;
-import org.chromium.chrome.browser.media.router.MediaRoute;
-import org.chromium.chrome.browser.media.router.MediaSink;
-import org.chromium.chrome.browser.media.router.MediaSource;
-import org.chromium.chrome.browser.media.router.cast.remoting.RemotingCastSession;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Establishes a {@link MediaRoute} by starting a Cast application represented by the given
- * presentation URL. Reports success or failure to {@link ChromeMediaRouter}.
- * Since there're numerous asynchronous calls involved in getting the application to launch
- * the class is implemented as a state machine.
- */
-// Migrated to CafMediaRouteProvider. See https://crbug.com/711860.
-public class CreateRouteRequest implements GoogleApiClient.ConnectionCallbacks,
-                                           GoogleApiClient.OnConnectionFailedListener,
-                                           ResultCallback<Cast.ApplicationConnectionResult>,
-                                           ChromeCastSessionManager.CastSessionLaunchRequest {
-    private static final String TAG = "MediaRouter";
-
-    @IntDef({State.IDLE, State.CONNECTING_TO_API, State.API_CONNECTION_SUSPENDED,
-            State.LAUNCHING_APPLICATION, State.LAUNCH_SUCCEEDED, State.TERMINATED})
-    @Retention(RetentionPolicy.SOURCE)
-    private @interface State {
-        int IDLE = 0;
-        int CONNECTING_TO_API = 1;
-        int API_CONNECTION_SUSPENDED = 2;
-        int LAUNCHING_APPLICATION = 3;
-        int LAUNCH_SUCCEEDED = 4;
-        int TERMINATED = 5;
-    }
-
-    private final MediaSource mSource;
-    private final MediaSink mSink;
-    private final String mPresentationId;
-    private final String mOrigin;
-    private final int mTabId;
-    private final boolean mIsIncognito;
-    private final int mRequestId;
-    private final CastMessageHandler mMessageHandler;
-    private final ChromeCastSessionManager.CastSessionManagerListener mSessionListener;
-    private final @RequestedCastSessionType int mSessionType;
-
-    private GoogleApiClient mApiClient;
-    private @State int mState = State.IDLE;
-
-    // Used to identify whether the request should launch a CastSessionImpl or a RemotingCastSession
-    // (based off of wheter the route creation was requested by a RemotingMediaRouteProvider or a
-    // CastMediaRouteProvider).
-    @IntDef({RequestedCastSessionType.CAST, RequestedCastSessionType.REMOTE})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface RequestedCastSessionType {
-        int CAST = 0;
-        int REMOTE = 1;
-    }
-
-    /**
-     * Initializes the request.
-     * @param source The {@link MediaSource} defining the application to launch on the Cast device.
-     * @param sink The {@link MediaSink} identifying the selected Cast device.
-     * @param presentationId The presentation id assigned to the route by {@link ChromeMediaRouter}.
-     * @param origin The origin of the frame requesting the route.
-     * @param tabId The id of the tab containing the frame requesting the route.
-     * @param isIncognito Whether the route is being requested from an Incognito profile.
-     * @param requestId The id of the route creation request for tracking by
-     * {@link ChromeMediaRouter}.
-     * @param listener The listener that should be notified of session/route creation changes.
-     * @param messageHandler A message handler (used to create CastSessionImpl instances).
-     */
-    public CreateRouteRequest(MediaSource source, MediaSink sink, String presentationId,
-            String origin, int tabId, boolean isIncognito, int requestId,
-            ChromeCastSessionManager.CastSessionManagerListener listener,
-            @RequestedCastSessionType int sessionType,
-            @Nullable CastMessageHandler messageHandler) {
-        assert source != null;
-        assert sink != null;
-
-        mSource = source;
-        mSink = sink;
-        mPresentationId = presentationId;
-        mOrigin = origin;
-        mTabId = tabId;
-        mIsIncognito = isIncognito;
-        mRequestId = requestId;
-        mSessionListener = listener;
-        mSessionType = sessionType;
-        mMessageHandler = messageHandler;
-    }
-
-    public MediaSource getSource() {
-        return mSource;
-    }
-
-    public MediaSink getSink() {
-        return mSink;
-    }
-
-    public String getPresentationId() {
-        return mPresentationId;
-    }
-
-    public String getOrigin() {
-        return mOrigin;
-    }
-
-    public int getTabId() {
-        return mTabId;
-    }
-
-    public boolean isIncognito() {
-        return mIsIncognito;
-    }
-
-    public int getNativeRequestId() {
-        return mRequestId;
-    }
-
-    /////////////////////////////////////////////////////////////////////////////////////////////
-    // ChromeCastSessionManager.CastSessionLaunchRequest implementation.
-
-    /**
-     * Returns the object that should be notified of session changes that result
-     * from this route creation request.
-     */
-    @Override
-    public ChromeCastSessionManager.CastSessionManagerListener getSessionListener() {
-        return mSessionListener;
-    }
-
-    /**
-     * Starts the process of launching the application on the Cast device.
-     */
-    @Override
-    public void start(Cast.Listener castListener) {
-        if (mState != State.IDLE) throwInvalidState();
-
-        mApiClient = createApiClient(castListener);
-        mApiClient.connect();
-        mState = State.CONNECTING_TO_API;
-    }
-
-    /////////////////////////////////////////////////////////////////////////////////////////////
-    // GoogleApiClient.* implementations.
-
-    @Override
-    public void onConnected(Bundle connectionHint) {
-        if (mState != State.CONNECTING_TO_API && mState != State.API_CONNECTION_SUSPENDED) {
-            throwInvalidState();
-        }
-
-        if (mState == State.API_CONNECTION_SUSPENDED) return;
-
-        try {
-            launchApplication(mApiClient, mSource.getApplicationId(), true)
-                    .setResultCallback(this);
-            mState = State.LAUNCHING_APPLICATION;
-        } catch (Exception e) {
-            Log.e(TAG, "Launch application failed: %s", mSource.getApplicationId(), e);
-            reportError();
-        }
-    }
-
-    @Override
-    public void onConnectionSuspended(int cause) {
-        mState = State.API_CONNECTION_SUSPENDED;
-    }
-
-    @Override
-    public void onConnectionFailed(ConnectionResult result) {
-        if (mState != State.CONNECTING_TO_API) throwInvalidState();
-
-        Log.e(TAG, "GoogleApiClient connection failed: %d, %b", result.getErrorCode(),
-                result.hasResolution());
-        reportError();
-    }
-
-    /**
-     * ResultCallback<Cast.ApplicationConnectionResult> implementation.
-     */
-    @Override
-    public void onResult(Cast.ApplicationConnectionResult result) {
-        if (mState != State.LAUNCHING_APPLICATION && mState != State.API_CONNECTION_SUSPENDED) {
-            throwInvalidState();
-        }
-
-        Status status = result.getStatus();
-        if (!status.isSuccess()) {
-            Log.e(TAG, "Launch application failed with status: %s, %d, %s",
-                    mSource.getApplicationId(), status.getStatusCode(), status.getStatusMessage());
-            reportError();
-            return;
-        }
-
-        mState = State.LAUNCH_SUCCEEDED;
-        reportSuccess(result);
-    }
-
-    private GoogleApiClient createApiClient(Cast.Listener listener) {
-        Cast.CastOptions.Builder apiOptionsBuilder =
-                new Cast.CastOptions.Builder(mSink.getDevice(), listener)
-                         // TODO(avayvod): hide this behind the flag or remove
-                         .setVerboseLoggingEnabled(true);
-
-        return new GoogleApiClient.Builder(ContextUtils.getApplicationContext())
-                .addApi(Cast.API, apiOptionsBuilder.build())
-                .addConnectionCallbacks(this)
-                .addOnConnectionFailedListener(this)
-                .build();
-    }
-
-    private PendingResult<Cast.ApplicationConnectionResult> launchApplication(
-            GoogleApiClient apiClient,
-            String appId,
-            boolean relaunchIfRunning) {
-        LaunchOptions.Builder builder = new LaunchOptions.Builder();
-        return Cast.CastApi.launchApplication(apiClient, appId,
-                builder.setRelaunchIfRunning(relaunchIfRunning)
-                        .build());
-    }
-
-    // TODO(crbug.com/635567): Fix this properly.
-    @SuppressLint("DefaultLocale")
-    private void throwInvalidState() {
-        throw new RuntimeException(String.format("Invalid state: %d", mState));
-    }
-
-    private void reportSuccess(Cast.ApplicationConnectionResult result) {
-        if (mState != State.LAUNCH_SUCCEEDED) throwInvalidState();
-
-        CastSession session = null;
-
-        switch (mSessionType) {
-            case RequestedCastSessionType.CAST:
-                session = new CastSessionImpl(mApiClient, result.getSessionId(),
-                        result.getApplicationMetadata(), result.getApplicationStatus(),
-                        mSink.getDevice(), mOrigin, mTabId, mIsIncognito, mSource, mMessageHandler);
-                break;
-            case RequestedCastSessionType.REMOTE:
-                session = new RemotingCastSession(mApiClient, result.getSessionId(),
-                        result.getApplicationMetadata(), result.getApplicationStatus(),
-                        mSink.getDevice(), mOrigin, mTabId, mIsIncognito, mSource);
-                break;
-        }
-
-        ChromeCastSessionManager.get().onSessionStarted(session);
-
-        terminate();
-    }
-
-    private void reportError() {
-        if (mState == State.TERMINATED) throwInvalidState();
-
-        ChromeCastSessionManager.get().onSessionStartFailed();
-
-        terminate();
-    }
-
-    private void terminate() {
-        mApiClient.unregisterConnectionCallbacks(this);
-        mApiClient.unregisterConnectionFailedListener(this);
-        mState = State.TERMINATED;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingCastSession.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingCastSession.java
deleted file mode 100644
index 69c64a2d..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingCastSession.java
+++ /dev/null
@@ -1,219 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.router.cast.remoting;
-
-import com.google.android.gms.cast.ApplicationMetadata;
-import com.google.android.gms.cast.Cast;
-import com.google.android.gms.cast.CastDevice;
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.common.api.ResultCallback;
-import com.google.android.gms.common.api.Status;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.chromium.base.Log;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.media.remote.RemoteMediaPlayerWrapper;
-import org.chromium.chrome.browser.media.router.CastSessionUtil;
-import org.chromium.chrome.browser.media.router.FlingingController;
-import org.chromium.chrome.browser.media.router.MediaSource;
-import org.chromium.chrome.browser.media.router.cast.CastMessageHandler;
-import org.chromium.chrome.browser.media.router.cast.CastSession;
-import org.chromium.chrome.browser.media.router.cast.CastSessionInfo;
-import org.chromium.chrome.browser.media.router.cast.ChromeCastSessionManager;
-import org.chromium.chrome.browser.media.ui.MediaNotificationInfo;
-import org.chromium.chrome.browser.media.ui.MediaNotificationListener;
-import org.chromium.chrome.browser.media.ui.MediaNotificationManager;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * A wrapper around a RemoteMediaPlayer, used in remote playback.
- */
-// Migrated to RemotingSessionController. See https://crbug.com/711860.
-public class RemotingCastSession
-        implements MediaNotificationListener, CastSession, Cast.MessageReceivedCallback {
-    private static final String TAG = "MediaRouter";
-
-    private final CastDevice mCastDevice;
-    private final MediaSource mSource;
-
-    private GoogleApiClient mApiClient;
-    private String mSessionId;
-    private String mApplicationStatus;
-    private ApplicationMetadata mApplicationMetadata;
-    private MediaNotificationInfo.Builder mNotificationBuilder;
-    private RemoteMediaPlayerWrapper mMediaPlayerWrapper;
-    private boolean mStoppingApplication;
-
-    public RemotingCastSession(GoogleApiClient apiClient, String sessionId,
-            ApplicationMetadata metadata, String applicationStatus, CastDevice castDevice,
-            String origin, int tabId, boolean isIncognito, MediaSource source) {
-        mCastDevice = castDevice;
-        mSource = source;
-        mApiClient = apiClient;
-        mApplicationMetadata = metadata;
-        mSessionId = sessionId;
-
-        mNotificationBuilder =
-                new MediaNotificationInfo.Builder()
-                        .setPaused(false)
-                        .setOrigin(origin)
-                        // TODO(avayvod): the same session might have more than one tab id. Should
-                        // we track the last foreground alive tab and update the notification with
-                        // it?
-                        .setTabId(tabId)
-                        .setPrivate(isIncognito)
-                        .setActions(MediaNotificationInfo.ACTION_STOP)
-                        .setNotificationSmallIcon(R.drawable.ic_notification_media_route)
-                        .setDefaultNotificationLargeIcon(R.drawable.cast_playing_square)
-                        .setId(R.id.presentation_notification)
-                        .setListener(this);
-
-        try {
-            Cast.CastApi.setMessageReceivedCallbacks(
-                    mApiClient, CastSessionUtil.MEDIA_NAMESPACE, this);
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to register media namespace listener", e);
-        }
-
-        mMediaPlayerWrapper = new RemoteMediaPlayerWrapper(mApiClient, mNotificationBuilder,
-                mCastDevice, ((RemotingMediaSource) source).getMediaUrl());
-    }
-
-    /**
-     * Cast.MessageReceivedCallback implementation.
-     */
-    @Override
-    public void onMessageReceived(CastDevice castDevice, String namespace, String message) {
-        if (!CastSessionUtil.MEDIA_NAMESPACE.equals(namespace)) {
-            Log.d(TAG,
-                    "RemotingCastSession received non-media message from Cast device: namespace=\""
-                            + namespace + "\" message=\"" + message + "\"");
-            return;
-        }
-
-        onMediaMessage(message);
-    }
-
-    @Override
-    public boolean isApiClientInvalid() {
-        return mApiClient == null || !mApiClient.isConnected();
-    }
-
-    @Override
-    public String getSourceId() {
-        return mSource.getSourceId();
-    }
-
-    @Override
-    public String getSinkId() {
-        return mCastDevice.getDeviceId();
-    }
-
-    @Override
-    public String getSessionId() {
-        return mSessionId;
-    }
-
-    @Override
-    public Set<String> getNamespaces() {
-        return new HashSet<String>();
-    }
-
-    @Override
-    public CastMessageHandler getMessageHandler() {
-        return null;
-    }
-
-    @Override
-    public CastSessionInfo getSessionInfo() {
-        // Only used by the CastMessageHandler, which is unused in the RemotingCastSession case.
-        return null;
-    }
-
-    @Override
-    public boolean sendStringCastMessage(
-            String message, String namespace, String clientId, int sequenceNumber) {
-        // String messages are not used in remoting scenarios.
-        return false;
-    }
-
-    @Override
-    public HandleVolumeMessageResult handleVolumeMessage(
-            JSONObject volume, String clientId, int sequenceNumber) throws JSONException {
-        // RemoteMediaPlayer's setStreamVolume() should be used instead of volume messages.
-        return null;
-    }
-
-    @Override
-    public void stopApplication() {
-        if (mStoppingApplication) return;
-
-        if (isApiClientInvalid()) return;
-
-        mStoppingApplication = true;
-        Cast.CastApi.stopApplication(mApiClient, mSessionId)
-                .setResultCallback(new ResultCallback<Status>() {
-                    @Override
-                    public void onResult(Status status) {
-                        // TODO(https://crbug.com/535577): handle a failure to stop the application.
-
-                        mSessionId = null;
-                        mApiClient = null;
-                        mMediaPlayerWrapper.clearApiClient();
-
-                        ChromeCastSessionManager.get().onSessionEnded();
-                        mStoppingApplication = false;
-
-                        MediaNotificationManager.clear(R.id.presentation_notification);
-                    }
-                });
-    }
-
-    @Override
-    public void onClientConnected(String clientId) {}
-
-    @Override
-    public void onMediaMessage(String message) {
-        mMediaPlayerWrapper.onMediaMessage(message);
-    }
-
-    @Override
-    public void onVolumeChanged() {}
-
-    @Override
-    public void updateSessionStatus() {}
-
-    /////////////////////////////////////////////////////////////////////////////////////////////
-    // MediaNotificationListener implementation.
-
-    @Override
-    public void onPlay(int actionSource) {
-        mMediaPlayerWrapper.play();
-    }
-
-    @Override
-    public void onPause(int actionSource) {
-        mMediaPlayerWrapper.pause();
-    }
-
-    @Override
-    public void onStop(int actionSource) {
-        stopApplication();
-        ChromeCastSessionManager.get().onSessionStopAction();
-    }
-
-    @Override
-    public void onMediaSessionAction(int action) {}
-
-    @Override
-    public FlingingController getFlingingController() {
-        return mMediaPlayerWrapper;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingMediaRouteProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingMediaRouteProvider.java
deleted file mode 100644
index 1ef9cb8..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingMediaRouteProvider.java
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-package org.chromium.chrome.browser.media.router.cast.remoting;
-
-import android.support.annotation.Nullable;
-import android.support.v7.media.MediaRouter;
-
-import org.chromium.base.Log;
-import org.chromium.base.VisibleForTesting;
-import org.chromium.chrome.browser.media.router.ChromeMediaRouter;
-import org.chromium.chrome.browser.media.router.FlingingController;
-import org.chromium.chrome.browser.media.router.MediaRoute;
-import org.chromium.chrome.browser.media.router.MediaRouteManager;
-import org.chromium.chrome.browser.media.router.MediaRouteProvider;
-import org.chromium.chrome.browser.media.router.MediaSink;
-import org.chromium.chrome.browser.media.router.MediaSource;
-import org.chromium.chrome.browser.media.router.cast.BaseMediaRouteProvider;
-import org.chromium.chrome.browser.media.router.cast.CastSession;
-import org.chromium.chrome.browser.media.router.cast.ChromeCastSessionManager;
-import org.chromium.chrome.browser.media.router.cast.CreateRouteRequest;
-
-/**
- * A {@link MediaRouteProvider} implementation for media remote playback.
- */
-// Migrated to CafRemotingMediaRouteProvider. See https://crbug.com/711860.
-public class RemotingMediaRouteProvider extends BaseMediaRouteProvider {
-    private static final String TAG = "MediaRemoting";
-
-    private int mPendingNativeRequestId;
-    private MediaRoute mPendingMediaRoute;
-
-    /**
-     * @return Initialized {@link RemotingMediaRouteProvider} object.
-     */
-    public static RemotingMediaRouteProvider create(MediaRouteManager manager) {
-        return new RemotingMediaRouteProvider(ChromeMediaRouter.getAndroidMediaRouter(), manager);
-    }
-
-    @Override
-    protected MediaSource getSourceFromId(String sourceId) {
-        return RemotingMediaSource.from(sourceId);
-    }
-
-    @Override
-    protected ChromeCastSessionManager.CastSessionLaunchRequest createSessionLaunchRequest(
-            MediaSource source, MediaSink sink, String presentationId, String origin, int tabId,
-            boolean isIncognito, int nativeRequestId) {
-        return new CreateRouteRequest(source, sink, presentationId, origin, tabId, isIncognito,
-                nativeRequestId, this, CreateRouteRequest.RequestedCastSessionType.REMOTE, null);
-    }
-
-    @Override
-    public void joinRoute(
-            String sourceId, String presentationId, String origin, int tabId, int nativeRequestId) {
-        mManager.onRouteRequestError(
-                "Remote playback doesn't support joining routes", nativeRequestId);
-    }
-
-    @Override
-    public void closeRoute(String routeId) {
-        MediaRoute route = mRoutes.get(routeId);
-        if (route == null) return;
-
-        if (mSession == null) {
-            mRoutes.remove(routeId);
-            mManager.onRouteTerminated(routeId);
-            return;
-        }
-
-        ChromeCastSessionManager.get().stopApplication();
-    }
-
-    @Override
-    public void detachRoute(String routeId) {
-        mRoutes.remove(routeId);
-    }
-
-    @Override
-    public void sendStringMessage(String routeId, String message) {
-        Log.e(TAG, "Remote playback does not support sending messages");
-    }
-
-    @VisibleForTesting
-    RemotingMediaRouteProvider(MediaRouter androidMediaRouter, MediaRouteManager manager) {
-        super(androidMediaRouter, manager);
-    }
-
-    @Override
-    public void onSessionEnded() {
-        if (mSession == null) return;
-
-        for (String routeId : mRoutes.keySet()) mManager.onRouteTerminated(routeId);
-        mRoutes.clear();
-
-        mSession = null;
-
-        if (mAndroidMediaRouter != null) {
-            mAndroidMediaRouter.selectRoute(mAndroidMediaRouter.getDefaultRoute());
-        }
-    }
-
-    @Override
-    public void onSessionStarting(ChromeCastSessionManager.CastSessionLaunchRequest launchRequest) {
-        CreateRouteRequest request = (CreateRouteRequest) launchRequest;
-        MediaSink sink = request.getSink();
-        MediaSource source = request.getSource();
-
-        // Calling mManager.onRouteCreated() too early causes some issues. If we call it here
-        // directly, getMediaController() might be called before onSessionStarted(), which causes
-        // the FlingingRenderer's creation to fail. Instead, save the route and request ID, and only
-        // signal the route as having been created when onSessionStarted() is called.
-        mPendingMediaRoute =
-                new MediaRoute(sink.getId(), source.getSourceId(), request.getPresentationId());
-        mPendingNativeRequestId = request.getNativeRequestId();
-    }
-
-    private void clearPendingRoute() {
-        mPendingMediaRoute = null;
-        mPendingNativeRequestId = 0;
-    }
-
-    @Override
-    public void onSessionStarted(CastSession session) {
-        super.onSessionStarted(session);
-
-        // Continued from onSessionStarting()
-        mRoutes.put(mPendingMediaRoute.id, mPendingMediaRoute);
-        mManager.onRouteCreated(mPendingMediaRoute.id, mPendingMediaRoute.sinkId,
-                mPendingNativeRequestId, this, true);
-
-        clearPendingRoute();
-    }
-
-    @Override
-    public void onSessionStartFailed() {
-        super.onSessionStartFailed();
-
-        mManager.onRouteRequestError(
-                "Failure to start RemotingCastSession", mPendingNativeRequestId);
-
-        clearPendingRoute();
-    };
-
-    @Override
-    @Nullable
-    public FlingingController getFlingingController(String routeId) {
-        // We cannot return a FlingingController if we don't have a session.
-        if (mSession == null) return null;
-
-        // Don't return controllers for stale routes.
-        if (!mRoutes.containsKey(routeId)) return null;
-
-        // RemotePlayback does not support joining routes, which means we only
-        // have a single route active at a time. If we have a a valid CastSession
-        // and the route ID is current, this means that the given |mSession|
-        // corresponds to the route ID, and it is ok to return the FlingingController.
-        return mSession.getFlingingController();
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/JourneyLogger.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/JourneyLogger.java
index 46491df..6cd045f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/JourneyLogger.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/JourneyLogger.java
@@ -80,7 +80,7 @@
     }
 
     /**
-     * Records the fact that the merchant called CanMakePayment and records it's return value.
+     * Records the fact that the merchant called CanMakePayment and records its return value.
      *
      * @param value The return value of the CanMakePayment call.
      */
@@ -89,6 +89,15 @@
     }
 
     /**
+     * Records the fact that the merchant called HasEnrolledInstrument and records its return value.
+     *
+     * @param value The return value of the HasEnrolledInstrument call.
+     */
+    public void setHasEnrolledInstrumentValue(boolean value) {
+        nativeSetHasEnrolledInstrumentValue(mJourneyLoggerAndroid, value);
+    }
+
+    /**
      * Records that an event occurred.
      *
      * @param event The event that occurred.
@@ -188,6 +197,8 @@
     private native void nativeIncrementSelectionAdds(long nativeJourneyLoggerAndroid, int section);
     private native void nativeSetCanMakePaymentValue(
             long nativeJourneyLoggerAndroid, boolean value);
+    private native void nativeSetHasEnrolledInstrumentValue(
+            long nativeJourneyLoggerAndroid, boolean value);
     private native void nativeSetEventOccurred(long nativeJourneyLoggerAndroid, int event);
     private native void nativeSetRequestedInformation(long nativeJourneyLoggerAndroid,
             boolean requestShipping, boolean requestEmail, boolean requestPhone,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestFactory.java
index c831680f..a9e64c68 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestFactory.java
@@ -61,7 +61,7 @@
         public void retry(PaymentValidationErrors errors) {}
 
         @Override
-        public void canMakePayment() {
+        public void canMakePayment(boolean legacyMode) {
             if (mClient != null) {
                 mClient.onCanMakePayment(CanMakePaymentQueryResult.CANNOT_MAKE_PAYMENT);
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index b2f3f05..db80fb76 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -228,6 +228,7 @@
 
     private PaymentRequestClient mClient;
     private boolean mIsCanMakePaymentResponsePending;
+    private boolean mUseLegacyCanMakePayment;
     private boolean mIsHasEnrolledInstrumentResponsePending;
     private boolean mHasEnrolledInstrumentUsesPerMethodQuota;
     private boolean mIsCurrentPaymentRequestShowing;
@@ -696,7 +697,10 @@
         }
 
         if (mIsCanMakePaymentResponsePending) {
-            respondCanMakePaymentQuery(mArePaymentMethodsSupported);
+            // New canMakePayment doesn't need to wait for all apps to be queried.
+            if (!mUseLegacyCanMakePayment || queryApps.isEmpty()) {
+                respondCanMakePaymentQuery(mUseLegacyCanMakePayment);
+            }
         }
 
         if (mIsHasEnrolledInstrumentResponsePending && queryApps.isEmpty()) {
@@ -1578,25 +1582,41 @@
      * Called by the merchant website to check if the user has complete payment instruments.
      */
     @Override
-    public void canMakePayment() {
+    public void canMakePayment(boolean legacyMode) {
         if (mClient == null) return;
 
         if (isFinishedQueryingPaymentApps()) {
-            respondCanMakePaymentQuery(mArePaymentMethodsSupported);
+            respondCanMakePaymentQuery(legacyMode);
         } else {
             mIsCanMakePaymentResponsePending = true;
+            mUseLegacyCanMakePayment = legacyMode;
         }
     }
 
-    private void respondCanMakePaymentQuery(boolean response) {
+    private void respondCanMakePaymentQuery(boolean legacyMode) {
         if (mClient == null) return;
 
         mIsCanMakePaymentResponsePending = false;
-        mClient.onCanMakePayment(response ? CanMakePaymentQueryResult.CAN_MAKE_PAYMENT
-                                          : CanMakePaymentQueryResult.CANNOT_MAKE_PAYMENT);
 
-        // TODO(https://crbug.com/915907): emit JourneyLogger event once the event names are
-        // updated.
+        boolean response = legacyMode ? mHasEnrolledInstrument : mArePaymentMethodsSupported;
+
+        // Only need to enforce query quota in legacy mode. Per-method quota not supported.
+        if (legacyMode
+                && !CanMakePaymentQuery.canQuery(mWebContents, mTopLevelOrigin,
+                        mPaymentRequestOrigin, mMethodData, /*perMethodQuota=*/false)) {
+            if (shouldEnforceCanMakePaymentQueryQuota()) {
+                mClient.onCanMakePayment(CanMakePaymentQueryResult.QUERY_QUOTA_EXCEEDED);
+            } else {
+                mClient.onCanMakePayment(response
+                                ? CanMakePaymentQueryResult.WARNING_CAN_MAKE_PAYMENT
+                                : CanMakePaymentQueryResult.WARNING_CANNOT_MAKE_PAYMENT);
+            }
+        } else {
+            mClient.onCanMakePayment(response ? CanMakePaymentQueryResult.CAN_MAKE_PAYMENT
+                                              : CanMakePaymentQueryResult.CANNOT_MAKE_PAYMENT);
+        }
+
+        mJourneyLogger.setCanMakePaymentValue(response || mIsIncognito);
 
         if (sObserverForTest != null) {
             sObserverForTest.onPaymentRequestServiceCanMakePaymentQueryResponded();
@@ -1637,7 +1657,7 @@
                             : HasEnrolledInstrumentQueryResult.WARNING_HAS_NO_ENROLLED_INSTRUMENT);
         }
 
-        mJourneyLogger.setCanMakePaymentValue(response || mIsIncognito);
+        mJourneyLogger.setHasEnrolledInstrumentValue(response || mIsIncognito);
 
         if (sObserverForTest != null) {
             sObserverForTest.onPaymentRequestServiceHasEnrolledInstrumentQueryResponded();
@@ -1746,6 +1766,10 @@
                 ? 0
                 : SectionInformation.NO_SELECTION;
 
+        if (mIsCanMakePaymentResponsePending) {
+            respondCanMakePaymentQuery(mUseLegacyCanMakePayment);
+        }
+
         if (mIsHasEnrolledInstrumentResponsePending) {
             respondHasEnrolledInstrumentQuery(mHasEnrolledInstrument);
         }
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index f2a255fc..f040fc3b 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -2793,9 +2793,6 @@
       <message name="IDS_CAST_CASTING_VIDEO" desc="AtHome text to tell user which screen casting is happening. [CHAR LIMIT=40]">
         Casting to <ph name="SCREEN_NAME">%1$s<ex>Living Room TV</ex></ph>
       </message>
-      <message name="IDS_CAST_ERROR_PLAYING_VIDEO" desc="The message shown to the user when playing a video to the Chromecast fails.">
-        Cannot play video on <ph name="SCREEN_NAME">%1$s<ex>Living Room TV</ex></ph>.
-      </message>
       <message name="IDS_ACCESSIBILITY_PLAY" desc="The play button that starts playing the media.">
         Play
       </message>
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index e3f220c..3961578 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -896,20 +896,7 @@
   "java/src/org/chromium/chrome/browser/media/MediaViewerUtils.java",
   "java/src/org/chromium/chrome/browser/media/PictureInPicture.java",
   "java/src/org/chromium/chrome/browser/media/PictureInPictureController.java",
-  "java/src/org/chromium/chrome/browser/media/remote/AbstractMediaRouteController.java",
-  "java/src/org/chromium/chrome/browser/media/remote/CastNotificationControl.java",
-  "java/src/org/chromium/chrome/browser/media/remote/DefaultMediaRouteController.java",
-  "java/src/org/chromium/chrome/browser/media/remote/ExpandedControllerActivity.java",
-  "java/src/org/chromium/chrome/browser/media/remote/FullscreenMediaRouteButton.java",
-  "java/src/org/chromium/chrome/browser/media/remote/MediaRouteChooserDialogFactory.java",
-  "java/src/org/chromium/chrome/browser/media/remote/MediaRouteController.java",
-  "java/src/org/chromium/chrome/browser/media/remote/MediaRouteControllerDialogFactory.java",
-  "java/src/org/chromium/chrome/browser/media/remote/MediaUrlResolver.java",
-  "java/src/org/chromium/chrome/browser/media/remote/PositionExtrapolator.java",
   "java/src/org/chromium/chrome/browser/media/remote/RecordCastAction.java",
-  "java/src/org/chromium/chrome/browser/media/remote/RemoteMediaPlayerController.java",
-  "java/src/org/chromium/chrome/browser/media/remote/RemoteMediaPlayerWrapper.java",
-  "java/src/org/chromium/chrome/browser/media/remote/RemoteVideoInfo.java",
   "java/src/org/chromium/chrome/browser/media/router/BaseMediaRouteDialogManager.java",
   "java/src/org/chromium/chrome/browser/media/router/CastRequestIdGenerator.java",
   "java/src/org/chromium/chrome/browser/media/router/CastSessionUtil.java",
@@ -938,6 +925,7 @@
   "java/src/org/chromium/chrome/browser/media/router/caf/CafMediaRouteProvider.java",
   "java/src/org/chromium/chrome/browser/media/router/caf/CafMessageHandler.java",
   "java/src/org/chromium/chrome/browser/media/router/caf/CafNotificationController.java",
+  "java/src/org/chromium/chrome/browser/media/router/caf/CastMediaSource.java",
   "java/src/org/chromium/chrome/browser/media/router/caf/CastOptionsProvider.java",
   "java/src/org/chromium/chrome/browser/media/router/caf/CastSessionController.java",
   "java/src/org/chromium/chrome/browser/media/router/caf/CastUtils.java",
@@ -945,21 +933,10 @@
   "java/src/org/chromium/chrome/browser/media/router/caf/remoting/CafExpandedControllerActivity.java",
   "java/src/org/chromium/chrome/browser/media/router/caf/remoting/CafRemotingMediaRouteProvider.java",
   "java/src/org/chromium/chrome/browser/media/router/caf/remoting/FlingingControllerAdapter.java",
+  "java/src/org/chromium/chrome/browser/media/router/caf/remoting/RemotingMediaSource.java",
   "java/src/org/chromium/chrome/browser/media/router/caf/remoting/RemotingNotificationController.java",
   "java/src/org/chromium/chrome/browser/media/router/caf/remoting/RemotingSessionController.java",
   "java/src/org/chromium/chrome/browser/media/router/caf/remoting/StreamPositionExtrapolator.java",
-  "java/src/org/chromium/chrome/browser/media/router/cast/BaseMediaRouteProvider.java",
-  "java/src/org/chromium/chrome/browser/media/router/cast/CastMediaRouteProvider.java",
-  "java/src/org/chromium/chrome/browser/media/router/cast/CastMediaSource.java",
-  "java/src/org/chromium/chrome/browser/media/router/cast/CastMessageHandler.java",
-  "java/src/org/chromium/chrome/browser/media/router/cast/CastSession.java",
-  "java/src/org/chromium/chrome/browser/media/router/cast/CastSessionImpl.java",
-  "java/src/org/chromium/chrome/browser/media/router/cast/CastSessionInfo.java",
-  "java/src/org/chromium/chrome/browser/media/router/cast/ChromeCastSessionManager.java",
-  "java/src/org/chromium/chrome/browser/media/router/cast/CreateRouteRequest.java",
-  "java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingCastSession.java",
-  "java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingMediaRouteProvider.java",
-  "java/src/org/chromium/chrome/browser/media/router/cast/remoting/RemotingMediaSource.java",
   "java/src/org/chromium/chrome/browser/media/ui/MediaButtonReceiver.java",
   "java/src/org/chromium/chrome/browser/media/ui/MediaImageCallback.java",
   "java/src/org/chromium/chrome/browser/media/ui/MediaImageManager.java",
@@ -2087,12 +2064,6 @@
   "javatests/src/org/chromium/chrome/browser/locale/LocaleManagerReferralTest.java",
   "javatests/src/org/chromium/chrome/browser/locale/LocaleManagerTest.java",
   "javatests/src/org/chromium/chrome/browser/media/MediaLauncherActivityTest.java",
-  "javatests/src/org/chromium/chrome/browser/media/remote/CastNotificationTest.java",
-  "javatests/src/org/chromium/chrome/browser/media/remote/CastPositionTransferTest.java",
-  "javatests/src/org/chromium/chrome/browser/media/remote/CastStartStopTest.java",
-  "javatests/src/org/chromium/chrome/browser/media/remote/CastSwitchVideoTest.java",
-  "javatests/src/org/chromium/chrome/browser/media/remote/CastTestRule.java",
-  "javatests/src/org/chromium/chrome/browser/media/remote/CastVideoControlsTest.java",
   "javatests/src/org/chromium/chrome/browser/media/router/MediaRouterIntegrationTest.java",
   "javatests/src/org/chromium/chrome/browser/media/router/MockMediaRouteProvider.java",
   "javatests/src/org/chromium/chrome/browser/media/ui/AutoplayMutedNotificationTest.java",
@@ -2478,9 +2449,6 @@
   "junit/src/org/chromium/chrome/browser/invalidation/InvalidationControllerTest.java",
   "junit/src/org/chromium/chrome/browser/invalidation/ResumableDelayedTaskRunnerTest.java",
   "junit/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManagerTest.java",
-  "junit/src/org/chromium/chrome/browser/media/remote/AbstractMediaRouteControllerTest.java",
-  "junit/src/org/chromium/chrome/browser/media/remote/MediaUrlResolverTest.java",
-  "junit/src/org/chromium/chrome/browser/media/remote/RemoteVideoInfoTest.java",
   "junit/src/org/chromium/chrome/browser/media/router/ChromeMediaRouterRouteTest.java",
   "junit/src/org/chromium/chrome/browser/media/router/ChromeMediaRouterSinkObservationTest.java",
   "junit/src/org/chromium/chrome/browser/media/router/ChromeMediaRouterTestBase.java",
@@ -2496,10 +2464,6 @@
   "junit/src/org/chromium/chrome/browser/media/router/caf/ShadowCastContext.java",
   "junit/src/org/chromium/chrome/browser/media/router/caf/ShadowCastMediaSource.java",
   "junit/src/org/chromium/chrome/browser/media/router/caf/ShadowMediaRouter.java",
-  "junit/src/org/chromium/chrome/browser/media/router/cast/CastMediaRouteProviderTest.java",
-  "junit/src/org/chromium/chrome/browser/media/router/cast/CastMediaSourceTest.java",
-  "junit/src/org/chromium/chrome/browser/media/router/cast/CastMessageHandlerTest.java",
-  "junit/src/org/chromium/chrome/browser/media/router/cast/ChromeCastSessionManagerTest.java",
   "junit/src/org/chromium/chrome/browser/media/ui/MediaImageManagerTest.java",
   "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationActionsUpdatedTest.java",
   "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationButtonComputationTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastNotificationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastNotificationTest.java
deleted file mode 100644
index 2382567..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastNotificationTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.DEFAULT_VIDEO_PAGE;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.MAX_VIEW_TIME_MS;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.STABILIZE_TIME_MS;
-
-import android.support.test.filters.LargeTest;
-
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisabledTest;
-import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.Restriction;
-import org.chromium.base.test.util.RetryOnFailure;
-import org.chromium.chrome.browser.ChromeSwitches;
-import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
-import org.chromium.chrome.browser.media.ui.MediaNotificationListener;
-import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.media.MediaSwitches;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * Tests of the notification.
- */
-@RunWith(ChromeJUnit4ClassRunner.class)
-@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
-        "disable-features=" + MediaSwitches.USE_MODERN_MEDIA_CONTROLS})
-public class CastNotificationTest {
-    @Rule
-    public CastTestRule mCastTestRule = new CastTestRule();
-
-    private static final long PAUSE_TEST_TIME_MS = 1000;
-
-    /**
-     * Test the pause button on the notification.
-     */
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @RetryOnFailure
-    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) // crbug.com/652872
-    public void testNotificationPause() throws InterruptedException, TimeoutException {
-        mCastTestRule.castDefaultVideoFromPage(DEFAULT_VIDEO_PAGE);
-
-        // Get the notification
-        final CastNotificationControl notificationControl = mCastTestRule.waitForCastNotification();
-        Assert.assertNotNull("No notificationTransportControl", notificationControl);
-        // Send pause
-        ThreadUtils.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                notificationControl.onPause(MediaNotificationListener.ACTION_SOURCE_MEDIA_SESSION);
-            }
-        });
-        Assert.assertTrue(
-                "Not paused", mCastTestRule.waitForState(PlayerState.PAUSED, MAX_VIEW_TIME_MS));
-
-        // The new position is sent in a separate message, so we have to wait a bit before
-        // fetching it.
-        Thread.sleep(STABILIZE_TIME_MS);
-        long position = mCastTestRule.getRemotePositionMs();
-        // Position should not change while paused
-        Thread.sleep(PAUSE_TEST_TIME_MS);
-        Assert.assertEquals(
-                "Pause didn't stop playback", position, mCastTestRule.getRemotePositionMs());
-        // Send play
-        ThreadUtils.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                notificationControl.onPlay(MediaNotificationListener.ACTION_SOURCE_MEDIA_SESSION);
-            }
-        });
-        Assert.assertTrue(
-                "Not playing", mCastTestRule.waitForState(PlayerState.PLAYING, MAX_VIEW_TIME_MS));
-
-        // Should now be running again.
-        Thread.sleep(PAUSE_TEST_TIME_MS);
-        Assert.assertTrue(
-                "Run didn't restart playback", position < mCastTestRule.getRemotePositionMs());
-    }
-}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastPositionTransferTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastPositionTransferTest.java
deleted file mode 100644
index e7cc0efa..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastPositionTransferTest.java
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.CAST_TEST_ROUTE;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.DEFAULT_VIDEO_PAGE;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.MAX_VIEW_TIME_MS;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.VIDEO_ELEMENT;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.VIEW_RETRY_MS;
-
-import android.graphics.Rect;
-import android.support.test.filters.LargeTest;
-
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisabledTest;
-import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.Restriction;
-import org.chromium.base.test.util.RetryOnFailure;
-import org.chromium.chrome.browser.ChromeSwitches;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.content_public.browser.test.util.JavaScriptUtils;
-import org.chromium.media.MediaSwitches;
-
-import java.util.Date;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Tests related to the transfer of the playback position between the local and
- * the remote player.
- */
-@RunWith(ChromeJUnit4ClassRunner.class)
-@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
-        "disable-features=" + MediaSwitches.USE_MODERN_MEDIA_CONTROLS})
-@RetryOnFailure
-public class CastPositionTransferTest {
-    @Rule
-    public CastTestRule mCastTestRule = new CastTestRule();
-
-    /** Reference position in the video where we should start casting */
-    private static final int CAST_START_TIME_MS = 3000;
-
-    /** Max accepted error for position comparisons. */
-    private static final int SEEK_EPSILON_MS = 250;
-
-    /** Used to wait for the UI to properly respond. It is smaller than the default
-     * {@link CastTestBase#STABILIZE_TIME_MS} to make sure the video doesn't finish while waiting.*/
-    private static final int SMALL_STABILIZE_TIME_MS = 250;
-
-
-    /** Returns the current time of the video, in milliseconds,  by getting it from JavaScript. */
-    private static long getLocalPositionMillis(Tab tab, String videoElementId)
-            throws InterruptedException, TimeoutException, NumberFormatException {
-        StringBuilder sb = new StringBuilder();
-        sb.append("(function() {");
-        sb.append("  var node = document.getElementById('" + videoElementId + "');");
-        sb.append("  if (node) return node.currentTime;");
-        sb.append("  return null");
-        sb.append("})();");
-        String jsResult = JavaScriptUtils.executeJavaScriptAndWaitForResult(
-                tab.getWebContents(), sb.toString());
-        return Math.round(Double.parseDouble(jsResult) * 1000);
-    }
-
-    private static void seekFromJS(Tab tab, long seekToMs)
-            throws InterruptedException, TimeoutException, NumberFormatException {
-        final long seekToSec = TimeUnit.MILLISECONDS.toSeconds(seekToMs);
-        StringBuilder sb = new StringBuilder();
-        sb.append("(function() {");
-        sb.append("  var node = document.getElementById('" + VIDEO_ELEMENT + "');");
-        sb.append("  if (node) node.currentTime = " + seekToSec + ";");
-        sb.append("  return 0;");
-        sb.append("})();");
-        JavaScriptUtils.executeJavaScriptAndWaitForResult(tab.getWebContents(), sb.toString());
-    }
-
-    /** Test for crbug.com/428409 */
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) // crbug.com/652872
-    public void testLocalToRemotePositionTransfer() throws InterruptedException, TimeoutException {
-        final Tab tab = mCastTestRule.getActivity().getActivityTab();
-        final Rect videoRect = mCastTestRule.prepareDefaultVideofromPage(DEFAULT_VIDEO_PAGE, tab);
-
-        // Jump to the position
-        seekFromJS(tab, CAST_START_TIME_MS);
-        final double localPositionMs = getLocalPositionMillis(tab, VIDEO_ELEMENT);
-        Assert.assertTrue("Local playback position (" + localPositionMs + ") did not advance past "
-                        + CAST_START_TIME_MS,
-                localPositionMs >= CAST_START_TIME_MS);
-
-        // Start cast
-        final long castStartTimeMs = new Date().getTime();
-        mCastTestRule.castVideoAndWaitUntilPlaying(CAST_TEST_ROUTE, tab, videoRect);
-
-        // Test the position
-        final long remotePositionMs = mCastTestRule.getRemotePositionMs();
-        final long castDelayMs = new Date().getTime() - castStartTimeMs;
-
-        Assert.assertTrue("The remote playback position (" + remotePositionMs
-                        + ") did not advance past " + CAST_START_TIME_MS,
-                remotePositionMs >= CAST_START_TIME_MS);
-        Assert.assertTrue("The remote playback position (" + remotePositionMs + ") went too far.",
-                remotePositionMs <= CAST_START_TIME_MS + castDelayMs);
-    }
-
-    /** Test for crbug.com/428409 */
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) // crbug.com/652872
-    public void testRemoteToLocalPositionTransfer() throws InterruptedException, TimeoutException {
-        final Tab tab = mCastTestRule.getActivity().getActivityTab();
-        final Rect videoRect = mCastTestRule.prepareDefaultVideofromPage(DEFAULT_VIDEO_PAGE, tab);
-
-        // Start cast
-        mCastTestRule.castVideoAndWaitUntilPlaying(CAST_TEST_ROUTE, tab, videoRect);
-
-        mCastTestRule.tapPlayPauseButton(tab, videoRect);
-        long pausePosition = mCastTestRule.getRemotePositionMs();
-        for (int time = 0; time < MAX_VIEW_TIME_MS; time += VIEW_RETRY_MS) {
-            Thread.sleep(VIEW_RETRY_MS);
-            long newPosition = mCastTestRule.getRemotePositionMs();
-            if (newPosition == pausePosition) {
-                break;
-            }
-            pausePosition = newPosition;
-        }
-        // Jump to the position
-        seekFromJS(tab, CAST_START_TIME_MS);
-        for (int time = 0;
-                time < MAX_VIEW_TIME_MS && mCastTestRule.getRemotePositionMs() != pausePosition;
-                time += VIEW_RETRY_MS) {
-            Thread.sleep(VIEW_RETRY_MS);
-        }
-        long remotePositionMs = mCastTestRule.getRemotePositionMs();
-        Assert.assertEquals("The remote player did not seek", CAST_START_TIME_MS, remotePositionMs,
-                SEEK_EPSILON_MS);
-
-        // Stop cast and play locally
-        final long castStopTimeMs = new Date().getTime();
-        mCastTestRule.clickDisconnectFromRoute(tab, videoRect);
-        mCastTestRule.tapPlayPauseButton(tab, videoRect);
-        mCastTestRule.sleepNoThrow(SMALL_STABILIZE_TIME_MS);
-
-        // Test the position
-        final double localPositionMs = getLocalPositionMillis(tab, VIDEO_ELEMENT);
-        final long castDelayMs = new Date().getTime() - castStopTimeMs;
-        Assert.assertTrue("The local playback position (" + localPositionMs
-                        + ") did not advance past " + CAST_START_TIME_MS,
-                localPositionMs >= CAST_START_TIME_MS);
-        Assert.assertTrue("The local playback position (" + localPositionMs + ") went too far.",
-                localPositionMs <= CAST_START_TIME_MS + castDelayMs);
-    }
-
-    /** Test for crbug.com/425105 */
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) // crbug.com/593840, crbug.com/652872
-    public void testPositionUpdate() throws InterruptedException, TimeoutException {
-        final Tab tab = mCastTestRule.getActivity().getActivityTab();
-        final Rect videoRect = mCastTestRule.prepareDefaultVideofromPage(DEFAULT_VIDEO_PAGE, tab);
-        mCastTestRule.castVideoAndWaitUntilPlaying(CAST_TEST_ROUTE, tab, videoRect);
-
-        mCastTestRule.sleepNoThrow(CAST_START_TIME_MS);
-
-        final long remotePositionMs = mCastTestRule.getRemotePositionMs();
-        final long localPositionMs = getLocalPositionMillis(tab, VIDEO_ELEMENT);
-
-        Assert.assertEquals("Remote playback position was not properly updated.",
-                CAST_START_TIME_MS, remotePositionMs, SEEK_EPSILON_MS);
-        Assert.assertEquals("The remote playback position was updated, but the local one was not.",
-                CAST_START_TIME_MS, localPositionMs, SEEK_EPSILON_MS);
-    }
-}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastStartStopTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastStartStopTest.java
deleted file mode 100644
index 4c854b1..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastStartStopTest.java
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.DEFAULT_VIDEO;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.DEFAULT_VIDEO_PAGE;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.STABILIZE_TIME_MS;
-
-import android.graphics.Rect;
-import android.support.test.filters.LargeTest;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisableIf;
-import org.chromium.base.test.util.DisabledTest;
-import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.FlakyTest;
-import org.chromium.base.test.util.Restriction;
-import org.chromium.base.test.util.RetryOnFailure;
-import org.chromium.chrome.browser.ChromeSwitches;
-import org.chromium.chrome.browser.media.ui.MediaNotificationListener;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.media.MediaSwitches;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * Simple tests of casting videos. These tests all use the same page, containing the same non-YT
- * video.
- */
-@RunWith(ChromeJUnit4ClassRunner.class)
-@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
-        "disable-features=" + MediaSwitches.USE_MODERN_MEDIA_CONTROLS})
-public class CastStartStopTest {
-    @Rule
-    public CastTestRule mCastTestRule = new CastTestRule();
-
-    /*
-     * Test that we can cast a video, and that we get the ExpandedControllerActivity when we do.
-     */
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @RetryOnFailure
-    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) // crbug.com/652872
-    public void testCastingGenericVideo() throws InterruptedException, TimeoutException {
-        mCastTestRule.castDefaultVideoFromPage(DEFAULT_VIDEO_PAGE);
-        mCastTestRule.checkVideoStarted(DEFAULT_VIDEO);
-    }
-
-    /*
-     * Test that we can disconnect a cast session from the expanded controller activity overlay.
-     */
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @RetryOnFailure
-    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) // crbug.com/652872
-    public void testStopFromVideoControls() throws InterruptedException, TimeoutException {
-        Rect videoRect = mCastTestRule.castDefaultVideoFromPage(DEFAULT_VIDEO_PAGE);
-
-        final Tab tab = mCastTestRule.getActivity().getActivityTab();
-
-        mCastTestRule.clickDisconnectFromRoute(tab, videoRect);
-
-        mCastTestRule.checkDisconnected();
-    }
-
-    /*
-     * Test that we can stop a cast session from the notification.
-     */
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @RetryOnFailure
-    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) // crbug.com/652872
-    public void testStopFromNotification() throws InterruptedException, TimeoutException {
-        mCastTestRule.castDefaultVideoFromPage(DEFAULT_VIDEO_PAGE);
-
-        // Get the notification
-        final CastNotificationControl notificationControl = mCastTestRule.waitForCastNotification();
-
-        // Send play
-        ThreadUtils.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                notificationControl.onStop(MediaNotificationListener.ACTION_SOURCE_MEDIA_SESSION);
-            }
-        });
-        mCastTestRule.checkDisconnected();
-    }
-
-    /*
-     * Test that a cast session disconnects when the video ends
-     */
-    @Test
-    @DisableIf.Build(sdk_is_less_than = 19, message = "crbug.com/582067")
-    @Feature({"VideoFling"})
-    @LargeTest
-    @FlakyTest
-    public void testStopWhenVideoEnds() throws InterruptedException, TimeoutException {
-        mCastTestRule.castDefaultVideoFromPage(DEFAULT_VIDEO_PAGE);
-        // Wait for the video to finish (this assumes the video is short, the test video
-        // is 8 seconds).
-        mCastTestRule.sleepNoThrow(STABILIZE_TIME_MS);
-
-        Thread.sleep(mCastTestRule.getRemoteDurationMs());
-
-        // Everything should now have disconnected
-        mCastTestRule.checkDisconnected();
-    }
-}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastSwitchVideoTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastSwitchVideoTest.java
deleted file mode 100644
index f3cd818f..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastSwitchVideoTest.java
+++ /dev/null
@@ -1,217 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.CAST_TEST_ROUTE;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.DEFAULT_VIDEO;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.DEFAULT_VIDEO_PAGE;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.TEST_VIDEO_2;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.TEST_VIDEO_PAGE_2;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.TWO_VIDEO_PAGE;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.VIDEO_ELEMENT;
-
-import android.graphics.Rect;
-import android.support.test.filters.LargeTest;
-
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisabledTest;
-import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.RetryOnFailure;
-import org.chromium.chrome.browser.ChromeSwitches;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.test.util.DOMUtils;
-import org.chromium.media.MediaSwitches;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * Test that other videos are played locally when casting
- */
-@RunWith(ChromeJUnit4ClassRunner.class)
-@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
-        "disable-features=" + MediaSwitches.USE_MODERN_MEDIA_CONTROLS})
-public class CastSwitchVideoTest {
-    @Rule
-    public CastTestRule mCastTestRule = new CastTestRule();
-
-    private static final String VIDEO_ELEMENT_2 = "video2";
-
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @RetryOnFailure // crbug.com/623526
-    public void testPlayNewVideoInNewTab() throws InterruptedException, TimeoutException {
-        checkPlaySecondVideo(DEFAULT_VIDEO_PAGE, VIDEO_ELEMENT, new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    mCastTestRule.loadUrlInNewTab(
-                            mCastTestRule.getTestServer().getURL(TEST_VIDEO_PAGE_2));
-                    playVideoFromCurrentTab(VIDEO_ELEMENT);
-                } catch (Exception e) {
-                    Assert.fail("Failed to start second video; " + e.getMessage());
-                }
-            }
-        });
-    }
-
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @RetryOnFailure // crbug.com/623526
-    public void testPlayNewVideoNewPageSameTab() throws InterruptedException, TimeoutException {
-        checkPlaySecondVideo(DEFAULT_VIDEO_PAGE, VIDEO_ELEMENT, new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    mCastTestRule.loadUrl(mCastTestRule.getTestServer().getURL(TEST_VIDEO_PAGE_2));
-                    playVideoFromCurrentTab(VIDEO_ELEMENT);
-                } catch (Exception e) {
-                    Assert.fail("Failed to start second video; " + e.getMessage());
-                }
-            }
-        });
-    }
-
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @RetryOnFailure // crbug.com/623526
-    public void testPlayTwoVideosSamePage() throws InterruptedException, TimeoutException {
-        checkPlaySecondVideo(TWO_VIDEO_PAGE, VIDEO_ELEMENT_2, new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    playVideoFromCurrentTab(VIDEO_ELEMENT_2);
-                } catch (Exception e) {
-                    Assert.fail("Failed to start second video; " + e.getMessage());
-                }
-            }
-        });
-    }
-
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @RetryOnFailure // crbug.com/623526
-    public void testCastNewVideoInNewTab() throws InterruptedException, TimeoutException {
-        checkCastSecondVideo(DEFAULT_VIDEO_PAGE, new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    mCastTestRule.loadUrlInNewTab(
-                            mCastTestRule.getTestServer().getURL(TEST_VIDEO_PAGE_2));
-                    castVideoFromCurrentTab(VIDEO_ELEMENT);
-                } catch (Exception e) {
-                    Assert.fail("Failed to start second video; " + e.getMessage());
-                }
-            }
-        });
-    }
-
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @RetryOnFailure // crbug.com/623526
-    public void testCastNewVideoNewPageSameTab() throws InterruptedException, TimeoutException {
-        checkCastSecondVideo(DEFAULT_VIDEO_PAGE, new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    mCastTestRule.loadUrl(mCastTestRule.getTestServer().getURL(TEST_VIDEO_PAGE_2));
-                    castVideoFromCurrentTab(VIDEO_ELEMENT);
-                } catch (Exception e) {
-                    Assert.fail("Failed to start second video; " + e.getMessage());
-                }
-            }
-        });
-    }
-
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @RetryOnFailure // crbug.com/623526
-    public void testCastTwoVideosSamePage() throws InterruptedException, TimeoutException {
-        checkCastSecondVideo(TWO_VIDEO_PAGE, new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    castVideoFromCurrentTab(VIDEO_ELEMENT_2);
-                } catch (Exception e) {
-                    Assert.fail("Failed to start second video; " + e.getMessage());
-                }
-            }
-        });
-    }
-
-    private void checkPlaySecondVideo(
-            String firstVideoPage, String secondVideoId, final Runnable startSecondVideo)
-                    throws InterruptedException, TimeoutException {
-        // TODO(aberent) Checking position is flaky, because it is timing dependent, but probably
-        // a good idea in principle. Need to find a way of unflaking it.
-        // int position = castAndPauseDefaultVideoFromPage(firstVideoPage);
-        mCastTestRule.castAndPauseDefaultVideoFromPage(firstVideoPage);
-
-        startSecondVideo.run();
-
-        // Check that we are still casting the default video
-        Assert.assertEquals("The first video is not casting",
-                mCastTestRule.getTestServer().getURL(DEFAULT_VIDEO), mCastTestRule.getUriPlaying());
-
-        // Check that the second video is still there and paused
-        final Tab tab = mCastTestRule.getActivity().getActivityTab();
-        WebContents webContents = tab.getWebContents();
-        Assert.assertFalse(
-                "Other video is not playing", DOMUtils.isMediaPaused(webContents, secondVideoId));
-    }
-
-    private void checkCastSecondVideo(String firstVideoPage,  final Runnable startSecondVideo)
-            throws InterruptedException, TimeoutException {
-        // TODO(aberent) Checking position is flaky, because it is timing dependent, but probably
-        // a good idea in principle. Need to find a way of unflaking it.
-        // int position = castAndPauseDefaultVideoFromPage(firstVideoPage);
-        mCastTestRule.castAndPauseDefaultVideoFromPage(firstVideoPage);
-
-        startSecondVideo.run();
-
-        // Check that we switch to playing the right video
-        mCastTestRule.checkVideoStarted(TEST_VIDEO_2);
-    }
-
-    private void castVideoFromCurrentTab(String videoElement) throws InterruptedException,
-            TimeoutException {
-        final Tab tab = mCastTestRule.getActivity().getActivityTab();
-        WebContents webContents = tab.getWebContents();
-        mCastTestRule.waitUntilVideoReady(videoElement, webContents);
-        Rect videoRect = DOMUtils.getNodeBounds(webContents, videoElement);
-
-        mCastTestRule.castVideoAndWaitUntilPlaying(CAST_TEST_ROUTE, tab, videoRect);
-    }
-
-    private void playVideoFromCurrentTab(String videoElement) throws InterruptedException,
-            TimeoutException {
-        WebContents webContents = mCastTestRule.getWebContents();
-
-        mCastTestRule.waitUntilVideoReady(videoElement, webContents);
-
-        // Need to click on the video first to overcome the user gesture requirement.
-        DOMUtils.clickNode(webContents, videoElement);
-        DOMUtils.playMedia(webContents, videoElement);
-        DOMUtils.waitForMediaPlay(webContents, videoElement);
-    }
-
-}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastTestRule.java
deleted file mode 100644
index 48f93e9e..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastTestRule.java
+++ /dev/null
@@ -1,548 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-
-import android.app.Dialog;
-import android.graphics.Rect;
-import android.support.test.InstrumentationRegistry;
-import android.support.v4.app.DialogFragment;
-import android.support.v4.app.FragmentManager;
-import android.view.View;
-
-import org.junit.Assert;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
-import org.chromium.chrome.browser.ChromeActivity;
-import org.chromium.chrome.browser.media.RouterTestUtils;
-import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.test.ChromeActivityTestRule;
-import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.test.util.ClickUtils;
-import org.chromium.content_public.browser.test.util.Coordinates;
-import org.chromium.content_public.browser.test.util.DOMUtils;
-import org.chromium.content_public.browser.test.util.JavaScriptUtils;
-import org.chromium.content_public.browser.test.util.UiUtils;
-import org.chromium.net.test.EmbeddedTestServer;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Custom TestRule for tests of Clank Cast. Contains functions for setting up a cast connection
- * and other utility functions.
- */
-public class CastTestRule extends ChromeActivityTestRule<ChromeActivity> {
-    private class TestListener implements MediaRouteController.UiListener {
-        @Override
-        public void onRouteSelected(String name, MediaRouteController mediaRouteController) {}
-
-        @Override
-        public void onRouteUnselected(MediaRouteController mediaRouteController) {}
-
-        @Override
-        public void onPrepared(MediaRouteController mediaRouteController) {}
-
-        @Override
-        public void onError(int errorType, String message) {}
-
-        @Override
-        public void onPlaybackStateChanged(final @PlayerState int newState) {
-            // Use postOnUiThread to handling the latch until the current UI task has completed,
-            // this makes sure that Cast has finished handling the event.
-            ThreadUtils.postOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    if ((mAwaitedStates.contains(newState))) {
-                        mLatch.countDown();
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void onDurationUpdated(long durationMillis) {}
-
-        @Override
-        public void onPositionChanged(long positionMillis) {}
-
-        @Override
-        public void onTitleChanged(String title) {}
-    }
-
-    private Set<Integer> mAwaitedStates;
-    private CountDownLatch mLatch;
-    private EmbeddedTestServer mTestServer;
-
-    // The name of the route provided by the dummy cast device.
-    public static final String CAST_TEST_ROUTE = "Cast Test Route";
-
-    // URLs of the default test page and video.
-    public static final String DEFAULT_VIDEO_PAGE =
-            "/chrome/test/data/android/media/simple_video.html";
-    public static final String DEFAULT_VIDEO = "/chrome/test/data/android/media/test.webm";
-
-    // Constants used to find the default video and maximise button on the page
-    public static final String VIDEO_ELEMENT = "video";
-
-    // Max time to open a view.
-    public static final int MAX_VIEW_TIME_MS = 10000;
-
-    // Time to let a video run to ensure that it has started.
-    public static final int RUN_TIME_MS = 1000;
-    // Time to allow for the UI to react to video controls,
-    public static final int STABILIZE_TIME_MS = 3000;
-
-    // Retry interval when looking for a view.
-    public static final int VIEW_RETRY_MS = 100;
-
-    public static final String TEST_VIDEO_PAGE_2 =
-            "/chrome/test/data/android/media/simple_video2.html";
-
-    public static final String TEST_VIDEO_2 = "/chrome/test/data/android/media/test2.webm";
-
-    public static final String TWO_VIDEO_PAGE = "/chrome/test/data/android/media/two_videos.html";
-
-    private static final String TAG = "CastTestRule";
-
-    private MediaRouteController mMediaRouteController;
-
-    public CastTestRule() {
-        super(ChromeActivity.class);
-    }
-
-    @Override
-    public Statement apply(final Statement base, Description description) {
-        return super.apply(new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                setUp();
-                base.evaluate();
-                tearDown();
-            }
-        }, description);
-    }
-
-    private void setUp() throws Exception {
-        startMainActivityOnBlankPage();
-        mTestServer = EmbeddedTestServer.createAndStartServer(
-                InstrumentationRegistry.getInstrumentation().getContext());
-    }
-
-    private void tearDown() throws Exception {
-        mTestServer.stopAndDestroyServer();
-    }
-
-    /**
-     * Wait for cast to reach a state we are interested in.
-     * Will deadlock if called on the target's UI thread.
-     * @param states
-     */
-    public boolean waitForStates(final Set<Integer> states, int waitTimeMs) {
-        mAwaitedStates = states;
-        mLatch = new CountDownLatch(1);
-        // Deal with the case where Chrome is already in the desired state
-        ThreadUtils.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                if (mMediaRouteController != null
-                        && states.contains(mMediaRouteController.getDisplayedPlayerState())) {
-                    mLatch.countDown();
-                }
-            }
-        });
-        try {
-            return mLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            return false;
-        }
-    }
-
-    /**
-     * Wait for cast to reach a state we are interested in.
-     * Will deadlock if called on the target's UI thread.
-     * @param state
-     */
-    public boolean waitForState(final @PlayerState int state, int waitTimeMs) {
-        Set<Integer> states = new HashSet<Integer>();
-        states.add(state);
-        return waitForStates(states, waitTimeMs);
-    }
-
-    @Override
-    public EmbeddedTestServer getTestServer() {
-        return mTestServer;
-    }
-
-    public void castAndPauseDefaultVideoFromPage(String pagePath)
-            throws InterruptedException, TimeoutException {
-        Rect videoRect = castDefaultVideoFromPage(pagePath);
-
-        final Tab tab = getActivity().getActivityTab();
-
-        Rect pauseButton = playPauseButton(videoRect);
-
-        // Make sure the video has made some progress
-        Thread.sleep(RUN_TIME_MS);
-
-        tapButton(tab, pauseButton);
-        Assert.assertTrue("Not paused", waitForState(PlayerState.PAUSED, MAX_VIEW_TIME_MS));
-    }
-
-    private boolean videoReady(String videoElement, WebContents webContents) {
-        // Create a javascript function to check if the video meta-data has been loaded.
-        StringBuilder sb = new StringBuilder();
-        sb.append("(function() {");
-        sb.append("  var node = document.getElementById('" + videoElement + "');");
-        sb.append("  if (!node) return null;");
-        // Any video readyState value greater than 0 means that at least the meta-data has been
-        // loaded but we also need the a document readyState of complete to ensure that page has
-        // been laid out with the correct video size, and everything is drawn.
-        sb.append("  return node.readyState > 0 && document.readyState == 'complete';");
-        sb.append("})();");
-        String javascriptResult;
-        try {
-            javascriptResult =
-                    JavaScriptUtils.executeJavaScriptAndWaitForResult(webContents, sb.toString());
-            Assert.assertFalse("Failed to retrieve contents for " + videoElement,
-                    javascriptResult.trim().equalsIgnoreCase("null"));
-
-            Boolean ready = javascriptResult.trim().equalsIgnoreCase("true");
-            return ready;
-        } catch (InterruptedException e) {
-            Assert.fail("Interrupted");
-        } catch (TimeoutException e) {
-            Assert.fail("Javascript execution timed out");
-        }
-        return false;
-    }
-
-    public void waitUntilVideoReady(String videoElement, WebContents webContents) {
-        for (int time = 0; time < MAX_VIEW_TIME_MS; time += VIEW_RETRY_MS) {
-            try {
-                if (videoReady(videoElement, webContents)) return;
-            } catch (Exception e) {
-                Assert.fail(e.toString());
-            }
-            sleepNoThrow(VIEW_RETRY_MS);
-        }
-        Assert.fail("Video not ready");
-    }
-
-    public Rect prepareDefaultVideofromPage(String pagePath, Tab currentTab)
-            throws InterruptedException, TimeoutException {
-        loadUrl(mTestServer.getURL(pagePath));
-
-        WebContents webContents = currentTab.getWebContents();
-
-        waitUntilVideoReady(VIDEO_ELEMENT, webContents);
-
-        return DOMUtils.getNodeBounds(webContents, VIDEO_ELEMENT);
-    }
-
-    public Rect castDefaultVideoFromPage(String pagePath)
-            throws InterruptedException, TimeoutException {
-        final Tab tab = getActivity().getActivityTab();
-        final Rect videoRect = prepareDefaultVideofromPage(pagePath, tab);
-
-        castVideoAndWaitUntilPlaying(CAST_TEST_ROUTE, tab, videoRect);
-
-        return videoRect;
-    }
-
-    public void castVideoAndWaitUntilPlaying(
-            final String chromecastName, final Tab tab, final Rect videoRect) {
-        castVideo(chromecastName, tab, videoRect);
-
-        Assert.assertTrue("Video didn't start playing", waitUntilPlaying());
-    }
-
-    public void castVideo(final String chromecastName, final Tab tab, final Rect videoRect) {
-        Log.i(TAG, "castVideo, videoRect = " + videoRect);
-
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
-            @Override
-            public void run() {
-                RemoteMediaPlayerController playerController =
-                        RemoteMediaPlayerController.instance();
-                mMediaRouteController = playerController.getMediaRouteController(
-                        mTestServer.getURL(DEFAULT_VIDEO), mTestServer.getURL(DEFAULT_VIDEO_PAGE));
-                Assert.assertNotNull("Could not get MediaRouteController", mMediaRouteController);
-                mMediaRouteController.addUiListener(new TestListener());
-            }
-        });
-        tapCastButton(tab, videoRect);
-
-        // Wait for the test device to appear in the device list.
-        try {
-            UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
-        } catch (InterruptedException e) {
-            Assert.fail();
-        }
-
-        View testRouteButton = RouterTestUtils.waitForRouteButton(
-                getActivity(), chromecastName, MAX_VIEW_TIME_MS, VIEW_RETRY_MS);
-        Assert.assertNotNull("Test route not found", testRouteButton);
-
-        ClickUtils.mouseSingleClickView(
-                InstrumentationRegistry.getInstrumentation(), testRouteButton);
-    }
-
-    public void checkDisconnected() {
-        HashSet<Integer> disconnectedStates = new HashSet<Integer>();
-        disconnectedStates.add(PlayerState.FINISHED);
-        disconnectedStates.add(PlayerState.INVALIDATED);
-        waitForStates(disconnectedStates, MAX_VIEW_TIME_MS);
-        // Could use Assert.assertTrue(isDisconnected()) here, but retesting the individual aspects
-        // of disconnection gives more specific error messages.
-        CastNotificationControl notificationControl = CastNotificationControl.getForTests();
-        if (notificationControl != null && notificationControl.isShowingForTests()) {
-            Assert.fail("Failed to close notification");
-        }
-        Assert.assertEquals("Video still playing?", null, getUriPlaying());
-        Assert.assertTrue("RemoteMediaPlayerController not stopped", !isPlayingRemotely());
-    }
-
-    public void clickDisconnectFromRoute(Tab tab, Rect videoRect) {
-        // Click on the cast control button to stop casting
-        tapCastButton(tab, videoRect);
-
-        // Wait for the disconnect button
-        final View disconnectButton = RouterTestUtils.waitForView(new Callable<View>() {
-            @Override
-            public View call() {
-                FragmentManager fm = getActivity().getSupportFragmentManager();
-                if (fm == null) return null;
-                DialogFragment mediaRouteControllerFragment = (DialogFragment) fm.findFragmentByTag(
-                        "android.support.v7.mediarouter:MediaRouteControllerDialogFragment");
-                if (mediaRouteControllerFragment == null) return null;
-                Dialog dialog = mediaRouteControllerFragment.getDialog();
-                if (dialog == null) return null;
-                // The stop button (previously called disconnect) simply uses 'button1' in the
-                // latest version of the support library. See:
-                // https://cs.corp.google.com/#android/frameworks/support/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java&l=90.
-                // TODO(aberent) remove dependency on internals of support library
-                //               https://crbug/548599
-                return dialog.findViewById(android.R.id.button1);
-            }
-        }, MAX_VIEW_TIME_MS, VIEW_RETRY_MS);
-
-        Assert.assertNotNull("No disconnect button", disconnectButton);
-
-        ClickUtils.clickButton(disconnectButton);
-    }
-
-    /*
-     * Check that a (non-YouTube) video has started playing, and that all the controls have been
-     * correctly set up.
-     */
-    public void checkVideoStarted(String testVideo) {
-        // Check we have a notification
-        CastNotificationControl notificationControl = waitForCastNotification();
-        Assert.assertNotNull("No notification controller", notificationControl);
-        Assert.assertTrue("No notification", notificationControl.isShowingForTests());
-        // Check that we are playing the right video
-        waitUntilVideoCurrent(testVideo);
-        Assert.assertEquals("Wrong video playing", mTestServer.getURL(testVideo), getUriPlaying());
-
-        // Check that the RemoteMediaPlayerController and the (YouTube)MediaRouteController have
-        // been set up correctly
-        waitUntilPlaying();
-        RemoteMediaPlayerController playerController = RemoteMediaPlayerController.getIfExists();
-        Assert.assertNotNull("No RemoteMediaPlayerController", playerController);
-        Assert.assertTrue("Video not playing", isPlayingRemotely());
-        Assert.assertTrue("Wrong sort of MediaRouteController",
-                (playerController.getCurrentlyPlayingMediaRouteController()
-                                instanceof DefaultMediaRouteController));
-    }
-
-    public void sleepNoThrow(long timeout) {
-        try {
-            Thread.sleep(timeout);
-        } catch (InterruptedException e) {
-            Assert.fail(e.toString());
-        }
-    }
-
-    public void tapVideoFullscreenButton(final Tab tab, final Rect videoRect) {
-        tapButton(tab, fullscreenButton(videoRect));
-    }
-
-    public void tapCastButton(final Tab tab, final Rect videoRect) {
-        tapButton(tab, castButton(videoRect));
-    }
-
-    public void tapPlayPauseButton(final Tab tab, final Rect videoRect) {
-        tapButton(tab, playPauseButton(videoRect));
-    }
-
-    public CastNotificationControl waitForCastNotification() {
-        for (int time = 0; time < MAX_VIEW_TIME_MS; time += VIEW_RETRY_MS) {
-            CastNotificationControl result = ThreadUtils.runOnUiThreadBlockingNoException(
-                    new Callable<CastNotificationControl>() {
-                        @Override
-                        public CastNotificationControl call() {
-                            return CastNotificationControl.getForTests();
-                        }
-                    });
-            if (result != null) {
-                return result;
-            }
-            sleepNoThrow(VIEW_RETRY_MS);
-        }
-        return null;
-    }
-
-    public boolean waitUntilPlaying() {
-        return waitForState(PlayerState.PLAYING, MAX_VIEW_TIME_MS);
-    }
-
-    private boolean isDisconnected() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
-            @Override
-            public Boolean call() {
-                CastNotificationControl notificationControl = CastNotificationControl.getForTests();
-                if (notificationControl != null && notificationControl.isShowingForTests()) {
-                    return false;
-                }
-                if (getUriPlaying() != null) return false;
-                return !isPlayingRemotely();
-            }
-        });
-    }
-
-    private boolean waitUntilVideoCurrent(String testVideo) {
-        for (int time = 0; time < MAX_VIEW_TIME_MS; time += VIEW_RETRY_MS) {
-            if (mTestServer.getURL(testVideo).equals(getUriPlaying())) {
-                return true;
-            }
-            sleepNoThrow(VIEW_RETRY_MS);
-        }
-        return false;
-    }
-
-    public String getUriPlaying() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<String>() {
-            @Override
-            public String call() {
-                if (mMediaRouteController == null) return "";
-                return mMediaRouteController.getUriPlaying();
-            }
-        });
-    }
-
-    public long getRemotePositionMs() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Long>() {
-            @Override
-            public Long call() {
-                return getMediaRouteController().getPosition();
-            }
-        });
-    }
-
-    public long getRemoteDurationMs() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Long>() {
-            @Override
-            public Long call() {
-                return getMediaRouteController().getDuration();
-            }
-        });
-    }
-
-    public boolean isPlayingRemotely() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
-            @Override
-            public Boolean call() {
-                RemoteMediaPlayerController playerController =
-                        RemoteMediaPlayerController.getIfExists();
-                if (playerController == null) return false;
-                MediaRouteController routeController =
-                        playerController.getCurrentlyPlayingMediaRouteController();
-                if (routeController == null) return false;
-                return routeController.isPlaying();
-            }
-        });
-    }
-
-    public MediaRouteController getMediaRouteController() {
-        return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<MediaRouteController>() {
-            @Override
-            public MediaRouteController call() {
-                RemoteMediaPlayerController playerController =
-                        RemoteMediaPlayerController.getIfExists();
-                Assert.assertNotNull("No RemoteMediaPlayerController", playerController);
-                MediaRouteController routeController =
-                        playerController.getCurrentlyPlayingMediaRouteController();
-                Assert.assertNotNull("No MediaRouteController", routeController);
-                return routeController;
-            }
-        });
-    }
-
-    /*
-     * Functions to find the controls Unfortunately the controls are invisible to the code outside
-     * Blink, so this is highly dependent on the geometry defined in Blink css (see
-     * MediaControls.css & MediaControlsAndroid.css).
-     */
-    private static final int CONTROLS_HEIGHT = 35;
-    private static final int BUTTON_WIDTH = 35;
-    private static final int CONTROL_BAR_MARGIN = 5;
-    private static final int BUTTON_RIGHT_MARGIN = 9;
-    private static final int PLAY_BUTTON_LEFT_MARGIN = 9;
-    private static final int FULLSCREEN_BUTTON_LEFT_MARGIN = -5;
-
-    private Rect controlBar(Rect videoRect) {
-        int left = videoRect.left + CONTROL_BAR_MARGIN;
-        int right = videoRect.right - CONTROL_BAR_MARGIN;
-        int bottom = videoRect.bottom - CONTROL_BAR_MARGIN;
-        int top = videoRect.bottom - CONTROLS_HEIGHT;
-        return new Rect(left, top, right, bottom);
-    }
-
-    private Rect playPauseButton(Rect videoRect) {
-        Rect bar = controlBar(videoRect);
-        int left = bar.left + PLAY_BUTTON_LEFT_MARGIN;
-        int right = left + BUTTON_WIDTH;
-        return new Rect(left, bar.top, right, bar.bottom);
-    }
-
-    private Rect castButton(Rect videoRect) {
-        Rect bar = controlBar(videoRect);
-        int right = bar.right - BUTTON_RIGHT_MARGIN;
-        int left = right - BUTTON_WIDTH;
-        return new Rect(left, bar.top, right, bar.bottom);
-    }
-
-    private Rect fullscreenButton(Rect videoRect) {
-        Rect downloadButton = downloadButton(videoRect);
-        int right = downloadButton.left;
-        int left = right - BUTTON_WIDTH;
-        return new Rect(left, downloadButton.top, right, downloadButton.bottom);
-    }
-
-    private Rect downloadButton(Rect videoRect) {
-        Rect castButton = castButton(videoRect);
-        int right = castButton.right - BUTTON_RIGHT_MARGIN;
-        int left = right - BUTTON_WIDTH;
-        return new Rect(left, castButton.top, right, castButton.bottom);
-    }
-
-    private void tapButton(Tab tab, Rect rect) {
-        Coordinates coord = Coordinates.createFor(tab.getWebContents());
-        int clickX = (int) coord.fromLocalCssToPix(((float) (rect.left + rect.right)) / 2);
-        int clickY = (int) coord.fromLocalCssToPix(((float) (rect.top + rect.bottom)) / 2)
-                + getActivity().getCompositorViewHolder().getTopControlsHeightPixels();
-        // Click using a virtual mouse, since a touch may result in a disambiguation pop-up.
-        ClickUtils.mouseSingleClickView(
-                InstrumentationRegistry.getInstrumentation(), tab.getView(), clickX, clickY);
-    }
-}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastVideoControlsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastVideoControlsTest.java
deleted file mode 100644
index 113223b..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/remote/CastVideoControlsTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.DEFAULT_VIDEO_PAGE;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.MAX_VIEW_TIME_MS;
-import static org.chromium.chrome.browser.media.remote.CastTestRule.VIEW_RETRY_MS;
-
-import android.graphics.Rect;
-import android.support.test.filters.LargeTest;
-
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisabledTest;
-import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.Restriction;
-import org.chromium.base.test.util.RetryOnFailure;
-import org.chromium.chrome.browser.ChromeSwitches;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.media.MediaSwitches;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * Instrumentation tests for the fullscreen cast controls.
- */
-@RunWith(ChromeJUnit4ClassRunner.class)
-@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
-        "disable-features=" + MediaSwitches.USE_MODERN_MEDIA_CONTROLS})
-public class CastVideoControlsTest {
-    @Rule
-    public CastTestRule mCastTestRule = new CastTestRule();
-
-    private static final long PAUSE_TEST_TIME_MS = 1000;
-
-    /*
-     * Test the pause button.
-     */
-    @Test
-    @DisabledTest // crbug.com/907307
-    @Feature({"VideoFling"})
-    @LargeTest
-    @RetryOnFailure
-    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) // crbug.com/652872
-    public void testPauseButton() throws InterruptedException, TimeoutException {
-        Rect videoRect = mCastTestRule.castDefaultVideoFromPage(DEFAULT_VIDEO_PAGE);
-
-        final Tab tab = mCastTestRule.getActivity().getActivityTab();
-
-        mCastTestRule.tapPlayPauseButton(tab, videoRect);
-        // The new position is sent in a separate message, so we have to wait a bit before
-        // fetching it.
-        long position = mCastTestRule.getRemotePositionMs();
-        boolean paused = false;
-        for (int time = 0; time < MAX_VIEW_TIME_MS; time += VIEW_RETRY_MS) {
-            Thread.sleep(VIEW_RETRY_MS);
-            long newPosition = mCastTestRule.getRemotePositionMs();
-            if (newPosition == position) {
-                paused = true;
-                break;
-            }
-            position = newPosition;
-        }
-        // Check we have paused before the end of the video (with a fudge factor for timing
-        // variation)
-        Assert.assertTrue("Pause didn't stop playback",
-                paused || position < mCastTestRule.getRemoteDurationMs() - 100);
-        mCastTestRule.tapPlayPauseButton(tab, videoRect);
-        Thread.sleep(PAUSE_TEST_TIME_MS);
-        Assert.assertTrue(
-                "Run didn't restart playback", position < mCastTestRule.getRemotePositionMs());
-    }
-}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java
index 8297c53..67e82ea 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestRule.java
@@ -113,6 +113,7 @@
     final CallbackHelper mBillingAddressChangeProcessed;
     final CallbackHelper mShowFailed;
     final CallbackHelper mCanMakePaymentQueryResponded;
+    final CallbackHelper mHasEnrolledInstrumentQueryResponded;
     final CallbackHelper mExpirationMonthChange;
     final CallbackHelper mPaymentResponseReady;
     PaymentRequestUI mUI;
@@ -144,6 +145,7 @@
         mPaymentResponseReady = new CallbackHelper();
         mShowFailed = new CallbackHelper();
         mCanMakePaymentQueryResponded = new CallbackHelper();
+        mHasEnrolledInstrumentQueryResponded = new CallbackHelper();
         mWebContentsRef = new AtomicReference<>();
         mTestFilePath = testFileName.startsWith("data:")
                 ? testFileName
@@ -218,10 +220,7 @@
         return mCanMakePaymentQueryResponded;
     }
     public CallbackHelper getHasEnrolledInstrumentQueryResponded() {
-        // TODO(https://crbug.com/915907): return mHasEnrolledInstrumentQueryResponded once
-        // hasEnrolledInstrument is exposed in the PaymentRequest JavaScript API and browser tests
-        // are switched over to use the new API.
-        return mCanMakePaymentQueryResponded;
+        return mHasEnrolledInstrumentQueryResponded;
     }
     public CallbackHelper getExpirationMonthChange() {
         return mExpirationMonthChange;
@@ -919,10 +918,7 @@
     @Override
     public void onPaymentRequestServiceHasEnrolledInstrumentQueryResponded() {
         ThreadUtils.assertOnUiThread();
-        // TODO(https://crbug.com/915907): return mHasEnrolledInstrumentQueryResponded once
-        // hasEnrolledInstrument is exposed in the PaymentRequest JavaScript API and browser tests
-        // are switched over to use the new API.
-        mCanMakePaymentQueryResponded.notifyCalled();
+        mHasEnrolledInstrumentQueryResponded.notifyCalled();
     }
 
     @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/profiling_host/ProfilingProcessHostAndroidTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/profiling_host/ProfilingProcessHostAndroidTest.java
index d5c59aa..f229514 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/profiling_host/ProfilingProcessHostAndroidTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/profiling_host/ProfilingProcessHostAndroidTest.java
@@ -43,36 +43,22 @@
     public void
     testModeBrowser() throws Exception {
         HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(shim.runTestForMode(
-                "browser", false, "native-include-thread-names", true, false, false));
+        Assert.assertTrue(
+                shim.runTestForMode("browser", false, "native-include-thread-names", false, false));
     }
 
     @Test
     @MediumTest
     public void testModeBrowserDynamic() throws Exception {
         HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(shim.runTestForMode("browser", true, "native", true, false, false));
-    }
-
-    @Test
-    @MediumTest
-    public void testModeBrowserDynamicNonStreaming() throws Exception {
-        HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(shim.runTestForMode("browser", true, "native", false, false, false));
+        Assert.assertTrue(shim.runTestForMode("browser", true, "native", false, false));
     }
 
     @Test
     @MediumTest
     public void testModeBrowserDynamicPseudo() throws Exception {
         HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", true, false, false));
-    }
-
-    @Test
-    @MediumTest
-    public void testModeBrowserDynamicPseudoNonStreaming() throws Exception {
-        HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", false, false, false));
+        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", false, false));
     }
 
     // Non-browser processes must be profiled with a command line flag, since
@@ -87,8 +73,7 @@
     Add({"memlog=all-renderers", "memlog-stack-mode=pseudo", "memlog-sampling-rate=1"})
     public void testModeRendererPseudo() throws Exception {
         HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(
-                shim.runTestForMode("all-renderers", false, "pseudo", true, false, false));
+        Assert.assertTrue(shim.runTestForMode("all-renderers", false, "pseudo", false, false));
     }
 
     @Test
@@ -96,28 +81,27 @@
     @CommandLineFlags.Add({"memlog=gpu", "memlog-stack-mode=pseudo", "memlog-sampling-rate=1"})
     public void testModeGpuPseudo() throws Exception {
         HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(shim.runTestForMode("gpu", false, "native", true, false, false));
+        Assert.assertTrue(shim.runTestForMode("gpu", false, "native", false, false));
     }
 
     @Test
     @MediumTest
     public void testModeBrowserDynamicPseudoSampleEverything() throws Exception {
         HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", true, true, true));
+        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", true, true));
     }
 
     @Test
     @MediumTest
     public void testModeBrowserDynamicPseudoSamplePartial() throws Exception {
         HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", true, true, false));
+        Assert.assertTrue(shim.runTestForMode("browser", true, "pseudo", true, false));
     }
 
     @Test
     @MediumTest
     public void testModeBrowserAndAllUtility() throws Exception {
         HeapProfilingTestShim shim = new HeapProfilingTestShim();
-        Assert.assertTrue(
-                shim.runTestForMode("utility-and-browser", true, "pseudo", true, true, false));
+        Assert.assertTrue(shim.runTestForMode("utility-and-browser", true, "pseudo", true, false));
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/remote/AbstractMediaRouteControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/remote/AbstractMediaRouteControllerTest.java
deleted file mode 100644
index 5cf8fd2f..0000000
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/remote/AbstractMediaRouteControllerTest.java
+++ /dev/null
@@ -1,460 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.net.Uri;
-import android.support.v7.media.MediaItemStatus;
-import android.support.v7.media.MediaRouteSelector;
-import android.support.v7.media.MediaRouter;
-import android.support.v7.media.MediaRouter.Callback;
-import android.support.v7.media.MediaRouter.ProviderInfo;
-import android.support.v7.media.MediaRouter.RouteInfo;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.robolectric.annotation.Config;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.shadows.multidex.ShadowMultiDex;
-import org.robolectric.util.ReflectionHelpers;
-
-import org.chromium.base.CommandLine;
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.Feature;
-import org.chromium.chrome.browser.media.remote.MediaRouteController.MediaStateListener;
-import org.chromium.chrome.browser.media.remote.MediaRouteController.UiListener;
-
-/** Tests for {@link AbstractMediaRouteController}. */
-@RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE,
-        shadows = {AbstractMediaRouteControllerTest.ShadowMediaRouter.class, ShadowMultiDex.class})
-public class AbstractMediaRouteControllerTest {
-    /** Reset the environment before each test. */
-    @Before
-    public void beforeTest() {
-        // TODO(dgn): Remove when command line flags are not used anymore to detect debug
-        // see http://crbug.com/469649
-        CommandLine.init(new String[] {});
-
-
-        ShadowMediaRouter.sMediaRouter = null;
-        ShadowMediaRouter.sCallback = null;
-        DummyMediaRouteController.sMediaRouteSelector = mock(MediaRouteSelector.class);
-    }
-
-    /**
-     * Test method for {@link AbstractMediaRouteController#isPlaying()}.
-     *
-     * Checks that it returns the correct value for all possible playback states.
-     */
-    @Test
-    @Feature({"MediaRemote"})
-    public void testIsPlaying() {
-        // Using a spy here to override some methods.
-        AbstractMediaRouteController mediaRouteCtrl = spy(new DummyMediaRouteController());
-
-        // Default
-        assertFalse(mediaRouteCtrl.isPlaying());
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_BUFFERING);
-        assertTrue(mediaRouteCtrl.isPlaying());
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_CANCELED);
-        assertFalse(mediaRouteCtrl.isPlaying());
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_ERROR);
-        assertFalse(mediaRouteCtrl.isPlaying());
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_FINISHED);
-        assertFalse(mediaRouteCtrl.isPlaying());
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_INVALIDATED);
-        assertFalse(mediaRouteCtrl.isPlaying());
-
-        doReturn(0L).when(mediaRouteCtrl).getPosition();
-        doReturn(5000L).when(mediaRouteCtrl).getDuration();
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_PAUSED);
-        assertFalse(mediaRouteCtrl.isPlaying());
-
-        doReturn(5000L).when(mediaRouteCtrl).getPosition();
-        doReturn(5000L).when(mediaRouteCtrl).getDuration();
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_PAUSED);
-        assertFalse(mediaRouteCtrl.isPlaying());
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_PENDING);
-        assertFalse(mediaRouteCtrl.isPlaying());
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_PLAYING);
-        assertTrue(mediaRouteCtrl.isPlaying());
-    }
-
-    /**
-     * Test method for {@link AbstractMediaRouteController#isBeingCast()}.
-     *
-     * Checks that it returns the correct value for all possible playback states.
-     */
-    @Test
-    @Feature({"MediaRemote"})
-    public void testIsBeingCast() {
-        // Using a spy here to override some methods.
-        AbstractMediaRouteController mediaRouteCtrl = spy(new DummyMediaRouteController());
-
-        // Default
-        assertFalse(mediaRouteCtrl.isBeingCast());
-
-        mediaRouteCtrl.setPreparedForTesting();
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_BUFFERING);
-        assertTrue(mediaRouteCtrl.isBeingCast());
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_CANCELED);
-        assertFalse(mediaRouteCtrl.isBeingCast());
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_ERROR);
-        assertFalse(mediaRouteCtrl.isBeingCast());
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_FINISHED);
-        assertFalse(mediaRouteCtrl.isBeingCast());
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_INVALIDATED);
-        assertFalse(mediaRouteCtrl.isBeingCast());
-
-        doReturn(0L).when(mediaRouteCtrl).getPosition();
-        doReturn(5000L).when(mediaRouteCtrl).getDuration();
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_PAUSED);
-        assertTrue(mediaRouteCtrl.isBeingCast());
-
-        doReturn(5000L).when(mediaRouteCtrl).getPosition();
-        doReturn(5000L).when(mediaRouteCtrl).getDuration();
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_PAUSED);
-        assertFalse(mediaRouteCtrl.isBeingCast());
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_PENDING);
-        assertTrue(mediaRouteCtrl.isBeingCast());
-
-        mediaRouteCtrl.setPlayerStateForMediaItemState(MediaItemStatus.PLAYBACK_STATE_PLAYING);
-        assertTrue(mediaRouteCtrl.isBeingCast());
-
-        mediaRouteCtrl.setUnprepared();
-        assertFalse(mediaRouteCtrl.isBeingCast());
-    }
-
-    /** Test method for {@link AbstractMediaRouteController#isRemotePlaybackAvailable()}.*/
-    @Test
-    @Feature({"MediaRemote"})
-    public void testIsRemotePlaybackAvailable() {
-        MediaRouter mediaRouter = mock(MediaRouter.class);
-        AbstractMediaRouteController mediaRouteCtrl = new DummyMediaRouteController();
-        when(mediaRouter.getSelectedRoute())
-                .thenReturn(createRouteInfo(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE));
-        when(mediaRouter.isRouteAvailable(any(MediaRouteSelector.class), anyInt()))
-                .thenReturn(true);
-
-        // Default
-        assertFalse(mediaRouteCtrl.isRemotePlaybackAvailable());
-
-        ShadowMediaRouter.sMediaRouter = mediaRouter;
-        mediaRouteCtrl = spy(new DummyMediaRouteController());
-        assertTrue(mediaRouteCtrl.isRemotePlaybackAvailable());
-
-        when(mediaRouter.getSelectedRoute())
-                .thenReturn(createRouteInfo(MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL));
-        assertTrue(mediaRouteCtrl.isRemotePlaybackAvailable());
-
-        when(mediaRouter.isRouteAvailable(any(MediaRouteSelector.class), anyInt()))
-                .thenReturn(false);
-        assertFalse(mediaRouteCtrl.isRemotePlaybackAvailable());
-
-        when(mediaRouter.getSelectedRoute())
-                .thenReturn(createRouteInfo(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE));
-        assertTrue(mediaRouteCtrl.isRemotePlaybackAvailable());
-    }
-
-    /**
-     * Test method for
-     * {@link AbstractMediaRouteController#addMediaStateListener(MediaStateListener)} and
-     * {@link AbstractMediaRouteController#removeMediaStateListener(MediaStateListener)}
-     *
-     * Makes sure that listeners gets notified when they are added and don't get notified
-     * when removed.
-     */
-    @Test
-    @Feature({"MediaRemote"})
-    public void testAddRemoveMediaStateListener() {
-        MediaStateListener listener = mock(MediaStateListener.class);
-        MediaStateListener otherListener = mock(MediaStateListener.class);
-        MediaRouter mediaRouter = ShadowMediaRouter.createCapturingMock();
-        when(mediaRouter.isRouteAvailable(any(MediaRouteSelector.class), anyInt()))
-                .thenReturn(true);
-        ShadowMediaRouter.sMediaRouter = mediaRouter;
-
-        // Creating the MediaRouteController
-        AbstractMediaRouteController mediaRouteController = new DummyMediaRouteController();
-        assertNotNull(mediaRouteController.getMediaRouter());
-
-        // 1. #addMediaStateListener()
-        mediaRouteController.addMediaStateListener(listener);
-
-        // The route selector should get notified of new states.
-        verify(mediaRouter)
-                .addCallback(any(MediaRouteSelector.class), any(Callback.class), anyInt());
-        verify(listener).onRouteAvailabilityChanged(true);
-
-        // Check the behavior difference between the first and subsequent additions.
-        mediaRouteController.addMediaStateListener(otherListener);
-        verify(otherListener).onRouteAvailabilityChanged(true);
-        // The call count should not change.
-        verify(mediaRouter)
-                .addCallback(any(MediaRouteSelector.class), any(Callback.class), anyInt());
-        verify(listener).onRouteAvailabilityChanged(true);
-
-        // 2. #removeMediaStateListener()
-        mediaRouteController.removeMediaStateListener(otherListener);
-
-        // The removed listener should not be notified of changes anymore.
-        when(mediaRouter.isRouteAvailable(any(MediaRouteSelector.class), anyInt()))
-                .thenReturn(false);
-        ShadowMediaRouter.sCallback.onRouteRemoved(mediaRouter, null);
-        verifyNoMoreInteractions(otherListener);
-        verify(listener).onRouteAvailabilityChanged(false);
-        verify(mediaRouter, times(0)).removeCallback(any(Callback.class));
-
-        mediaRouteController.removeMediaStateListener(listener);
-
-        when(mediaRouter.isRouteAvailable(any(MediaRouteSelector.class), anyInt()))
-                .thenReturn(true);
-        ShadowMediaRouter.sCallback.onRouteAdded(mediaRouter, null);
-        verifyNoMoreInteractions(otherListener);
-        verifyNoMoreInteractions(listener);
-        verify(mediaRouter).removeCallback(any(Callback.class));
-    }
-
-    /**
-     * Test method for
-     * {@link AbstractMediaRouteController#addMediaStateListener(MediaStateListener)}
-     *
-     * Tests that listeners are not used (state not initialized) when the media router
-     * is not initialized.
-     */
-    @Test
-    @Feature({"MediaRemote"})
-    public void testAddMediaStateListenerInitFailed() {
-        MediaStateListener listener = mock(MediaStateListener.class);
-
-        AbstractMediaRouteController mediaRouteController = new DummyMediaRouteController();
-        mediaRouteController.addMediaStateListener(listener);
-
-        verify(listener, never()).onRouteAvailabilityChanged(anyBoolean());
-    }
-
-    /** Test method for {@link AbstractMediaRouteController#prepareMediaRoute()}.*/
-    @Test
-    @Feature({"MediaRemote"})
-    public void testPrepareMediaRoute() {
-        // Check when no media router, check that not done twice
-
-        ShadowMediaRouter.sMediaRouter = mock(MediaRouter.class);
-        AbstractMediaRouteController mediaRouteCtrl = new DummyMediaRouteController();
-
-        verify(ShadowMediaRouter.sMediaRouter, times(0))
-                .addCallback(any(MediaRouteSelector.class), any(Callback.class), anyInt());
-
-        mediaRouteCtrl.prepareMediaRoute();
-        verify(ShadowMediaRouter.sMediaRouter, times(1))
-                .addCallback(eq(DummyMediaRouteController.sMediaRouteSelector), any(Callback.class),
-                        eq(MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY));
-
-        mediaRouteCtrl.prepareMediaRoute();
-        verify(ShadowMediaRouter.sMediaRouter, times(1))
-                .addCallback(any(MediaRouteSelector.class), any(Callback.class), anyInt());
-    }
-
-    /**
-     * Test method for {@link AbstractMediaRouteController#addUiListener(UiListener)} and
-     * {@link AbstractMediaRouteController#removeMediaStateListener(MediaStateListener)}.
-     */
-    @Test
-    @Feature({"MediaRemote"})
-    public void testAddRemoveUiListener() {
-        DummyMediaRouteController mediaRouteCtrl = new DummyMediaRouteController();
-        UiListener listener = mock(UiListener.class);
-
-        mediaRouteCtrl.addUiListener(listener);
-        mediaRouteCtrl.verifyListenerActivation(1, listener);
-
-        // Should not be added twice.
-        mediaRouteCtrl.addUiListener(listener);
-        mediaRouteCtrl.verifyListenerActivation(2, listener);
-
-        mediaRouteCtrl.removeUiListener(listener);
-        mediaRouteCtrl.verifyListenerActivation(2, listener);
-    }
-
-    private static RouteInfo createRouteInfo(int playbackType) {
-        Class<?>[] paramClasses = new Class[] {ProviderInfo.class, String.class, String.class};
-        Object[] paramValues = new Object[] {null, "", ""};
-        RouteInfo routeInfo = ReflectionHelpers.callConstructor(RouteInfo.class,
-                ReflectionHelpers.ClassParameter.fromComponentLists(paramClasses, paramValues));
-        ReflectionHelpers.setField(routeInfo, "mPlaybackType", playbackType);
-        return routeInfo;
-    }
-
-    /** Shadow needed because getInstance() can't be mocked */
-    @Implements(MediaRouter.class)
-    public static class ShadowMediaRouter {
-        public static MediaRouter sMediaRouter;
-        public static Callback sCallback;
-
-        @Implementation
-        public static MediaRouter getInstance(Context context) {
-            return sMediaRouter;
-        }
-
-        /**
-         * Creates a {@link MediaRouter} mock that will capture the callback argument
-         * when {@link MediaRouter#addCallback(MediaRouteSelector, Callback, int)} is called
-         * and sets it to {@link #sCallback}.
-         */
-        public static MediaRouter createCapturingMock() {
-            MediaRouter mediaRouter = mock(MediaRouter.class);
-
-            final ArgumentCaptor<Callback> callbackArg = ArgumentCaptor.forClass(Callback.class);
-            final Answer<?> addCallbackAnswer = new Answer<Object>() {
-                @Override
-                public Object answer(InvocationOnMock invocation) {
-                    sCallback = callbackArg.getValue();
-                    return null;
-                }
-            };
-
-            Mockito.doAnswer(addCallbackAnswer)
-                    .when(mediaRouter)
-                    .addCallback(any(MediaRouteSelector.class), callbackArg.capture(), anyInt());
-            return mediaRouter;
-        }
-    }
-
-    /**
-     * A dummy class used here to be able to instantiate the abstract class while calling its
-     * constructor. {@link AbstractMediaRouteController#buildMediaRouteSelector()} needs to be
-     * implemented before the object's construction. Mockito's mocks don't call constructors.
-     */
-    private static class DummyMediaRouteController extends AbstractMediaRouteController {
-        /**
-         * MediaRouteSelector required during MediaRouteController's constructor call via
-         * {@link #buildMediaRouteSelector()}
-         */
-        public static MediaRouteSelector sMediaRouteSelector;
-
-        @Override
-        public boolean initialize() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean canPlayMedia(String sourceUrl, String frameUrl) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public MediaRouteSelector buildMediaRouteSelector() {
-            return sMediaRouteSelector;
-        }
-
-        @Override
-        public boolean reconnectAnyExistingRoute() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void setDataSource(Uri uri, String cookies) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void prepareAsync(String frameUrl, long startPositionMillis) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void setRemoteVolume(int delta) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void resume() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void pause() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public long getPosition() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public long getDuration() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void seekTo(long msec) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void release() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        protected void onRouteAddedEvent(MediaRouter router, RouteInfo route) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void onRouteSelected(MediaStateListener player, MediaRouter router,
-                RouteInfo route) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        protected void onRouteUnselectedEvent(MediaRouter router, RouteInfo route) {
-            throw new UnsupportedOperationException();
-        }
-
-        public void verifyListenerActivation(int times, UiListener listener) {
-            String testTitle = "foo";
-            updateTitle(testTitle); // protected methods, needs to be called within the class.
-            verify(listener, times(times)).onTitleChanged(testTitle);
-        }
-    }
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/remote/MediaUrlResolverTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/remote/MediaUrlResolverTest.java
deleted file mode 100644
index 024ed80..0000000
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/remote/MediaUrlResolverTest.java
+++ /dev/null
@@ -1,393 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.junit.Assert.assertThat;
-
-import android.net.Uri;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowApplication;
-
-import org.chromium.base.CommandLine;
-import org.chromium.base.task.AsyncTask;
-import org.chromium.base.task.test.CustomShadowAsyncTask;
-import org.chromium.base.test.BaseRobolectricTestRunner;
-
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLStreamHandler;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Unit tests (run on host) for {@link org.chromium.chrome.browser.media.remote.MediaUrlResolver}.
- */
-@RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE, shadows = {CustomShadowAsyncTask.class})
-public class MediaUrlResolverTest {
-    // Constants copied from MediaUrlResolver. Don't use the copies in MediaUrlResolver
-    // since we want the tests to detect if these are changed or corrupted.
-    private static final String COOKIES_HEADER_NAME = "Cookies";
-    private static final String CORS_HEADER_NAME = "Access-Control-Allow-Origin";
-    private static final String RANGE_HEADER_NAME = "Range";
-    private static final String RANGE_HEADER_VALUE = "bytes=0-65536";
-
-    private static final String USER_AGENT_HEADER_NAME = "User-Agent";
-    private static final String CHROMECAST_ORIGIN = "https://www.gstatic.com";
-
-    private class TestDelegate implements MediaUrlResolver.Delegate {
-        private final String mCookies;
-        private final Uri mInputUri;
-
-        private boolean mReturnedPlayable;
-        private Uri mReturnedUri;
-
-        TestDelegate(Uri uri, String cookies) {
-            mInputUri = uri;
-            mCookies = cookies;
-        }
-
-        @Override
-        public String getCookies() {
-            return mCookies;
-        }
-
-        @Override
-        public Uri getUri() {
-            return mInputUri;
-        }
-
-        @Override
-        public void deliverResult(Uri uri, boolean playable) {
-            mReturnedUri = uri;
-            mReturnedPlayable = playable;
-        }
-
-        Uri getReturnedUri() {
-            return mReturnedUri;
-        }
-
-        boolean isPlayable() {
-            return mReturnedPlayable;
-        }
-    }
-
-    /**
-     * Dummy HttpURLConnection class that returns canned headers. Also prevents the test from going
-     * out to the real network.
-     */
-    private class DummyUrlConnection extends HttpURLConnection {
-
-        private final URL mUrl;
-
-        protected DummyUrlConnection(URL u) {
-            super(u);
-            mUrl = u;
-        }
-
-        @Override
-        public void connect() throws IOException {
-            if (mThrowIOException) throw new IOException();
-        }
-
-        @Override
-        public void disconnect() {
-        }
-
-        @Override
-        public Map<String, List<String>> getHeaderFields() {
-            return mReturnedHeaders;
-        }
-
-        @Override
-        public URL getURL() {
-            return mReturnedUrl == null ? mUrl : mReturnedUrl;
-        }
-
-        @Override
-        public void setRequestProperty(String key, String value) {
-            mRequestProperties.put(key, value);
-        }
-
-        @Override
-        public boolean usingProxy() {
-            return false;
-        }
-
-        @Override
-        public int getResponseCode() {
-            return mReturnedResponseCode;
-        }
-
-    }
-
-    /**
-     * Class for plugging in DummyUrlConnection
-     */
-    private class DummyUrlStreamHandler extends URLStreamHandler {
-
-        @Override
-        protected URLConnection openConnection(URL u) throws IOException {
-            return new DummyUrlConnection(u);
-        }
-
-    }
-
-    private Map<String, String> mRequestProperties;
-
-    private Map<String, List<String>> mReturnedHeaders;
-
-    private URL mReturnedUrl;
-
-    private int mReturnedResponseCode;
-
-    private boolean mThrowIOException;
-
-    @Before
-    public void setup() {
-        // Initialize the command line to avoid a crash when the code checks the logging flag.
-        CommandLine.init(new String[0]);
-    }
-
-    /**
-     * Test method for {@link MediaUrlResolver#MediaUrlResolver} with valid MPEG4 URL
-     *
-     * @throws MalformedURLException
-     */
-    @Test
-    public void testMediaUrlResolver_validMpeg4() throws MalformedURLException {
-        // A valid mpeg4 URI is playable and unchanged.
-        Uri uri = Uri.parse("http://example.com/test.mp4");
-        TestDelegate delegate =  resolveUri(uri, null, 200, null, null, false);
-        assertThat("A valid mp4 uri is unchanged", delegate.getReturnedUri(), equalTo(uri));
-        assertThat("A valid mp4 uri is playable", delegate.isPlayable(), equalTo(true));
-
-        // Check that the correct message was sent
-        assertThat("The message contained the user agent name",
-                mRequestProperties.get(USER_AGENT_HEADER_NAME), equalTo("User agent"));
-        assertThat("The message contained the range header",
-                mRequestProperties.get(RANGE_HEADER_NAME), equalTo(RANGE_HEADER_VALUE));
-        assertThat("The message didn't contain any cookies",
-                mRequestProperties.get(COOKIES_HEADER_NAME), nullValue());
-    }
-
-    /**
-     * Test method for {@link MediaUrlResolver#MediaUrlResolver} testing a null URL.
-     */
-    @Test
-    public void testMediaUrlResolver_nullUri() {
-        // An null URL isn't playable
-        TestDelegate delegate =  resolveUri(null, null, 200, null, null, false);
-
-        assertThat("An empty uri remains empty", delegate.getReturnedUri(), equalTo(Uri.EMPTY));
-        assertThat("An empty uri isn't playable", delegate.isPlayable(), equalTo(false));
-    }
-
-    /**
-     * Test method for {@link MediaUrlResolver#MediaUrlResolver} testing empty URL.
-     */
-    @Test
-    public void testMediaUrlResolver_emptyUri() {
-        // An empty URL isn't playable
-        TestDelegate delegate =  resolveUri(Uri.EMPTY, null, 200, null, null, false);
-
-        assertThat("An empty uri remains empty", delegate.getReturnedUri(), equalTo(Uri.EMPTY));
-        assertThat("An empty uri isn't playable", delegate.isPlayable(), equalTo(false));
-    }
-
-    /**
-     * Test method for {@link MediaUrlResolver#MediaUrlResolver} setting the cookies
-     *
-     * @throws MalformedURLException
-     */
-    @Test
-    public void testMediaUrlResolver_cookies() throws MalformedURLException {
-        // Check that cookies are sent correctly.
-        Uri uri = Uri.parse("http://example.com/test.mp4");
-        TestDelegate delegate =  resolveUri(uri, "Cookies!", 200, null, null, false);
-
-        assertThat("The message contained the cookies",
-                mRequestProperties.get(COOKIES_HEADER_NAME), equalTo("Cookies!"));
-    }
-
-    /**
-     * Test method for {@link MediaUrlResolver#MediaUrlResolver} with a valid HLS URL and
-     * the Range-Request header.
-     *
-     * @throws MalformedURLException
-     */
-    @Test
-    public void testMediaUrlResolver_validHLSNoRange() throws MalformedURLException {
-        // Don't set range request header for manifest URLs like HLS.
-        Uri uri = Uri.parse("http://example.com/test.m3u8");
-        TestDelegate delegate =  resolveUri(uri, null, 200, null, null, false);
-        assertThat("The message didn't have the range header",
-                mRequestProperties.get(RANGE_HEADER_NAME), nullValue());
-    }
-
-    /**
-     * Test method for {@link MediaUrlResolver#MediaUrlResolver} updating the URL in case of
-     * redirects.
-     *
-     * @throws MalformedURLException
-     */
-    @Test
-    public void testMediaUrlResolver_redirect() throws MalformedURLException {
-        // A redirected URI is retuend and is playable.
-        Uri uri = Uri.parse("http://example.com/test.mp4");
-        URL returnedUri = new URL("http://cdn.example.com/foo/test.mp4");
-        TestDelegate delegate =  resolveUri(uri, null, 200, returnedUri, null, false);
-
-        assertThat("A redirected uri is returned",
-                delegate.getReturnedUri(), equalTo(Uri.parse(returnedUri.toString())));
-        assertThat("A redirected uri is playable", delegate.isPlayable(), equalTo(true));
-    }
-
-    /**
-     * Test method for {@link MediaUrlResolver#MediaUrlResolver} testing bad response code.
-     *
-     * @throws MalformedURLException
-     */
-    @Test
-    public void testMediaUrlResolver_serverError() {
-        // A valid URL is unplayable and an empty URL is returned if the server request fails.
-        Uri uri = Uri.parse("http://example.com/test.mp4");
-        TestDelegate delegate =  resolveUri(uri, null, 404, null, null, false);
-
-        assertThat("An empty uri is returned on server error",
-                delegate.getReturnedUri(), equalTo(Uri.EMPTY));
-        assertThat("Server error means unplayable uri", delegate.isPlayable(), equalTo(false));
-    }
-
-    /**
-     * Test method for {@link MediaUrlResolver#MediaUrlResolver} when a network error happens.
-     *
-     * @throws MalformedURLException
-     */
-    @Test
-    public void testMediaUrlResolver_networkError() throws MalformedURLException {
-        // A random, non parsable, URI of unknown type is treated as not playable.
-        Uri uri = Uri.parse("http://example.com/test.mp4");
-        TestDelegate delegate =  resolveUri(uri, null, 404, null, null, true);
-
-        assertThat("An empty uri is returned on network error",
-                delegate.getReturnedUri(), equalTo(Uri.EMPTY));
-        assertThat("Network error means unplayable uri", delegate.isPlayable(), equalTo(false));
-    }
-
-    /**
-     * Test method for {@link MediaUrlResolver#MediaUrlResolver} with valid MPEG4 URL and compatible
-     * CORS header in the response.
-     *
-     * @throws MalformedURLException
-     */
-    @Test
-    public void testMediaUrlResolver_validMpeg4CompatibleCors() throws MalformedURLException {
-        // If a compatible CORS header returned, a valid mpeg4 URI is playable and unchanged.
-        Uri uri = Uri.parse("http://example.com/test.mp4");
-        HashMap<String, List<String>> corsHeaders = new HashMap<String, List<String>>();
-        corsHeaders.put(CORS_HEADER_NAME, Lists.newArrayList(CHROMECAST_ORIGIN));
-        TestDelegate delegate =  resolveUri(uri, null, 200, null, corsHeaders, false);
-        assertThat("A valid mp4 uri with CORS is unchanged",
-                delegate.getReturnedUri(), equalTo(uri));
-        assertThat("A valid mp4 uri with CORS is playable", delegate.isPlayable(), equalTo(true));
-    }
-
-    /**
-     * Test method for {@link MediaUrlResolver#MediaUrlResolver} with valid MPEG4 URL and
-     * incompatible CORS header in the response.
-     *
-     * @throws MalformedURLException
-     */
-    @Test
-    public void testMediaUrlResolver_validMpeg4InompatilbeCors() throws MalformedURLException {
-        // If an incompatible CORS header returned, a valid mpeg4 URI is not playable but unchanged.
-        Uri uri = Uri.parse("http://example.com/test.mp4");
-        HashMap<String, List<String>> corsHeaders = new HashMap<String, List<String>>();
-        corsHeaders.put(CORS_HEADER_NAME, Lists.newArrayList("http://google.com"));
-
-        TestDelegate delegate =  resolveUri(uri, null, 200, null, corsHeaders, false);
-        assertThat("A valid mp4 uri with incompatible CORS is unchanged",
-                delegate.getReturnedUri(), equalTo(uri));
-        assertThat("A valid mp4 uri with incompatible CORS is not playable",
-                delegate.isPlayable(), equalTo(false));
-    }
-
-    /**
-     * Test method for {@link MediaUrlResolver#MediaUrlResolver} with a valid HLS URL and no CORS.
-     *
-     * @throws MalformedURLException
-     */
-    @Test
-    public void testMediaUrlResolver_validHLSNoCors() throws MalformedURLException {
-        // A valid mpeg4 URI is playable and unchanged.
-        Uri uri = Uri.parse("http://example.com/test.m3u8");
-        TestDelegate delegate =  resolveUri(uri, null, 200, null, null, false);
-
-        assertThat("A valid HLS uri with no CORS is unchanged",
-                delegate.getReturnedUri(), equalTo(uri));
-        assertThat("A valid HLS uri with no CORS is not playable",
-                delegate.isPlayable(), equalTo(false));
-    }
-
-    /**
-     * Test method for {@link MediaUrlResolver#MediaUrlResolver} with an unknown media type.
-     *
-     * @throws MalformedURLException
-     */
-    @Test
-    public void testMediaUrlResolver_unknownMediaType() throws MalformedURLException {
-        // A URI with an unknown media type is unchanged but not playable.
-        Uri uri = Uri.parse("http://example.com/test.foo");
-        TestDelegate delegate =  resolveUri(uri, null, 200, null, null, false);
-
-        assertThat("A valid uri with unknown media type is unchanged",
-                delegate.getReturnedUri(), equalTo(uri));
-        assertThat("A valid uri with unknown media type is not playable",
-                delegate.isPlayable(), equalTo(false));
-    }
-
-    private TestDelegate resolveUri(
-            final Uri uri,
-            final String cookies,
-            int responseCode,
-            URL returnedUrl,
-            Map<String, List<String>> returnedHeaders,
-            boolean throwIOException) {
-        mReturnedResponseCode = responseCode;
-        mReturnedUrl = returnedUrl;
-        mReturnedHeaders = returnedHeaders == null ? new HashMap<String, List<String>>()
-                : returnedHeaders;
-        mRequestProperties = new HashMap<String, String>();
-        mThrowIOException = throwIOException;
-
-        TestDelegate delegate = new TestDelegate(uri, cookies);
-        MediaUrlResolver resolver = new MediaUrlResolver(delegate, "User agent",
-                new DummyUrlStreamHandler()) {
-            // The RecordHistogram members are static, and call native, so we must prevent any calls
-            // to them by overriding the wrapper.
-            @Override
-            void recordResultHistogram(int result) {
-            }
-        };
-        resolver.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
-        ShadowApplication.runBackgroundTasks();
-
-        return delegate;
-    }
-
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/remote/RemoteVideoInfoTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/remote/RemoteVideoInfoTest.java
deleted file mode 100644
index 00e38eed..0000000
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/remote/RemoteVideoInfoTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.remote;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertThat;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
-
-import org.chromium.base.test.BaseRobolectricTestRunner;
-
-/**
- * Unit tests (run on host) for {@link RemoteVideoInfo}.
- */
-@RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
-public class RemoteVideoInfoTest {
-    /**
-     * Test method for {@link RemoteVideoInfo#hashCode()}.
-     */
-    @Test
-    public void testHashCode() {
-        RemoteVideoInfo i1 = new RemoteVideoInfo("Test", 100, RemoteVideoInfo.PlayerState.STOPPED,
-                50, null);
-        int h1 = i1.hashCode();
-
-        RemoteVideoInfo i2 = new RemoteVideoInfo(i1);
-        assertThat("Copying RemoteVideoInfo preserves the hash code", i2.hashCode(), equalTo(h1));
-
-        i2 = new RemoteVideoInfo("Test", 100, RemoteVideoInfo.PlayerState.STOPPED,
-                50, null);
-        assertThat("Identical RemoteVideoInfos the same hash code", i2.hashCode(), equalTo(h1));
-
-        int ho = new Object().hashCode();
-        assertThat("Objects of different types are unequal to RemoteVideoInfos", i1.hashCode(),
-                not(equalTo(ho)));
-
-        i2 = new RemoteVideoInfo("Test1", 100, RemoteVideoInfo.PlayerState.STOPPED, 50, null);
-        assertThat("Changing title changes hash code", i2.hashCode(), not(equalTo(h1)));
-
-        i2 = new RemoteVideoInfo("Test", 200, RemoteVideoInfo.PlayerState.STOPPED, 50, null);
-        assertThat("Changing duration changes hash code", i2.hashCode(), not(equalTo(h1)));
-
-        i2 = new RemoteVideoInfo("Test", 100, RemoteVideoInfo.PlayerState.INVALIDATED, 50, null);
-        assertThat("Changing state changes hash code", i2.hashCode(), not(equalTo(h1)));
-
-        i2 = new RemoteVideoInfo("Test", 100, RemoteVideoInfo.PlayerState.STOPPED, 70, null);
-        assertThat("Changing current time changes hash code", i2.hashCode(), not(equalTo(h1)));
-
-        i2 = new RemoteVideoInfo("Test", 100, RemoteVideoInfo.PlayerState.STOPPED, 50, "Error");
-        assertThat("Changing error changes hash code", i2.hashCode(), not(equalTo(h1)));
-    }
-
-    /**
-     * Test method for
-     * {@link RemoteVideoInfo#equals(java.lang.Object)}.
-     */
-    @Test
-    public void testEqualsObject() {
-        RemoteVideoInfo i1 = new RemoteVideoInfo("Test", 100, RemoteVideoInfo.PlayerState.STOPPED,
-                50, null);
-        assertThat("A RemoteVideoInfo is equal to itself", i1, equalTo(i1));
-
-        RemoteVideoInfo i2 = new RemoteVideoInfo(i1);
-        assertThat("A RemoteVideoInfo is equal to its copy", i2, equalTo(i1));
-
-        i2 = new RemoteVideoInfo("Test", 100, RemoteVideoInfo.PlayerState.STOPPED,
-                50, null);
-        assertThat("identical RemoteVideoInfos are equal", i2, equalTo(i1));
-
-        Object o = new Object();
-        assertThat("Objects of different types are unequal to RemoteVideoInfos", i1,
-                not(equalTo(o)));
-
-        i2 = new RemoteVideoInfo("Test1", 100, RemoteVideoInfo.PlayerState.STOPPED, 50, null);
-        assertThat("Changing title makes RemoteVideoInfos unequal", i2, not(equalTo(i1)));
-
-        i2 = new RemoteVideoInfo("Test", 200, RemoteVideoInfo.PlayerState.STOPPED, 50, null);
-        assertThat("Changing duration makes RemoteVideoInfos unequal", i2, not(equalTo(i1)));
-
-        i2 = new RemoteVideoInfo("Test", 100, RemoteVideoInfo.PlayerState.INVALIDATED, 50, null);
-        assertThat("Changing state makes RemoteVideoInfos unequal", i2, not(equalTo(i1)));
-
-        i2 = new RemoteVideoInfo("Test", 100, RemoteVideoInfo.PlayerState.STOPPED, 70, null);
-        assertThat("Changing current time makes RemoteVideoInfos unequal", i2, not(equalTo(i1)));
-
-        i2 = new RemoteVideoInfo("Test", 100, RemoteVideoInfo.PlayerState.STOPPED, 50, "Error");
-        assertThat("Changing error makes RemoteVideoInfos unequal", i2, not(equalTo(i1)));
-    }
-
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/router/caf/CafMediaRouteProviderTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/router/caf/CafMediaRouteProviderTest.java
index 532dd83..d7bfd8a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/router/caf/CafMediaRouteProviderTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/router/caf/CafMediaRouteProviderTest.java
@@ -46,7 +46,6 @@
 import org.chromium.chrome.browser.media.router.MediaRoute;
 import org.chromium.chrome.browser.media.router.MediaRouteManager;
 import org.chromium.chrome.browser.media.router.MediaSink;
-import org.chromium.chrome.browser.media.router.cast.CastMediaSource;
 
 /**
  * Robolectric tests for CafMediaRouteProvider.
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/router/caf/ShadowCastMediaSource.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/router/caf/ShadowCastMediaSource.java
index 4907a979..6d9f698a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/router/caf/ShadowCastMediaSource.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/router/caf/ShadowCastMediaSource.java
@@ -7,8 +7,6 @@
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 
-import org.chromium.chrome.browser.media.router.cast.CastMediaSource;
-
 /** Shadow implementation for {@link MediaRouter}. */
 @Implements(CastMediaSource.class)
 public class ShadowCastMediaSource {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/router/cast/CastMediaRouteProviderTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/router/cast/CastMediaRouteProviderTest.java
deleted file mode 100644
index 02631a79..0000000
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/router/cast/CastMediaRouteProviderTest.java
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.router.cast;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.support.v7.media.MediaRouter;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
-
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.Feature;
-import org.chromium.chrome.browser.media.router.ChromeMediaRouter;
-import org.chromium.chrome.browser.media.router.MediaRoute;
-import org.chromium.chrome.browser.media.router.MediaRouteManager;
-import org.chromium.chrome.browser.media.router.MediaSink;
-
-import java.util.ArrayList;
-
-/**
- * Robolectric tests for {@link CastMediaRouteProvider}.
- */
-@RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
-public class CastMediaRouteProviderTest {
-    private static final String SUPPORTED_SOURCE = "cast:DEADBEEF";
-
-    private static final String SUPPORTED_AUTOJOIN_SOURCE = "cast:DEADBEEF"
-            + "?clientId=12345&autoJoinPolicy=" + CastMediaSource.AUTOJOIN_TAB_AND_ORIGIN_SCOPED;
-
-    // TODO(crbug.com/672704): Android does not currently support 1-UA mode.
-    private static final String UNSUPPORTED_SOURCE = "https://example.com";
-
-    private MediaRouteManager mMockManager;
-    private CastMediaRouteProvider mProvider;
-
-    protected void setUpMediaRouter(MediaRouter router) {
-        ChromeMediaRouter.setAndroidMediaRouterForTest(router);
-        mMockManager = mock(MediaRouteManager.class);
-        mProvider = CastMediaRouteProvider.create(mMockManager);
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testStartObservingMediaSinksNoMediaRouter() {
-        setUpMediaRouter(null);
-
-        mProvider.startObservingMediaSinks(SUPPORTED_SOURCE);
-
-        verify(mMockManager, timeout(100))
-                .onSinksReceived(
-                        eq(SUPPORTED_SOURCE), same(mProvider), eq(new ArrayList<MediaSink>()));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testStartObservingMediaSinksUnsupportedSource() {
-        setUpMediaRouter(mock(MediaRouter.class));
-
-        mProvider.startObservingMediaSinks(UNSUPPORTED_SOURCE);
-
-        verify(mMockManager, timeout(100))
-                .onSinksReceived(
-                        eq(UNSUPPORTED_SOURCE), same(mProvider), eq(new ArrayList<MediaSink>()));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnSessionClosedNoClientRecord() {
-        setUpMediaRouter(mock(MediaRouter.class));
-
-        CastSession mockSession = mock(CastSession.class);
-        mProvider.onSessionStarted(mockSession);
-
-        MediaRoute route = new MediaRoute("sink", SUPPORTED_SOURCE, "");
-        mProvider.addRoute(route, "", -1);
-        mProvider.onSessionEnded();
-
-        verify(mMockManager).onRouteTerminated(route.id);
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCloseRouteWithNoSession() {
-        setUpMediaRouter(mock(MediaRouter.class));
-
-        MediaRoute route = new MediaRoute("sink", SUPPORTED_SOURCE, "");
-        mProvider.addRoute(route, "", -1);
-        mProvider.closeRoute(route.id);
-
-        verify(mMockManager).onRouteTerminated(route.id);
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testAutoJoinWithSameOrigin() {
-        setUpMediaRouter(mock(MediaRouter.class));
-
-        CastSession mockSession = mock(CastSession.class);
-        when(mockSession.getSinkId()).thenReturn("sinkId");
-        when(mockSession.getSourceId()).thenReturn(SUPPORTED_AUTOJOIN_SOURCE);
-
-        MediaRoute route = new MediaRoute("sinkId", SUPPORTED_AUTOJOIN_SOURCE, "presentationId");
-        mProvider.addRoute(route, "https://example.com", 1);
-        mProvider.onSessionStarted(mockSession);
-        mProvider.joinRoute(SUPPORTED_AUTOJOIN_SOURCE, "auto-join", "https://example.com", 1, -1);
-
-        verify(mMockManager)
-                .onRouteCreated("route:auto-join/sinkId/" + SUPPORTED_AUTOJOIN_SOURCE, "sinkId", -1,
-                        mProvider, false);
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testNoAutoJoinWithOneUniqueOrigin() {
-        setUpMediaRouter(mock(MediaRouter.class));
-
-        CastSession mockSession = mock(CastSession.class);
-        when(mockSession.getSinkId()).thenReturn("sinkId");
-        when(mockSession.getSourceId()).thenReturn(SUPPORTED_AUTOJOIN_SOURCE);
-
-        MediaRoute route = new MediaRoute("sinkId", SUPPORTED_AUTOJOIN_SOURCE, "presentationId");
-        mProvider.addRoute(route, "https://example.com", 1);
-        mProvider.onSessionStarted(mockSession);
-        mProvider.joinRoute(SUPPORTED_AUTOJOIN_SOURCE, "auto-join", "", 1, -1);
-
-        verify(mMockManager).onRouteRequestError("No matching route", -1);
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testNoAutoJoinWithTwoUniqueOrigins() {
-        setUpMediaRouter(mock(MediaRouter.class));
-
-        CastSession mockSession = mock(CastSession.class);
-        when(mockSession.getSinkId()).thenReturn("sinkId");
-        when(mockSession.getSourceId()).thenReturn(SUPPORTED_AUTOJOIN_SOURCE);
-
-        MediaRoute route = new MediaRoute("sinkId", SUPPORTED_AUTOJOIN_SOURCE, "presentationId");
-        mProvider.addRoute(route, "", 1);
-        mProvider.onSessionStarted(mockSession);
-        mProvider.joinRoute(SUPPORTED_AUTOJOIN_SOURCE, "auto-join", "", 1, -1);
-
-        verify(mMockManager).onRouteRequestError("No matching route", -1);
-    }
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/router/cast/CastMediaSourceTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/router/cast/CastMediaSourceTest.java
deleted file mode 100644
index 4d36d68..0000000
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/router/cast/CastMediaSourceTest.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.router.cast;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
-
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.Feature;
-
-/**
- * Robolectric tests for {@link CastMediaSource} class.
- */
-@RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
-public class CastMediaSourceTest {
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCorrectSourceId() {
-        final String sourceId = "https://example.com/path?query"
-                + "#__castAppId__=ABCD1234(video_out,audio_out)"
-                + "/__castClientId__=1234567890"
-                + "/__castAutoJoinPolicy__=tab_and_origin_scoped";
-
-        CastMediaSource source = CastMediaSource.from(sourceId);
-        assertNotNull(source);
-        assertEquals("ABCD1234", source.getApplicationId());
-
-        assertNotNull(source.getCapabilities());
-        assertEquals(2, source.getCapabilities().length);
-        assertEquals("video_out", source.getCapabilities()[0]);
-        assertEquals("audio_out", source.getCapabilities()[1]);
-
-        assertEquals("1234567890", source.getClientId());
-        assertEquals("tab_and_origin_scoped", source.getAutoJoinPolicy());
-
-        assertEquals(sourceId, source.getSourceId());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testNoFragment() {
-        assertNull(CastMediaSource.from("https://example.com/path?query"));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testEmptyFragment() {
-        assertNull(CastMediaSource.from("https://example.com/path?query#"));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testNoAppId() {
-        assertNull(CastMediaSource.from("https://example.com/path?query#fragment"));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testNoValidAppId() {
-        // Invalid app id needs to indicate no availability so {@link CastMediaSource} needs to be
-        // created.
-        CastMediaSource empty =
-                CastMediaSource.from("https://example.com/path?query#__castAppId__=");
-        assertNotNull(empty);
-        assertEquals("", empty.getApplicationId());
-
-        CastMediaSource invalid =
-                CastMediaSource.from("https://example.com/path?query#__castAppId__=INVALID-APP-ID");
-        assertNotNull(invalid);
-        assertEquals("INVALID-APP-ID", invalid.getApplicationId());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testNoCapabilitiesSuffix() {
-        assertNull(CastMediaSource.from("https://example.com/path?query#__castAppId__=A("));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCapabilitiesEmpty() {
-        assertNull(CastMediaSource.from("https://example.com/path?query#__castAppId__=A()"));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testInvalidCapability() {
-        assertNull(CastMediaSource.from("https://example.com/path?query#__castAppId__=A(a)"));
-        assertNull(
-                CastMediaSource.from("https://example.com/path?query#__castAppId__=A(video_in,b)"));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testInvalidAutoJoinPolicy() {
-        assertNull(CastMediaSource.from("https://example.com/path?query#__castAppId__=A"
-                + "/__castAutoJoinPolicy__=invalidPolicy"));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOptionalParameters() {
-        CastMediaSource source =
-                CastMediaSource.from("https://example.com/path?query#__castAppId__=A");
-        assertNotNull(source);
-        assertEquals("A", source.getApplicationId());
-
-        assertNull(source.getCapabilities());
-        assertNull(source.getClientId());
-        assertEquals("tab_and_origin_scoped", source.getAutoJoinPolicy());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testBasicCastPresentationUrl() {
-        CastMediaSource source = CastMediaSource.from("cast:ABCD1234");
-        assertNotNull(source);
-        assertEquals("ABCD1234", source.getApplicationId());
-        assertNull(source.getCapabilities());
-        assertNull(source.getClientId());
-        assertEquals("tab_and_origin_scoped", source.getAutoJoinPolicy());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCastPresentationUrlWithParameters() {
-        CastMediaSource source = CastMediaSource.from("cast:ABCD1234?clientId=1234"
-                + "&capabilities=video_out,audio_out"
-                + "&autoJoinPolicy=tab_and_origin_scoped");
-        assertNotNull(source);
-        assertEquals("ABCD1234", source.getApplicationId());
-        assertNotNull(source.getCapabilities());
-        assertEquals(2, source.getCapabilities().length);
-        assertEquals("video_out", source.getCapabilities()[0]);
-        assertEquals("audio_out", source.getCapabilities()[1]);
-        assertEquals("1234", source.getClientId());
-        assertEquals("tab_and_origin_scoped", source.getAutoJoinPolicy());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCastPresentationUrlInvalidCapability() {
-        assertNull(CastMediaSource.from("cast:ABCD1234?clientId=1234"
-                + "&capabilities=invalidCapability"));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCastPresentationUrlInvalidAutoJoinPolicy() {
-        assertNull(CastMediaSource.from("cast:ABCD1234?clientId=1234"
-                + "&autoJoinPolicy=invalidPolicy"));
-    }
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/router/cast/CastMessageHandlerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/router/cast/CastMessageHandlerTest.java
deleted file mode 100644
index 81467be..0000000
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/router/cast/CastMessageHandlerTest.java
+++ /dev/null
@@ -1,909 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.router.cast;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowLog;
-
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.Feature;
-import org.chromium.chrome.browser.media.router.CastSessionUtil;
-import org.chromium.chrome.browser.media.router.ClientRecord;
-import org.chromium.chrome.browser.media.router.JSONTestUtils.JSONObjectLike;
-import org.chromium.chrome.browser.media.router.JSONTestUtils.JSONStringLike;
-import org.chromium.chrome.browser.media.router.cast.CastMessageHandler.RequestRecord;
-
-import java.util.ArrayDeque;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Robolectric tests for CastSession.
- */
-@RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
-public class CastMessageHandlerTest {
-    private static final String TAG = "MediaRouter";
-
-    private static final String SESSION_ID = "SESSION_ID";
-    private static final String INVALID_SESSION_ID = "INVALID_SESSION_ID";
-    private static final String CLIENT_ID1 = "00000000000000001";
-    private static final String CLIENT_ID2 = "00000000000000002";
-    private static final String INVALID_CLIENT_ID = "xxxxxxxxxxxxxxxxx";
-    private static final String NAMESPACE1 = "namespace1";
-    private static final String NAMESPACE2 = "namespace2";
-    private static final String MEDIA_NAMESPACE = CastSessionUtil.MEDIA_NAMESPACE;
-    private static final int SEQUENCE_NUMBER1 = 1;
-    private static final int SEQUENCE_NUMBER2 = 2;
-    private static final int REQUEST_ID1 = 1;
-    private static final int REQUEST_ID2 = 2;
-    private static final int INVALID_SEQUENCE_NUMBER =
-            CastMessageHandler.INVALID_SEQUENCE_NUMBER;
-    private CastMediaRouteProvider mRouteProvider;
-    private CastSession mSession;
-    private CastMessageHandler mMessageHandler;
-    private int mNumStopApplicationCalled;
-
-    private interface CheckedRunnable { void run() throws Exception; }
-
-    @Before
-    public void setUp() {
-        ShadowLog.stream = System.out;
-        mRouteProvider = mock(CastMediaRouteProvider.class);
-        mSession = mock(CastSession.class);
-        doReturn(SESSION_ID).when(mSession).getSessionId();
-        doReturn(true).when(mSession).sendStringCastMessage(
-                anyString(), anyString(), anyString(), anyInt());
-        mMessageHandler = spy(new CastMessageHandler(mRouteProvider));
-        mMessageHandler.onSessionCreated(mSession);
-        final Set<String> clientIds = new HashSet<String>();
-        clientIds.add(CLIENT_ID1);
-        clientIds.add(CLIENT_ID2);
-        doAnswer(new Answer() {
-            @Override
-            public Object answer(InvocationOnMock invocation) {
-                return clientIds;
-            }
-        })
-                .when(mRouteProvider)
-                .getClients();
-        doNothing().when(mRouteProvider).onMessage(anyString(), anyString());
-    }
-
-    void setUpForAppMessageTest() throws JSONException {
-        Set<String> namespaces = new HashSet<String>();
-        namespaces.add(NAMESPACE1);
-        doReturn(namespaces).when(mSession).getNamespaces();
-        doReturn(true).when(mMessageHandler)
-                .sendJsonCastMessage(any(JSONObject.class), anyString(), anyString(), anyInt());
-    }
-
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnSessionCreated() {
-        Map<String, ClientRecord> clientRecords = new HashMap<String, ClientRecord>();
-        clientRecords.put(CLIENT_ID1, new ClientRecord(
-                "DONTCARE", CLIENT_ID1, "DONTCARE", "DONTCARE", "DONTCARE", 0));
-        clientRecords.put(CLIENT_ID2, new ClientRecord(
-                "DONTCARE", CLIENT_ID2, "DONTCARE", "DONTCARE", "DONTCARE", 0));
-        clientRecords.get(CLIENT_ID1).isConnected = true;
-        doReturn(clientRecords).when(mRouteProvider).getClientRecords();
-        // The call in setUp() actually only sets the session. This call will notify the clients
-        // that have sent "client_connect"
-        mMessageHandler.onSessionCreated(mSession);
-        verify(mSession).onClientConnected(CLIENT_ID1);
-        verify(mSession, never()).onClientConnected(CLIENT_ID2);
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testHandleSessionMessageOfV2MessageType() throws JSONException {
-        doReturn(true).when(mMessageHandler).handleCastV2Message(any(JSONObject.class));
-
-        JSONObject message = new JSONObject();
-        message.put("type", "v2_message");
-        assertTrue(mMessageHandler.handleSessionMessage(message));
-        verify(mMessageHandler).handleCastV2Message(argThat(new JSONObjectLike(message)));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testHandleSessionMessageOfAppMessageType() throws JSONException {
-        doReturn(true).when(mMessageHandler).handleAppMessage(any(JSONObject.class));
-
-        JSONObject message = new JSONObject();
-        message.put("type", "app_message");
-        assertTrue(mMessageHandler.handleSessionMessage(message));
-        verify(mMessageHandler).handleAppMessage(argThat(new JSONObjectLike(message)));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testHandleSessionMessageOfUnsupportedType() throws JSONException {
-        doReturn(true).when(mMessageHandler).handleCastV2Message(any(JSONObject.class));
-
-        JSONObject message = new JSONObject();
-        message.put("type", "unsupported");
-        assertFalse(mMessageHandler.handleSessionMessage(message));
-        verify(mMessageHandler, never()).handleCastV2Message(any(JSONObject.class));
-        verify(mMessageHandler, never()).handleAppMessage(any(JSONObject.class));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCastV2MessageWithWrongTypeInnerMessage() throws JSONException {
-        JSONObject innerMessage = new JSONObject()
-                .put("type", "STOP");
-        final JSONObject message = buildCastV2Message(CLIENT_ID1, innerMessage);
-        // Replace the inner JSON message with string.
-        message.put("message", "wrong type inner message");
-        expectException(new CheckedRunnable() {
-                @Override
-                public void run() throws Exception {
-                    mMessageHandler.handleCastV2Message(message);
-                }
-            }, JSONException.class);
-        verify(mMessageHandler, never()).handleStopMessage(anyString(), anyInt());
-        verify(mSession, never()).handleVolumeMessage(any(JSONObject.class), anyString(), anyInt());
-        verify(mMessageHandler, never()).sendJsonCastMessage(
-                any(JSONObject.class), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCastV2MessageOfStopType() throws JSONException {
-        JSONObject innerMessage = new JSONObject()
-                .put("type", "STOP");
-        JSONObject message = buildCastV2Message(CLIENT_ID1, innerMessage);
-        assertTrue(mMessageHandler.handleCastV2Message(message));
-        verify(mMessageHandler).handleStopMessage(eq(CLIENT_ID1), eq(SEQUENCE_NUMBER1));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCastV2MessageofSetVolumeTypeShouldWait() throws JSONException {
-        doReturn(new CastSession.HandleVolumeMessageResult(true, true))
-                .when(mSession).handleVolumeMessage(any(JSONObject.class), anyString(), anyInt());
-        JSONObject innerMessage = new JSONObject()
-                .put("type", "SET_VOLUME")
-                .put("volume", new JSONObject()
-                     .put("level", (double) 1)
-                     .put("muted", false));
-        JSONObject message = buildCastV2Message(CLIENT_ID1, innerMessage);
-        assertTrue(mMessageHandler.handleCastV2Message(message));
-        JSONObject volumeMessage = innerMessage.getJSONObject("volume");
-        verify(mSession).handleVolumeMessage(
-                argThat(new JSONObjectLike(innerMessage.getJSONObject("volume"))),
-                eq(CLIENT_ID1), eq(SEQUENCE_NUMBER1));
-        assertEquals(mMessageHandler.getVolumeRequestsForTest().size(), 1);
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCastV2MessageofSetVolumeTypeShouldNotWait() throws JSONException {
-        doReturn(new CastSession.HandleVolumeMessageResult(true, false))
-                .when(mSession).handleVolumeMessage(any(JSONObject.class), anyString(), anyInt());
-        JSONObject innerMessage = new JSONObject()
-                .put("type", "SET_VOLUME")
-                .put("volume", new JSONObject()
-                     .put("level", (double) 1)
-                     .put("muted", false));
-        JSONObject message = buildCastV2Message(CLIENT_ID1, innerMessage);
-        assertTrue(mMessageHandler.handleCastV2Message(message));
-        JSONObject volumeMessage = innerMessage.getJSONObject("volume");
-        verify(mSession).handleVolumeMessage(
-                argThat(new JSONObjectLike(innerMessage.getJSONObject("volume"))),
-                eq(CLIENT_ID1), eq(SEQUENCE_NUMBER1));
-        assertEquals(mMessageHandler.getVolumeRequestsForTest().size(), 0);
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCastV2MessageofSetVolumeTypeWithNullVolumeMessage() throws JSONException {
-        JSONObject innerMessage = new JSONObject()
-                .put("type", "SET_VOLUME");
-        final JSONObject message = buildCastV2Message(CLIENT_ID1, innerMessage);
-        expectException(new CheckedRunnable() {
-                @Override
-                public void run() throws Exception {
-                    mMessageHandler.handleCastV2Message(message);
-                }
-            }, JSONException.class);
-        verify(mSession, never()).handleVolumeMessage(any(JSONObject.class), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCastV2MessageofSetVolumeTypeWithWrongTypeVolumeMessage() throws JSONException {
-        JSONObject innerMessage = new JSONObject()
-                .put("type", "SET_VOLUME")
-                .put("volume", "wrong type volume message");
-        final JSONObject message = buildCastV2Message(CLIENT_ID1, innerMessage);
-        expectException(new CheckedRunnable() {
-                @Override
-                public void run() throws Exception {
-                    mMessageHandler.handleCastV2Message(message);
-                }
-            }, JSONException.class);
-        verify(mSession, never()).handleVolumeMessage(any(JSONObject.class), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCastV2MessageOfMediaMessageType() throws JSONException {
-        doReturn(true).when(mMessageHandler).sendJsonCastMessage(
-                any(JSONObject.class), anyString(), anyString(), anyInt());
-        for (String messageType : CastMessageHandler.getMediaMessageTypesForTest()) {
-            // TODO(zqzhang): SET_VOLUME and STOP should not reach here?
-            if ("MEDIA_SET_VOLUME".equals(messageType) || "STOP_MEDIA".equals(messageType)) {
-                continue;
-            }
-            JSONObject innerMessage = new JSONObject().put("type", messageType);
-            JSONObject message = buildCastV2Message(CLIENT_ID1, innerMessage);
-            assertTrue(mMessageHandler.handleCastV2Message(message));
-
-            JSONObject expected = new JSONObject();
-            if (CastMessageHandler.getMediaOverloadedMessageTypesForTest()
-                    .containsKey(messageType)) {
-                expected.put("type", CastMessageHandler.getMediaOverloadedMessageTypesForTest()
-                        .get(messageType));
-            } else {
-                expected.put("type", messageType);
-            }
-            verify(mMessageHandler)
-                    .sendJsonCastMessage(argThat(new JSONObjectLike(expected)),
-                            eq(CastSessionUtil.MEDIA_NAMESPACE), eq(CLIENT_ID1),
-                            eq(SEQUENCE_NUMBER1));
-        }
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testCastV2MessageWithNullSequenceNumber() throws JSONException {
-        JSONObject innerMessage = new JSONObject()
-                .put("type", "STOP");
-        JSONObject message = buildCastV2Message(CLIENT_ID1, innerMessage);
-        message.remove("sequenceNumber");
-        assertTrue(mMessageHandler.handleCastV2Message(message));
-        verify(mMessageHandler).handleStopMessage(eq(CLIENT_ID1), eq(INVALID_SEQUENCE_NUMBER));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testHandleStopMessage() throws JSONException {
-        assertEquals(0, mMessageHandler.getStopRequestsForTest().size());
-        mMessageHandler.handleStopMessage(CLIENT_ID1, SEQUENCE_NUMBER1);
-        assertEquals(1, mMessageHandler.getStopRequestsForTest().get(CLIENT_ID1).size(), 1);
-        verify(mSession).stopApplication();
-        mMessageHandler.handleStopMessage(CLIENT_ID1, SEQUENCE_NUMBER2);
-        assertEquals(2, mMessageHandler.getStopRequestsForTest().get(CLIENT_ID1).size(), 2);
-        verify(mSession, times(2)).stopApplication();
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testAppMessageWithExistingNamespace() throws JSONException {
-        setUpForAppMessageTest();
-
-        JSONObject actualMessage = buildActualAppMessage();
-        JSONObject message = buildAppMessage(CLIENT_ID1, NAMESPACE1, actualMessage);
-        assertTrue(mMessageHandler.handleAppMessage(message));
-        verify(mMessageHandler).sendJsonCastMessage(
-                argThat(new JSONObjectLike(actualMessage)), eq(NAMESPACE1),
-                eq(CLIENT_ID1), eq(SEQUENCE_NUMBER1));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testAppMessageWithNonexistingNamespace() throws JSONException {
-        setUpForAppMessageTest();
-
-        JSONObject actualMessage = buildActualAppMessage();
-        JSONObject message = buildAppMessage(CLIENT_ID1, NAMESPACE2, actualMessage);
-        assertFalse(mMessageHandler.handleAppMessage(message));
-        verify(mMessageHandler, never()).sendJsonCastMessage(
-                any(JSONObject.class), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testAppMessageWithoutSequenceNumber() throws JSONException {
-        setUpForAppMessageTest();
-
-        JSONObject actualMessage = buildActualAppMessage();
-        JSONObject message = buildAppMessage(CLIENT_ID1, NAMESPACE1, actualMessage);
-        message.remove("sequenceNumber");
-        assertTrue(mMessageHandler.handleAppMessage(message));
-        verify(mMessageHandler).sendJsonCastMessage(
-                argThat(new JSONObjectLike(actualMessage)), eq(NAMESPACE1),
-                eq(CLIENT_ID1), eq(INVALID_SEQUENCE_NUMBER));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testAppMessageWithNullSessionId() throws JSONException {
-        setUpForAppMessageTest();
-
-        JSONObject actualMessage = buildActualAppMessage();
-        final JSONObject message = buildAppMessage(CLIENT_ID1, NAMESPACE1, actualMessage);
-        message.getJSONObject("message").remove("sessionId");
-        expectException(new CheckedRunnable() {
-                @Override
-                public void run() throws Exception {
-                    mMessageHandler.handleAppMessage(message);
-                }
-            }, JSONException.class);
-        verify(mMessageHandler, never()).sendJsonCastMessage(
-                any(JSONObject.class), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testAppMessageWithWrongSessionId() throws JSONException {
-        setUpForAppMessageTest();
-
-        JSONObject actualMessage = buildActualAppMessage();
-        JSONObject message = buildAppMessage(CLIENT_ID1, NAMESPACE1, actualMessage);
-        message.getJSONObject("message").put("sessionId", INVALID_SESSION_ID);
-        assertFalse(mMessageHandler.handleAppMessage(message));
-        verify(mMessageHandler, never()).sendJsonCastMessage(
-                any(JSONObject.class), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testAppMessageWithNullActualMessage() throws JSONException {
-        setUpForAppMessageTest();
-
-        JSONObject actualMessage = buildActualAppMessage();
-        final JSONObject message = buildAppMessage(CLIENT_ID1, NAMESPACE1, actualMessage);
-        message.getJSONObject("message").remove("message");
-        expectException(new CheckedRunnable() {
-                @Override
-                public void run() throws Exception {
-                    mMessageHandler.handleAppMessage(message);
-                }
-            }, JSONException.class);
-        verify(mMessageHandler, never()).sendJsonCastMessage(
-                any(JSONObject.class), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testAppMessageWithStringMessage() throws JSONException {
-        setUpForAppMessageTest();
-
-        JSONObject actualMessage = buildActualAppMessage();
-        JSONObject message = buildAppMessage(CLIENT_ID1, NAMESPACE1, actualMessage);
-        message.getJSONObject("message").put("message", "string message");
-        assertTrue(mMessageHandler.handleAppMessage(message));
-        verify(mMessageHandler, never()).sendJsonCastMessage(
-                any(JSONObject.class), anyString(), anyString(), anyInt());
-        verify(mSession).sendStringCastMessage(
-                eq("string message"), eq(NAMESPACE1), eq(CLIENT_ID1), eq(SEQUENCE_NUMBER1));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testAppMessageWithNullAppMessage() throws JSONException {
-        setUpForAppMessageTest();
-
-        JSONObject actualMessage = buildActualAppMessage();
-        final JSONObject message = buildAppMessage(CLIENT_ID1, NAMESPACE1, actualMessage);
-        message.remove("message");
-        expectException(new CheckedRunnable() {
-                @Override
-                public void run() throws Exception {
-                    mMessageHandler.handleAppMessage(message);
-                }
-            }, JSONException.class);
-        verify(mMessageHandler, never()).sendJsonCastMessage(
-                any(JSONObject.class), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testAppMessageWithEmptyAppMessage() throws JSONException {
-        setUpForAppMessageTest();
-
-        JSONObject actualMessage = buildActualAppMessage();
-        final JSONObject message = buildAppMessage(CLIENT_ID1, NAMESPACE1, actualMessage);
-        message.put("message", new JSONObject());
-        expectException(new CheckedRunnable() {
-                @Override
-                public void run() throws Exception {
-                    mMessageHandler.handleAppMessage(message);
-                }
-            }, JSONException.class);
-        verify(mMessageHandler, never()).sendJsonCastMessage(
-                any(JSONObject.class), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testAppMessageWithWrongTypeAppMessage() throws JSONException {
-        setUpForAppMessageTest();
-
-        JSONObject actualMessage = buildActualAppMessage();
-        final JSONObject message = buildAppMessage(CLIENT_ID1, NAMESPACE1, actualMessage);
-        message.put("message", "wrong type app message");
-        expectException(new CheckedRunnable() {
-                @Override
-                public void run() throws Exception {
-                    mMessageHandler.handleAppMessage(message);
-                }
-            }, JSONException.class);
-        verify(mMessageHandler, never()).sendJsonCastMessage(
-                any(JSONObject.class), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testAppMessageWithNullClient() throws JSONException {
-        setUpForAppMessageTest();
-
-        JSONObject actualMessage = buildActualAppMessage();
-        final JSONObject message = buildAppMessage(CLIENT_ID1, NAMESPACE1, actualMessage);
-        message.remove("clientId");
-        expectException(new CheckedRunnable() {
-                @Override
-                public void run() throws Exception {
-                    mMessageHandler.handleAppMessage(message);
-                }
-            }, JSONException.class);
-        verify(mMessageHandler, never()).sendJsonCastMessage(
-                any(JSONObject.class), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testAppMessageWithNonexistingClient() throws JSONException {
-        setUpForAppMessageTest();
-
-        JSONObject actualMessage = buildActualAppMessage();
-        JSONObject message = buildAppMessage(CLIENT_ID1, NAMESPACE1, actualMessage);
-        message.put("clientId", INVALID_CLIENT_ID);
-        assertFalse(mMessageHandler.handleAppMessage(message));
-        verify(mMessageHandler, never()).sendJsonCastMessage(
-                any(JSONObject.class), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testSendJsonCastMessage() throws JSONException {
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 0);
-        JSONObject message = buildJsonCastMessage("message");
-        assertTrue(mMessageHandler.sendJsonCastMessage(
-                message, NAMESPACE1, CLIENT_ID1, SEQUENCE_NUMBER1));
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 1);
-        verify(mSession).sendStringCastMessage(
-                argThat(new JSONStringLike(message)), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testSendJsonCastMessageWhenApiClientInvalid() throws JSONException {
-        doReturn(true).when(mSession).isApiClientInvalid();
-
-        JSONObject message = buildJsonCastMessage("message");
-        assertFalse(mMessageHandler.sendJsonCastMessage(
-                message, NAMESPACE1, CLIENT_ID1, SEQUENCE_NUMBER1));
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 0);
-        verify(mSession, never()).sendStringCastMessage(
-                anyString(), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testSendJsonCastMessageWithInvalidSequenceNumber() throws JSONException {
-        JSONObject message = buildJsonCastMessage("message");
-        assertTrue(mMessageHandler.sendJsonCastMessage(
-                message, NAMESPACE1, CLIENT_ID1, INVALID_SEQUENCE_NUMBER));
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 0);
-        verify(mSession).sendStringCastMessage(
-                argThat(new JSONStringLike(message)), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testSendJsonCastMessageWithNullRequestId() throws JSONException {
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 0);
-        JSONObject message = buildJsonCastMessage("message");
-        message.remove("requestId");
-        assertTrue(mMessageHandler.sendJsonCastMessage(
-                message, NAMESPACE1, CLIENT_ID1, SEQUENCE_NUMBER1));
-        assertTrue(message.has("requestId"));
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 1);
-        verify(mSession).sendStringCastMessage(
-                argThat(new JSONStringLike(message)), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnMessageReceivedWithExistingRequestId() throws JSONException {
-        doNothing().when(mMessageHandler).onAppMessage(
-                anyString(), anyString(), any(RequestRecord.class));
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 0);
-        RequestRecord request = new RequestRecord(CLIENT_ID1, SEQUENCE_NUMBER1);
-        mMessageHandler.getRequestsForTest().append(
-                REQUEST_ID1, request);
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 1);
-        JSONObject message = new JSONObject();
-        message.put("requestId", REQUEST_ID1);
-        mMessageHandler.onMessageReceived(NAMESPACE1, message.toString());
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 0);
-        verify(mMessageHandler).onAppMessage(eq(message.toString()), eq(NAMESPACE1), eq(request));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnMessageReceivedWithNonexistingRequestId() throws JSONException {
-        doNothing().when(mMessageHandler).onAppMessage(
-                anyString(), anyString(), any(RequestRecord.class));
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 0);
-        RequestRecord request = new RequestRecord(CLIENT_ID1, SEQUENCE_NUMBER1);
-        mMessageHandler.getRequestsForTest().append(
-                REQUEST_ID1, request);
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 1);
-        JSONObject message = new JSONObject();
-        message.put("requestId", REQUEST_ID2);
-        mMessageHandler.onMessageReceived(NAMESPACE1, message.toString());
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 1);
-        verify(mMessageHandler).onAppMessage(
-                eq(message.toString()), eq(NAMESPACE1), (RequestRecord) isNull());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnMessageReceivedWithoutRequestId() throws JSONException {
-        doNothing().when(mMessageHandler).onAppMessage(
-                anyString(), anyString(), any(RequestRecord.class));
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 0);
-        RequestRecord request = new RequestRecord(CLIENT_ID1, SEQUENCE_NUMBER1);
-        mMessageHandler.getRequestsForTest().append(
-                REQUEST_ID1, request);
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 1);
-        JSONObject message = new JSONObject();
-        mMessageHandler.onMessageReceived(NAMESPACE1, message.toString());
-        assertEquals(mMessageHandler.getRequestsForTest().size(), 1);
-        verify(mMessageHandler).onAppMessage(
-                eq(message.toString()), eq(NAMESPACE1), (RequestRecord) isNull());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnMessageReceivedOfMediaNamespace() throws JSONException {
-        doNothing().when(mMessageHandler).onMediaMessage(anyString(), any(RequestRecord.class));
-        mMessageHandler.onMessageReceived(MEDIA_NAMESPACE, "anymessage");
-        verify(mMessageHandler).onMediaMessage(eq("anymessage"), (RequestRecord) isNull());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnMediaMessageOfMediaStatusTypeWithRequestRecord() {
-        doNothing().when(mMessageHandler).sendClientMessageTo(
-                anyString(), anyString(), anyString(), anyInt());
-        doReturn(true).when(mMessageHandler).isMediaStatusMessage(anyString());
-        RequestRecord request = new RequestRecord(CLIENT_ID1, SEQUENCE_NUMBER1);
-        mMessageHandler.onMediaMessage("anymessage", request);
-        verify(mMessageHandler, never()).sendClientMessageTo(
-                eq(CLIENT_ID1), eq("v2_message"), eq("anymessage"), eq(INVALID_SEQUENCE_NUMBER));
-        verify(mMessageHandler).sendClientMessageTo(
-                eq(CLIENT_ID2), eq("v2_message"), eq("anymessage"), eq(INVALID_SEQUENCE_NUMBER));
-        verify(mMessageHandler).sendClientMessageTo(
-                eq(CLIENT_ID1), eq("v2_message"), eq("anymessage"), eq(SEQUENCE_NUMBER1));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnMediaMessageOfMediaStatusTypeWithNullRequestRecord() {
-        doNothing().when(mMessageHandler).sendClientMessageTo(
-                anyString(), anyString(), anyString(), anyInt());
-        doReturn(true).when(mMessageHandler).isMediaStatusMessage(anyString());
-        mMessageHandler.onMediaMessage("anymessage", null);
-        verify(mMessageHandler).sendClientMessageTo(
-                eq(CLIENT_ID1), eq("v2_message"), eq("anymessage"), eq(INVALID_SEQUENCE_NUMBER));
-        verify(mMessageHandler).sendClientMessageTo(
-                eq(CLIENT_ID2), eq("v2_message"), eq("anymessage"), eq(INVALID_SEQUENCE_NUMBER));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnMediaMessageOfNonMediaStatusTypeWithRequestRecord() {
-        doNothing().when(mMessageHandler).sendClientMessageTo(
-                anyString(), anyString(), anyString(), anyInt());
-        doReturn(false).when(mMessageHandler).isMediaStatusMessage(anyString());
-        RequestRecord request = new RequestRecord(CLIENT_ID1, SEQUENCE_NUMBER1);
-        mMessageHandler.onMediaMessage("anymessage", request);
-        verify(mMessageHandler, never()).sendClientMessageTo(
-                anyString(), anyString(), anyString(), eq(INVALID_SEQUENCE_NUMBER));
-        verify(mMessageHandler).sendClientMessageTo(
-                eq(CLIENT_ID1), eq("v2_message"), eq("anymessage"), eq(SEQUENCE_NUMBER1));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnMediaMessageOfNonMediaStatusTypeWithNullRequestRecord() {
-        doNothing().when(mMessageHandler).sendClientMessageTo(
-                anyString(), anyString(), anyString(), anyInt());
-        doReturn(false).when(mMessageHandler).isMediaStatusMessage(anyString());
-        mMessageHandler.onMediaMessage("anymessage", null);
-        verify(mMessageHandler, never()).sendClientMessageTo(
-                anyString(), anyString(), anyString(), eq(INVALID_SEQUENCE_NUMBER));
-        verify(mMessageHandler, never()).sendClientMessageTo(
-                anyString(), anyString(), anyString(), anyInt());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnAppMessageWithRequestRecord() throws JSONException {
-        doNothing().when(mMessageHandler).sendClientMessageTo(
-                anyString(), anyString(), anyString(), anyInt());
-        RequestRecord request = new RequestRecord(CLIENT_ID1, SEQUENCE_NUMBER1);
-        mMessageHandler.onAppMessage("anyMessage", NAMESPACE1, request);
-        JSONObject expected = new JSONObject();
-        expected.put("sessionId", SESSION_ID);
-        expected.put("namespaceName", NAMESPACE1);
-        expected.put("message", "anyMessage");
-        verify(mMessageHandler).sendClientMessageTo(
-                eq(CLIENT_ID1), eq("app_message"),
-                argThat(new JSONStringLike(expected)), eq(SEQUENCE_NUMBER1));
-        verify(mMessageHandler, never()).broadcastClientMessage(anyString(), anyString());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnAppMessageWithNullRequestRecord() throws JSONException {
-        doNothing().when(mMessageHandler).broadcastClientMessage(anyString(), anyString());
-        mMessageHandler.onAppMessage("anyMessage", NAMESPACE1, null);
-        JSONObject expected = new JSONObject();
-        expected.put("sessionId", SESSION_ID);
-        expected.put("namespaceName", NAMESPACE1);
-        expected.put("message", "anyMessage");
-        verify(mMessageHandler, never()).sendClientMessageTo(
-                anyString(), anyString(), anyString(), anyInt());
-        verify(mMessageHandler).broadcastClientMessage(
-                eq("app_message"), argThat(new JSONStringLike(expected)));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnApplicationStopped() {
-        doNothing().when(mMessageHandler).sendClientMessageTo(
-                anyString(), anyString(), anyString(), anyInt());
-        assertEquals(0, mMessageHandler.getStopRequestsForTest().size());
-        mMessageHandler.getStopRequestsForTest().put(CLIENT_ID1, new ArrayDeque<Integer>());
-        mMessageHandler.getStopRequestsForTest().get(CLIENT_ID1).add(SEQUENCE_NUMBER1);
-        mMessageHandler.getStopRequestsForTest().get(CLIENT_ID1).add(SEQUENCE_NUMBER2);
-        assertEquals(1, mMessageHandler.getStopRequestsForTest().size());
-        assertEquals(2, mMessageHandler.getStopRequestsForTest().get(CLIENT_ID1).size());
-
-        mMessageHandler.onApplicationStopped();
-        verify(mMessageHandler).sendClientMessageTo(
-                eq(CLIENT_ID1), eq("remove_session"), eq(SESSION_ID), eq(SEQUENCE_NUMBER1));
-        verify(mMessageHandler).sendClientMessageTo(
-                eq(CLIENT_ID1), eq("remove_session"), eq(SESSION_ID), eq(SEQUENCE_NUMBER2));
-        verify(mMessageHandler).sendClientMessageTo(
-                eq(CLIENT_ID2), eq("remove_session"), eq(SESSION_ID), eq(INVALID_SEQUENCE_NUMBER));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnVolumeChanged() {
-        doNothing().when(mMessageHandler).onVolumeChanged(anyString(), anyInt());
-        assertEquals(0, mMessageHandler.getVolumeRequestsForTest().size());
-        mMessageHandler.getVolumeRequestsForTest()
-                .add(new RequestRecord(CLIENT_ID1, SEQUENCE_NUMBER1));
-        assertEquals(1, mMessageHandler.getVolumeRequestsForTest().size());
-
-        mMessageHandler.onVolumeChanged();
-        verify(mMessageHandler).onVolumeChanged(CLIENT_ID1, SEQUENCE_NUMBER1);
-        assertEquals(0, mMessageHandler.getVolumeRequestsForTest().size());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnVolumeChangedWithEmptyVolumeRequests() {
-        mMessageHandler.onVolumeChanged();
-        verify(mMessageHandler, never()).onVolumeChanged(eq(CLIENT_ID1), eq(SEQUENCE_NUMBER1));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnVolumeChangedForClient() {
-        doNothing().when(mMessageHandler).sendClientMessageTo(
-                anyString(), anyString(), anyString(), anyInt());
-        mMessageHandler.onVolumeChanged(CLIENT_ID1, SEQUENCE_NUMBER1);
-        verify(mMessageHandler).sendClientMessageTo(
-                eq(CLIENT_ID1), eq("v2_message"), (String) isNull(), eq(SEQUENCE_NUMBER1));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testOnAppMessageSent() {
-        doNothing().when(mMessageHandler).sendClientMessageTo(
-                anyString(), anyString(), anyString(), anyInt());
-        mMessageHandler.onAppMessageSent(CLIENT_ID1, SEQUENCE_NUMBER1);
-        verify(mMessageHandler).sendClientMessageTo(
-                eq(CLIENT_ID1), eq("app_message"), (String) isNull(), eq(SEQUENCE_NUMBER1));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testSendClientMessageTo() throws JSONException {
-        String message = "discarded_message1";
-        doReturn(message).when(mMessageHandler)
-                .buildInternalMessage(anyString(), anyString(), anyString(), anyInt());
-        mMessageHandler.sendClientMessageTo(
-                CLIENT_ID1, "anytype", "discarded_message2", SEQUENCE_NUMBER1);
-
-        verify(mRouteProvider).onMessage(
-                eq(CLIENT_ID1), eq(message));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testBroadcastClientMessage() {
-        doNothing().when(mMessageHandler).sendClientMessageTo(
-                anyString(), anyString(), anyString(), anyInt());
-        mMessageHandler.broadcastClientMessage("anytype", "anymessage");
-        for (String clientId : mRouteProvider.getClients()) {
-            verify(mMessageHandler).sendClientMessageTo(
-                    eq(clientId), eq("anytype"), eq("anymessage"), eq(INVALID_SEQUENCE_NUMBER));
-        }
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testBuildInternalMessageWithNullMessage() throws JSONException {
-        String message = mMessageHandler.buildInternalMessage(
-                "anytype", null, CLIENT_ID1, SEQUENCE_NUMBER1);
-        JSONObject expected = new JSONObject();
-        expected.put("type", "anytype");
-        expected.put("sequenceNumber", SEQUENCE_NUMBER1);
-        expected.put("timeoutMillis", 0);
-        expected.put("clientId", CLIENT_ID1);
-        expected.put("message", null);
-
-        assertTrue("\nexpected: " + expected.toString() + ",\n  actual: " + message.toString(),
-                new JSONObjectLike(expected).matches(new JSONObject(message)));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testBuildInternalMessageOfRemoveSessionType() throws JSONException {
-        String message = mMessageHandler.buildInternalMessage(
-                "remove_session", SESSION_ID, CLIENT_ID1, SEQUENCE_NUMBER1);
-        JSONObject expected = new JSONObject();
-        expected.put("type", "remove_session");
-        expected.put("sequenceNumber", SEQUENCE_NUMBER1);
-        expected.put("timeoutMillis", 0);
-        expected.put("clientId", CLIENT_ID1);
-        expected.put("message", SESSION_ID);
-
-        assertTrue("\nexpected: " + expected.toString() + ",\n  actual: " + message.toString(),
-                new JSONObjectLike(expected).matches(new JSONObject(message)));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testBuildInternalMessageOfDisconnectSessionType() throws JSONException {
-        String message = mMessageHandler.buildInternalMessage(
-                "disconnect_session", SESSION_ID, CLIENT_ID1, SEQUENCE_NUMBER1);
-        JSONObject expected = new JSONObject();
-        expected.put("type", "disconnect_session");
-        expected.put("sequenceNumber", SEQUENCE_NUMBER1);
-        expected.put("timeoutMillis", 0);
-        expected.put("clientId", CLIENT_ID1);
-        expected.put("message", SESSION_ID);
-
-        assertTrue("\nexpected: " + expected.toString() + ",\n  actual: " + message.toString(),
-                new JSONObjectLike(expected).matches(new JSONObject(message)));
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testBuildInternalMessageWithInnerMessage() throws JSONException {
-        JSONObject innerMessage = buildSessionMessage(SESSION_ID);
-        String message = mMessageHandler.buildInternalMessage(
-                "anytype", innerMessage.toString(), CLIENT_ID1, SEQUENCE_NUMBER1);
-        JSONObject expected = new JSONObject();
-        expected.put("type", "anytype");
-        expected.put("sequenceNumber", SEQUENCE_NUMBER1);
-        expected.put("timeoutMillis", 0);
-        expected.put("clientId", CLIENT_ID1);
-        expected.put("message", innerMessage);
-
-        assertTrue("\nexpected: " + expected.toString() + ",\n  actual: " + message.toString(),
-                new JSONObjectLike(expected).matches(new JSONObject(message)));
-    }
-
-    JSONObject buildCastV2Message(String clientId, JSONObject innerMessage) throws JSONException {
-        JSONObject message = new JSONObject();
-        message.put("type", "v2_message");
-        message.put("message", innerMessage);
-        message.put("sequenceNumber", SEQUENCE_NUMBER1);
-        message.put("timeoutMillis", 0);
-        message.put("clientId", clientId);
-
-        return message;
-    }
-
-    JSONObject buildAppMessage(String clientId, String namespace, Object actualMessage)
-            throws JSONException {
-        JSONObject innerMessage = new JSONObject();
-        innerMessage.put("sessionId", mSession.getSessionId());
-        innerMessage.put("namespaceName", namespace);
-        innerMessage.put("message", actualMessage);
-
-        JSONObject message = new JSONObject();
-        message.put("type", "app_message");
-        message.put("message", innerMessage);
-        message.put("sequenceNumber", SEQUENCE_NUMBER1);
-        message.put("timeoutMillis", 0);
-        message.put("clientId", clientId);
-
-        return message;
-    }
-
-    JSONObject buildActualAppMessage() throws JSONException {
-        JSONObject message = new JSONObject();
-        message.put("type", "actual app message type");
-
-        return message;
-    }
-
-    JSONObject buildSessionMessage(String sessionId) throws JSONException {
-        JSONObject message = new JSONObject();
-        message.put("sessionId", sessionId);
-
-        return message;
-    }
-
-    void expectException(CheckedRunnable r, Class exceptionClass) {
-        boolean caughtException = false;
-        try {
-            r.run();
-        } catch (Exception e) {
-            if (e.getClass() == exceptionClass) caughtException = true;
-        }
-        assertTrue(caughtException);
-    }
-
-    JSONObject buildJsonCastMessage(String message) throws JSONException {
-        JSONObject jsonMessage = new JSONObject();
-        jsonMessage.put("requestId", REQUEST_ID1);
-        jsonMessage.put("message", message);
-        return jsonMessage;
-    }
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/router/cast/ChromeCastSessionManagerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/router/cast/ChromeCastSessionManagerTest.java
deleted file mode 100644
index 157c217..0000000
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/router/cast/ChromeCastSessionManagerTest.java
+++ /dev/null
@@ -1,208 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.media.router.cast;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.robolectric.annotation.Config;
-
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.Feature;
-import org.chromium.chrome.browser.media.router.cast.ChromeCastSessionManager.CastSessionLaunchRequest;
-import org.chromium.chrome.browser.media.router.cast.ChromeCastSessionManager.CastSessionManagerListener;
-
-/**
- * Robolectric tests for {@link CastMediaSource} class.
- */
-@RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
-public class ChromeCastSessionManagerTest {
-    private CastSessionManagerListener mListener;
-    private CastSessionLaunchRequest mRequest;
-    private CastSession mSession;
-    private ChromeCastSessionManager mManager;
-
-    // Mocks to be used in scenarios where multiple sessions are launched.
-    private CastSessionManagerListener mAltListener;
-    private CastSessionLaunchRequest mAltRequest;
-    private CastSession mAltSession;
-
-    @Before
-    public void setup() {
-        mListener = mock(CastSessionManagerListener.class);
-        mRequest = mock(CastSessionLaunchRequest.class);
-        mSession = mock(CastSession.class);
-
-        doReturn(mListener).when(mRequest).getSessionListener();
-
-        mManager = ChromeCastSessionManager.get();
-    }
-
-    private void setupAlternateMocks() {
-        mAltListener = mock(CastSessionManagerListener.class);
-        mAltRequest = mock(CastSessionLaunchRequest.class);
-        mAltSession = mock(CastSession.class);
-
-        doReturn(mAltListener).when(mAltRequest).getSessionListener();
-    }
-
-    @After
-    public void cleanup() {
-        ChromeCastSessionManager.resetInstanceForTesting();
-    }
-
-    private void setupLaunch(
-            CastSessionLaunchRequest request, CastSession session, boolean success) {
-        doAnswer(new Answer() {
-            @Override
-            public Object answer(InvocationOnMock invocation) {
-                if (success)
-                    mManager.onSessionStarted(session);
-                else
-                    mManager.onSessionStartFailed();
-
-                return null;
-            }
-        })
-                .when(request)
-                .start(any());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testLaunchingRequest() {
-        mManager.requestSessionLaunch(mRequest);
-        verify(mRequest).start(any());
-        verify(mListener).onSessionStarting(mRequest);
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testSessionRequestSucceeded() {
-        // Setup a successful launch.
-        setupLaunch(mRequest, mSession, true);
-
-        mManager.requestSessionLaunch(mRequest);
-        verify(mListener).onSessionStarted(mSession);
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testSessionRequestFailed() {
-        // Setup a failed launch.
-        setupLaunch(mRequest, null, false);
-
-        mManager.requestSessionLaunch(mRequest);
-        verify(mListener).onSessionStartFailed();
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testNewRequestWaitOnSessionClose() {
-        // Make sure |mManager| currently has a session.
-        setupAlternateMocks();
-        setupLaunch(mAltRequest, mAltSession, true);
-        mManager.requestSessionLaunch(mAltRequest);
-
-        // Simulate the session taking a while to stop.
-        doNothing().when(mAltSession).stopApplication();
-
-        // Launching a new request should stop the current session, and wait until it has has
-        // stopped before launching the request.
-        mManager.requestSessionLaunch(mRequest);
-        verify(mAltSession).stopApplication();
-        verify(mRequest, never()).start(any());
-    }
-
-    @Test
-    @Feature({"MediaRouter"})
-    public void testNewSessionStopsOldOnes() {
-        // Make sure |mManager| currently has a session.
-        setupAlternateMocks();
-        setupLaunch(mAltRequest, mAltSession, true);
-        mManager.requestSessionLaunch(mAltRequest);
-
-        // Setup the current session to successfuly stop.
-        doAnswer(new Answer() {
-            @Override
-            public Object answer(InvocationOnMock invocation) {
-                mManager.onSessionEnded();
-                return null;
-            }
-        })
-                .when(mAltSession)
-                .stopApplication();
-
-        // The old observer should be notified of a session closing.
-        // The new request should be launched
-        mManager.requestSessionLaunch(mRequest);
-        verify(mAltListener).onSessionEnded();
-        verify(mRequest).start(any());
-    }
-
-    // Makes sure we can override pending requests while waiting for an old
-    // session to close.
-    @Test
-    @Feature({"MediaRouter"})
-    public void testNewRequestOverwritePendingOnes() {
-        // Make sure |mManager| currently has a session.
-        setupAlternateMocks();
-        setupLaunch(mAltRequest, mAltSession, true);
-        mManager.requestSessionLaunch(mAltRequest);
-
-        // Simulate the current session taking a while to stop.
-        doNothing().when(mAltSession).stopApplication();
-
-        // Make sure the queued request isn't launched immediately.
-        CastSessionLaunchRequest middleRequest = mock(CastSessionLaunchRequest.class);
-        mManager.requestSessionLaunch(middleRequest);
-
-        // Queue up another pending request.
-        mManager.requestSessionLaunch(mRequest);
-
-        // Simulate |mAltSession| finally closing.
-        mManager.onSessionEnded();
-
-        // The first request shouldn't have started.
-        verify(middleRequest, never()).start(any());
-        // The last request should have been started.
-        verify(mRequest).start(any());
-    }
-
-    // Makes sure new requests are dropped when we are waiting for a request to
-    // complete its launch.
-    @Test
-    @Feature({"MediaRouter"})
-    public void testRequestAreDroppedWhenLaunching() {
-        setupAlternateMocks();
-
-        // Simulate a slow to launch request.
-        doNothing().when(mAltRequest).start(any());
-        mManager.requestSessionLaunch(mAltRequest);
-
-        // Queue up a new request whilst launching.
-        // Make this new request isn't started.
-        setupLaunch(mRequest, mSession, true);
-        mManager.requestSessionLaunch(mRequest);
-        verify(mRequest, never()).start(any());
-
-        // Simulate the completion of the slow request.
-        // Make sure the 2nd request was not launched.
-        mManager.onSessionStarted(mAltSession);
-        verify(mRequest, never()).start(any());
-    }
-}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 5511262..e0f586f 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1543,6 +1543,8 @@
     "ssl/ssl_blocking_page_base.cc",
     "ssl/ssl_blocking_page_base.h",
     "ssl/ssl_cert_reporter.h",
+    "ssl/ssl_client_auth_metrics.cc",
+    "ssl/ssl_client_auth_metrics.h",
     "ssl/ssl_client_certificate_selector.h",
     "ssl/ssl_config_service_manager.h",
     "ssl/ssl_config_service_manager_pref.cc",
@@ -3214,8 +3216,12 @@
       "apps/app_service/arc_apps_factory.h",
       "apps/app_service/built_in_chromeos_apps.cc",
       "apps/app_service/built_in_chromeos_apps.h",
+      "apps/app_service/crostini_apps.cc",
+      "apps/app_service/crostini_apps.h",
       "apps/app_service/extension_apps.cc",
       "apps/app_service/extension_apps.h",
+      "apps/app_service/launch_util.cc",
+      "apps/app_service/launch_util.h",
       "ash_service_registry.cc",
       "ash_service_registry.h",
       "component_updater/cros_component_installer_chromeos.cc",
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 9eadc92c..72040b0 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -163,7 +163,6 @@
     &payments::features::kWebPaymentsModifiers,
     &payments::features::kWebPaymentsSingleAppUiSkip,
     &language::kExplicitLanguageAsk,
-    &media::kCafMediaRouterImpl,
     &ntp_snippets::kArticleSuggestionsFeature,
     &ntp_snippets::kIncreasedVisibility,
     &ntp_snippets::kNotificationsFeature,
diff --git a/chrome/browser/apps/app_service/app_service_proxy.cc b/chrome/browser/apps/app_service/app_service_proxy.cc
index 056cc1c..f736bcc 100644
--- a/chrome/browser/apps/app_service/app_service_proxy.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy.cc
@@ -36,6 +36,7 @@
   // responsibility isn't intrinsically part of the AppServiceProxy, but doing
   // that here, for each such app type, is as good a place as any.
   built_in_chrome_os_apps_.Initialize(app_service_, profile);
+  crostini_apps_.Initialize(app_service_, profile);
   extension_apps_.Initialize(app_service_, profile,
                              apps::mojom::AppType::kExtension);
   extension_web_apps_.Initialize(app_service_, profile,
diff --git a/chrome/browser/apps/app_service/app_service_proxy.h b/chrome/browser/apps/app_service/app_service_proxy.h
index 2efc254..0c8c2667 100644
--- a/chrome/browser/apps/app_service/app_service_proxy.h
+++ b/chrome/browser/apps/app_service/app_service_proxy.h
@@ -14,6 +14,7 @@
 
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/apps/app_service/built_in_chromeos_apps.h"
+#include "chrome/browser/apps/app_service/crostini_apps.h"
 #include "chrome/browser/apps/app_service/extension_apps.h"
 #endif  // OS_CHROMEOS
 
@@ -69,6 +70,7 @@
 
 #if defined(OS_CHROMEOS)
   BuiltInChromeOsApps built_in_chrome_os_apps_;
+  CrostiniApps crostini_apps_;
   ExtensionApps extension_apps_;
   ExtensionApps extension_web_apps_;
 #endif  // OS_CHROMEOS
diff --git a/chrome/browser/apps/app_service/app_service_proxy_factory.cc b/chrome/browser/apps/app_service/app_service_proxy_factory.cc
index 6bfec56..3e62aba 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_factory.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_factory.cc
@@ -11,6 +11,7 @@
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 
 #if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
 #include "extensions/browser/extension_registry_factory.h"
 #endif  // OS_CHROMEOS
 
@@ -46,6 +47,7 @@
           "AppServiceProxy",
           BrowserContextDependencyManager::GetInstance()) {
 #if defined(OS_CHROMEOS)
+  DependsOn(crostini::CrostiniRegistryServiceFactory::GetInstance());
   DependsOn(extensions::ExtensionRegistryFactory::GetInstance());
 #endif  // OS_CHROMEOS
 }
diff --git a/chrome/browser/apps/app_service/arc_apps.cc b/chrome/browser/apps/app_service/arc_apps.cc
index ac996cf..ed4ddbf 100644
--- a/chrome/browser/apps/app_service/arc_apps.cc
+++ b/chrome/browser/apps/app_service/arc_apps.cc
@@ -115,18 +115,18 @@
 }
 
 ArcApps::ArcApps(Profile* profile)
-    : binding_(this),
-      profile_(profile),
-      prefs_(ArcAppListPrefs::Get(profile)),
-      next_u_key_(1) {
-  if (!prefs_ || !arc::IsArcAllowedForProfile(profile_)) {
+    : binding_(this), profile_(profile), prefs_(nullptr), next_u_key_(1) {
+  if (!arc::IsArcAllowedForProfile(profile_) ||
+      (arc::ArcServiceManager::Get() == nullptr)) {
     return;
   }
-  auto* arc_service_manager = arc::ArcServiceManager::Get();
-  if (!arc_service_manager) {
+
+  prefs_ = ArcAppListPrefs::Get(profile);
+  if (!prefs_) {
     return;
   }
-  ObservePrefs();
+  prefs_->AddObserver(this);
+  prefs_->app_connection_holder()->AddObserver(this);
 
   apps::mojom::PublisherPtr publisher;
   binding_.Bind(mojo::MakeRequest(&publisher));
@@ -144,13 +144,10 @@
 void ArcApps::Connect(apps::mojom::SubscriberPtr subscriber,
                       apps::mojom::ConnectOptionsPtr opts) {
   std::vector<apps::mojom::AppPtr> apps;
-  if (prefs_) {
-    for (const auto& app_id : prefs_->GetAppIds()) {
-      std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
-          prefs_->GetApp(app_id);
-      if (app_info) {
-        apps.push_back(Convert(app_id, *app_info));
-      }
+  for (const auto& app_id : prefs_->GetAppIds()) {
+    std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs_->GetApp(app_id);
+    if (app_info) {
+      apps.push_back(Convert(app_id, *app_info));
     }
   }
   subscriber->OnApps(std::move(apps));
@@ -162,7 +159,7 @@
                        apps::mojom::IconCompression icon_compression,
                        int32_t size_hint_in_dip,
                        LoadIconCallback callback) {
-  if (prefs_ && !icon_key.is_null() &&
+  if (!icon_key.is_null() &&
       (icon_key->icon_type == apps::mojom::IconType::kArc) &&
       !icon_key->s_key.empty()) {
     // Treat the Play Store as a special case, loading an icon defined by a
@@ -275,18 +272,11 @@
 }
 
 void ArcApps::OnConnectionReady() {
-  if (!prefs_) {
-    prefs_ = ArcAppListPrefs::Get(profile_);
-    ObservePrefs();
+  AppConnectionHolder* app_connection_holder = prefs_->app_connection_holder();
+  for (auto& pending : pending_load_icon_calls_) {
+    std::move(pending).Run(app_connection_holder);
   }
-  if (prefs_) {
-    AppConnectionHolder* app_connection_holder =
-        prefs_->app_connection_holder();
-    for (auto& pending : pending_load_icon_calls_) {
-      std::move(pending).Run(app_connection_holder);
-    }
-    pending_load_icon_calls_.clear();
-  }
+  pending_load_icon_calls_.clear();
 }
 
 void ArcApps::OnAppRegistered(const std::string& app_id,
@@ -340,17 +330,8 @@
   }
 }
 
-void ArcApps::ObservePrefs() {
-  prefs_->AddObserver(this);
-  prefs_->app_connection_holder()->AddObserver(this);
-}
-
 const base::FilePath ArcApps::GetCachedIconFilePath(const std::string& app_id,
                                                     int32_t size_hint_in_dip) {
-  if (!prefs_) {
-    return base::FilePath();
-  }
-
   // TODO(crbug.com/826982): process the app_id argument like the private
   // GetAppFromAppOrGroupId function and the ArcAppIcon::mapped_app_id_ field
   // in arc_app_icon.cc?
@@ -365,25 +346,23 @@
                              apps::mojom::IconCompression icon_compression,
                              int32_t size_hint_in_dip,
                              LoadIconCallback callback) {
-  if (prefs_) {
-    std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
-        prefs_->GetApp(icon_key_s_key);
-    if (app_info) {
-      base::OnceCallback<void(apps::ArcApps::AppConnectionHolder*)> pending =
-          base::BindOnce(&LoadIcon0, icon_compression,
-                         ConvertDipToPx(size_hint_in_dip),
-                         app_info->package_name, app_info->activity,
-                         app_info->icon_resource_id, std::move(callback));
+  std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
+      prefs_->GetApp(icon_key_s_key);
+  if (app_info) {
+    base::OnceCallback<void(apps::ArcApps::AppConnectionHolder*)> pending =
+        base::BindOnce(&LoadIcon0, icon_compression,
+                       ConvertDipToPx(size_hint_in_dip), app_info->package_name,
+                       app_info->activity, app_info->icon_resource_id,
+                       std::move(callback));
 
-      AppConnectionHolder* app_connection_holder =
-          prefs_->app_connection_holder();
-      if (app_connection_holder->IsConnected()) {
-        std::move(pending).Run(app_connection_holder);
-      } else {
-        pending_load_icon_calls_.push_back(std::move(pending));
-      }
-      return;
+    AppConnectionHolder* app_connection_holder =
+        prefs_->app_connection_holder();
+    if (app_connection_holder->IsConnected()) {
+      std::move(pending).Run(app_connection_holder);
+    } else {
+      pending_load_icon_calls_.push_back(std::move(pending));
     }
+    return;
   }
 
   // On failure, we still run the callback, with the zero IconValue.
diff --git a/chrome/browser/apps/app_service/arc_apps.h b/chrome/browser/apps/app_service/arc_apps.h
index 9e51c6e5..f6f31b8 100644
--- a/chrome/browser/apps/app_service/arc_apps.h
+++ b/chrome/browser/apps/app_service/arc_apps.h
@@ -73,8 +73,6 @@
                         const std::string& name) override;
   void OnAppLastLaunchTimeUpdated(const std::string& app_id) override;
 
-  void ObservePrefs();
-
   const base::FilePath GetCachedIconFilePath(const std::string& app_id,
                                              int32_t size_hint_in_dip);
   void LoadIconFromVM(const std::string icon_key_s_key,
diff --git a/chrome/browser/apps/app_service/crostini_apps.cc b/chrome/browser/apps/app_service/crostini_apps.cc
new file mode 100644
index 0000000..54d4260fd
--- /dev/null
+++ b/chrome/browser/apps/app_service/crostini_apps.cc
@@ -0,0 +1,240 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/apps/app_service/crostini_apps.h"
+
+#include <utility>
+
+#include "chrome/browser/apps/app_service/app_icon_factory.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/launch_util.h"
+#include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_util.h"
+#include "chrome/grit/chrome_unscaled_resources.h"
+
+// TODO(crbug.com/826982): the equivalent of
+// CrostiniAppModelBuilder::MaybeCreateRootFolder. Does some sort of "root
+// folder" abstraction belong here (on the publisher side of the App Service)
+// or should we hard-code that in one particular subscriber (the App List UI)?
+
+namespace apps {
+
+CrostiniApps::CrostiniApps()
+    : binding_(this), registry_(nullptr), next_u_key_(1) {}
+
+CrostiniApps::~CrostiniApps() {
+  if (registry_) {
+    registry_->RemoveObserver(this);
+  }
+}
+
+void CrostiniApps::Initialize(const apps::mojom::AppServicePtr& app_service,
+                              Profile* profile) {
+  if (!crostini::IsCrostiniUIAllowedForProfile(profile)) {
+    return;
+  }
+  registry_ = crostini::CrostiniRegistryServiceFactory::GetForProfile(profile);
+  if (!registry_) {
+    return;
+  }
+
+  registry_->AddObserver(this);
+
+  apps::mojom::PublisherPtr publisher;
+  binding_.Bind(mojo::MakeRequest(&publisher));
+  app_service->RegisterPublisher(std::move(publisher),
+                                 apps::mojom::AppType::kCrostini);
+}
+
+void CrostiniApps::Connect(apps::mojom::SubscriberPtr subscriber,
+                           apps::mojom::ConnectOptionsPtr opts) {
+  std::vector<apps::mojom::AppPtr> apps;
+  for (const std::string& app_id : registry_->GetRegisteredAppIds()) {
+    base::Optional<crostini::CrostiniRegistryService::Registration>
+        registration = registry_->GetRegistration(app_id);
+    if (registration.has_value()) {
+      apps.push_back(Convert(app_id, *registration, true));
+    }
+  }
+  subscriber->OnApps(std::move(apps));
+  subscribers_.AddPtr(std::move(subscriber));
+}
+
+void CrostiniApps::LoadIcon(const std::string& app_id,
+                            apps::mojom::IconKeyPtr icon_key,
+                            apps::mojom::IconCompression icon_compression,
+                            int32_t size_hint_in_dip,
+                            LoadIconCallback callback) {
+  if (!icon_key.is_null()) {
+    if ((icon_key->icon_type == apps::mojom::IconType::kResource) &&
+        (icon_key->u_key != 0) && (icon_key->u_key <= INT_MAX)) {
+      int resource_id = static_cast<int>(icon_key->u_key);
+      LoadIconFromResource(icon_compression, size_hint_in_dip, resource_id,
+                           std::move(callback));
+      return;
+    }
+
+    if (icon_key->icon_type == apps::mojom::IconType::kCrostini) {
+      // TODO(crbug.com/826982): don't hard-code SCALE_FACTOR_100P.
+      auto scale_factor = ui::ScaleFactor::SCALE_FACTOR_100P;
+
+      // Try loading the icon from an on-disk cache. If that fails, fall back
+      // to LoadIconFromVM.
+      LoadIconFromFileWithFallback(
+          icon_compression, size_hint_in_dip,
+          registry_->GetIconPath(icon_key->s_key, scale_factor),
+          std::move(callback),
+          base::BindOnce(&CrostiniApps::LoadIconFromVM,
+                         weak_ptr_factory_.GetWeakPtr(), icon_key->s_key,
+                         scale_factor));
+      return;
+    }
+  }
+
+  // On failure, we still run the callback, with the zero IconValue.
+  std::move(callback).Run(apps::mojom::IconValue::New());
+}
+
+void CrostiniApps::Launch(const std::string& app_id,
+                          int32_t event_flags,
+                          apps::mojom::LaunchSource launch_source,
+                          int64_t display_id) {
+  apps_util::Launch(app_id, event_flags, launch_source, display_id);
+}
+
+void CrostiniApps::SetPermission(const std::string& app_id,
+                                 apps::mojom::PermissionPtr permission) {
+  NOTIMPLEMENTED();
+}
+
+void CrostiniApps::Uninstall(const std::string& app_id) {
+  NOTIMPLEMENTED();
+}
+
+void CrostiniApps::OpenNativeSettings(const std::string& app_id) {
+  NOTIMPLEMENTED();
+}
+
+void CrostiniApps::OnRegistryUpdated(
+    crostini::CrostiniRegistryService* registry_service,
+    const std::vector<std::string>& updated_apps,
+    const std::vector<std::string>& removed_apps,
+    const std::vector<std::string>& inserted_apps) {
+  for (const std::string& app_id : updated_apps) {
+    PublishAppID(app_id, PublishAppIDType::kUpdate);
+  }
+  for (const std::string& app_id : removed_apps) {
+    PublishAppID(app_id, PublishAppIDType::kUninstall);
+  }
+  for (const std::string& app_id : inserted_apps) {
+    PublishAppID(app_id, PublishAppIDType::kInstall);
+  }
+}
+
+void CrostiniApps::OnAppIconUpdated(const std::string& app_id,
+                                    ui::ScaleFactor scale_factor) {
+  apps::mojom::AppPtr app = apps::mojom::App::New();
+  app->app_type = apps::mojom::AppType::kCrostini;
+  app->app_id = app_id;
+  app->icon_key = NewIconKey(app_id);
+  Publish(std::move(app));
+}
+
+void CrostiniApps::LoadIconFromVM(const std::string icon_key_s_key,
+                                  ui::ScaleFactor scale_factor,
+                                  LoadIconCallback callback) {
+  // Treat this as failure. We still run the callback, with the zero IconValue.
+  std::move(callback).Run(apps::mojom::IconValue::New());
+
+  // Ask the VM to load the icon (and write a cached copy to the file system).
+  // The "Maybe" is because multiple requests for the same icon will be merged,
+  // calling OnAppIconUpdated only once. In OnAppIconUpdated, we'll publish a
+  // new IconKey, and subscribers can re-schedule new LoadIcon calls, with new
+  // LoadIconCallback's, that will pick up that cached copy.
+  //
+  // TODO(crbug.com/826982): add a safeguard to prevent an infinite loop where
+  // OnAppIconUpdated somehow doesn't write the cached icon file where we
+  // expect, leading to another MaybeRequestIcon call, leading to another
+  // OnAppIconUpdated call, leading to another MaybeRequestIcon call, etc.
+  registry_->MaybeRequestIcon(icon_key_s_key, scale_factor);
+}
+
+apps::mojom::AppPtr CrostiniApps::Convert(
+    const std::string& app_id,
+    const crostini::CrostiniRegistryService::Registration& registration,
+    bool new_icon_key) {
+  apps::mojom::AppPtr app = apps::mojom::App::New();
+
+  app->app_type = apps::mojom::AppType::kCrostini;
+  app->app_id = app_id;
+  app->readiness = apps::mojom::Readiness::kReady;
+  app->name = registration.Name();
+
+  if (new_icon_key) {
+    app->icon_key = NewIconKey(app_id);
+  }
+
+  app->last_launch_time = registration.LastLaunchTime();
+  app->install_time = registration.InstallTime();
+
+  app->installed_internally = apps::mojom::OptionalBool::kFalse;
+
+  // TODO(crbug.com/826982): if Crostini isn't enabled, don't show the Terminal
+  // item until it becomes enabled.
+  auto show = !registration.NoDisplay() ? apps::mojom::OptionalBool::kTrue
+                                        : apps::mojom::OptionalBool::kFalse;
+  app->show_in_launcher = show;
+  app->show_in_search = show;
+
+  return app;
+}
+
+apps::mojom::IconKeyPtr CrostiniApps::NewIconKey(const std::string& app_id) {
+  auto icon_key = apps::mojom::IconKey::New();
+
+  // Treat the Crostini Terminal as a special case, loading an icon defined by
+  // a resource instead of asking the Crostini VM (or the cache of previous
+  // responses from the Crostini VM). Presumably this is for bootstrapping: the
+  // Crostini Terminal icon (the UI for enabling and installing Crostini apps)
+  // should be showable even before the user has installed their first Crostini
+  // app and before bringing up an Crostini VM for the first time.
+  if (app_id == crostini::kCrostiniTerminalId) {
+    icon_key->icon_type = apps::mojom::IconType::kResource;
+    icon_key->u_key = IDR_LOGO_CROSTINI_TERMINAL;
+  } else {
+    icon_key->icon_type = apps::mojom::IconType::kCrostini;
+    icon_key->s_key = app_id;
+    icon_key->u_key = next_u_key_++;
+  }
+
+  return icon_key;
+}
+
+void CrostiniApps::PublishAppID(const std::string& app_id,
+                                PublishAppIDType type) {
+  if (type == PublishAppIDType::kUninstall) {
+    apps::mojom::AppPtr app = apps::mojom::App::New();
+    app->app_type = apps::mojom::AppType::kCrostini;
+    app->app_id = app_id;
+    app->readiness = apps::mojom::Readiness::kUninstalledByUser;
+    Publish(std::move(app));
+    return;
+  }
+
+  base::Optional<crostini::CrostiniRegistryService::Registration> registration =
+      registry_->GetRegistration(app_id);
+  if (registration.has_value()) {
+    Publish(Convert(app_id, *registration, type == PublishAppIDType::kInstall));
+  }
+}
+
+void CrostiniApps::Publish(apps::mojom::AppPtr app) {
+  subscribers_.ForAllPtrs([&app](apps::mojom::Subscriber* subscriber) {
+    std::vector<apps::mojom::AppPtr> apps;
+    apps.push_back(app.Clone());
+    subscriber->OnApps(std::move(apps));
+  });
+}
+
+}  // namespace apps
diff --git a/chrome/browser/apps/app_service/crostini_apps.h b/chrome/browser/apps/app_service/crostini_apps.h
new file mode 100644
index 0000000..148c6316
--- /dev/null
+++ b/chrome/browser/apps/app_service/crostini_apps.h
@@ -0,0 +1,98 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_APPS_APP_SERVICE_CROSTINI_APPS_H_
+#define CHROME_BROWSER_APPS_APP_SERVICE_CROSTINI_APPS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
+#include "chrome/services/app_service/public/mojom/app_service.mojom.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/interface_ptr_set.h"
+
+class Profile;
+
+namespace apps {
+
+// An app publisher (in the App Service sense) of Crostini apps,
+//
+// See chrome/services/app_service/README.md.
+class CrostiniApps : public KeyedService,
+                     public apps::mojom::Publisher,
+                     public crostini::CrostiniRegistryService::Observer {
+ public:
+  CrostiniApps();
+  ~CrostiniApps() override;
+
+  void Initialize(const apps::mojom::AppServicePtr& app_service,
+                  Profile* profile);
+
+ private:
+  enum class PublishAppIDType {
+    kInstall,
+    kUninstall,
+    kUpdate,
+  };
+
+  // apps::mojom::Publisher overrides.
+  void Connect(apps::mojom::SubscriberPtr subscriber,
+               apps::mojom::ConnectOptionsPtr opts) override;
+  void LoadIcon(const std::string& app_id,
+                apps::mojom::IconKeyPtr icon_key,
+                apps::mojom::IconCompression icon_compression,
+                int32_t size_hint_in_dip,
+                LoadIconCallback callback) override;
+  void Launch(const std::string& app_id,
+              int32_t event_flags,
+              apps::mojom::LaunchSource launch_source,
+              int64_t display_id) override;
+  void SetPermission(const std::string& app_id,
+                     apps::mojom::PermissionPtr permission) override;
+  void Uninstall(const std::string& app_id) override;
+  void OpenNativeSettings(const std::string& app_id) override;
+
+  // CrostiniRegistryService::Observer overrides.
+  void OnRegistryUpdated(
+      crostini::CrostiniRegistryService* registry_service,
+      const std::vector<std::string>& updated_apps,
+      const std::vector<std::string>& removed_apps,
+      const std::vector<std::string>& inserted_apps) override;
+  void OnAppIconUpdated(const std::string& app_id,
+                        ui::ScaleFactor scale_factor) override;
+
+  void LoadIconFromVM(const std::string icon_key_s_key,
+                      ui::ScaleFactor scale_factor,
+                      LoadIconCallback callback);
+
+  apps::mojom::AppPtr Convert(
+      const std::string& app_id,
+      const crostini::CrostiniRegistryService::Registration& registration,
+      bool new_icon_key);
+  apps::mojom::IconKeyPtr NewIconKey(const std::string& app_id);
+  void PublishAppID(const std::string& app_id, PublishAppIDType type);
+  void Publish(apps::mojom::AppPtr app);
+
+  mojo::Binding<apps::mojom::Publisher> binding_;
+  mojo::InterfacePtrSet<apps::mojom::Subscriber> subscribers_;
+
+  crostini::CrostiniRegistryService* registry_;
+
+  // |next_u_key_| is incremented every time Convert returns a valid AppPtr, so
+  // that when an app's icon has changed, this apps::mojom::Publisher sends a
+  // different IconKey even though the IconKey's s_key hasn't changed.
+  uint64_t next_u_key_;
+
+  base::WeakPtrFactory<CrostiniApps> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(CrostiniApps);
+};
+
+}  // namespace apps
+
+#endif  // CHROME_BROWSER_APPS_APP_SERVICE_CROSTINI_APPS_H_
diff --git a/chrome/browser/apps/app_service/extension_apps.cc b/chrome/browser/apps/app_service/extension_apps.cc
index c82e0d8..da3f2ce 100644
--- a/chrome/browser/apps/app_service/extension_apps.cc
+++ b/chrome/browser/apps/app_service/extension_apps.cc
@@ -8,9 +8,9 @@
 #include <utility>
 #include <vector>
 
-#include "ash/public/cpp/shelf_types.h"
 #include "base/strings/string16.h"
 #include "chrome/browser/apps/app_service/app_icon_factory.h"
+#include "chrome/browser/apps/app_service/launch_util.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_util.h"
@@ -18,7 +18,6 @@
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ui/app_list/extension_app_utils.h"
 #include "chrome/browser/ui/app_list/search/search_util.h"
-#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/common/extensions/extension_metrics.h"
@@ -59,21 +58,6 @@
     CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
 };
 
-ash::ShelfLaunchSource ConvertLaunchSource(
-    apps::mojom::LaunchSource launch_source) {
-  switch (launch_source) {
-    case apps::mojom::LaunchSource::kUnknown:
-      return ash::LAUNCH_FROM_UNKNOWN;
-    case apps::mojom::LaunchSource::kFromAppListGrid:
-    case apps::mojom::LaunchSource::kFromAppListGridContextMenu:
-      return ash::LAUNCH_FROM_APP_LIST;
-    case apps::mojom::LaunchSource::kFromAppListQuery:
-    case apps::mojom::LaunchSource::kFromAppListQueryContextMenu:
-    case apps::mojom::LaunchSource::kFromAppListRecommendation:
-      return ash::LAUNCH_FROM_APP_LIST_SEARCH;
-  }
-}
-
 }  // namespace
 
 namespace apps {
@@ -191,9 +175,7 @@
       break;
   }
 
-  ChromeLauncherController::instance()->LaunchApp(
-      ash::ShelfID(app_id), ConvertLaunchSource(launch_source), event_flags,
-      display_id);
+  apps_util::Launch(app_id, event_flags, launch_source, display_id);
 }
 
 void ExtensionApps::SetPermission(const std::string& app_id,
diff --git a/chrome/browser/apps/app_service/launch_util.cc b/chrome/browser/apps/app_service/launch_util.cc
new file mode 100644
index 0000000..6ccd1b0e
--- /dev/null
+++ b/chrome/browser/apps/app_service/launch_util.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/apps/app_service/launch_util.h"
+
+#include "ash/public/cpp/shelf_types.h"
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
+
+namespace {
+
+ash::ShelfLaunchSource ConvertLaunchSource(
+    apps::mojom::LaunchSource launch_source) {
+  switch (launch_source) {
+    case apps::mojom::LaunchSource::kUnknown:
+      return ash::LAUNCH_FROM_UNKNOWN;
+    case apps::mojom::LaunchSource::kFromAppListGrid:
+    case apps::mojom::LaunchSource::kFromAppListGridContextMenu:
+      return ash::LAUNCH_FROM_APP_LIST;
+    case apps::mojom::LaunchSource::kFromAppListQuery:
+    case apps::mojom::LaunchSource::kFromAppListQueryContextMenu:
+    case apps::mojom::LaunchSource::kFromAppListRecommendation:
+      return ash::LAUNCH_FROM_APP_LIST_SEARCH;
+  }
+}
+
+}  // namespace
+
+namespace apps_util {
+
+void Launch(const std::string& app_id,
+            int32_t event_flags,
+            apps::mojom::LaunchSource launch_source,
+            int64_t display_id) {
+  ChromeLauncherController::instance()->LaunchApp(
+      ash::ShelfID(app_id), ConvertLaunchSource(launch_source), event_flags,
+      display_id);
+}
+
+}  // namespace apps_util
diff --git a/chrome/browser/apps/app_service/launch_util.h b/chrome/browser/apps/app_service/launch_util.h
new file mode 100644
index 0000000..30afbdb
--- /dev/null
+++ b/chrome/browser/apps/app_service/launch_util.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_APPS_APP_SERVICE_LAUNCH_UTIL_H_
+#define CHROME_BROWSER_APPS_APP_SERVICE_LAUNCH_UTIL_H_
+
+// Utility functions for launching an app.
+
+#include <string>
+
+#include "chrome/services/app_service/public/mojom/types.mojom.h"
+
+namespace apps_util {
+
+void Launch(const std::string& app_id,
+            int32_t event_flags,
+            apps::mojom::LaunchSource launch_source,
+            int64_t display_id);
+
+}  // namespace apps_util
+
+#endif  // CHROME_BROWSER_APPS_APP_SERVICE_LAUNCH_UTIL_H_
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 70d2354..ec6d3ed 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -233,9 +233,9 @@
         <include name="IDR_APP_MANAGEMENT_API_LISTENER_HTML" file="resources\app_management\api_listener.html" type="BINDATA"/>
         <include name="IDR_APP_MANAGEMENT_API_LISTENER_JS" file="resources\app_management\api_listener.js" type="BINDATA"/>
         <include name="IDR_APP_MANAGEMENT_APP_HTML" file="resources\app_management\app.html" type="BINDATA" />
-        <include name="IDR_APP_MANAGEMENT_APP_JS" file="resources\app_management\app.js" type="BINDATA" />
         <include name="IDR_APP_MANAGEMENT_APP_ITEM_HTML" file="resources\app_management\app_item.html" type="BINDATA" />
         <include name="IDR_APP_MANAGEMENT_APP_ITEM_JS" file="resources\app_management\app_item.js" type="BINDATA" />
+        <include name="IDR_APP_MANAGEMENT_APP_JS" file="resources\app_management\app.js" type="BINDATA" />
         <include name="IDR_APP_MANAGEMENT_BROWSER_PROXY_HTML" file="resources\app_management\browser_proxy.html" type="BINDATA" />
         <include name="IDR_APP_MANAGEMENT_BROWSER_PROXY_JS" file="resources\app_management\browser_proxy.js" type="BINDATA" />
         <include name="IDR_APP_MANAGEMENT_CHROME_APP_PERMISSION_VIEW_HTML" file="resources\app_management\chrome_app_permission_view.html" type="BINDATA"/>
@@ -246,14 +246,16 @@
         <include name="IDR_APP_MANAGEMENT_INDEX_HTML" file="resources\app_management\index.html" type="BINDATA" />
         <include name="IDR_APP_MANAGEMENT_MAIN_VIEW_HTML" file="resources\app_management\main_view.html" type="BINDATA" />
         <include name="IDR_APP_MANAGEMENT_MAIN_VIEW_JS" file="resources\app_management\main_view.js" type="BINDATA" />
-        <include name="IDR_APP_MANAGEMENT_NOTIFICATIONS_VIEW_HTML" file="resources\app_management\notifications_view.html" type="BINDATA"/>
-        <include name="IDR_APP_MANAGEMENT_NOTIFICATIONS_VIEW_JS" file="resources\app_management\notifications_view.js" type="BINDATA"/>
-        <include name="IDR_APP_MANAGEMENT_PERMISSION_VIEW_HEADER_HTML" file="resources\app_management\permission_view_header.html" type="BINDATA"/>
-        <include name="IDR_APP_MANAGEMENT_PERMISSION_VIEW_HEADER_JS" file="resources\app_management\permission_view_header.js" type="BINDATA"/>
         <include name="IDR_APP_MANAGEMENT_METADATA_VIEW_HTML" file="resources\app_management\metadata_view.html" type="BINDATA" />
         <include name="IDR_APP_MANAGEMENT_METADATA_VIEW_JS" file="resources\app_management\metadata_view.js" type="BINDATA" />
+        <include name="IDR_APP_MANAGEMENT_NOTIFICATIONS_VIEW_HTML" file="resources\app_management\notifications_view.html" type="BINDATA"/>
+        <include name="IDR_APP_MANAGEMENT_NOTIFICATIONS_VIEW_JS" file="resources\app_management\notifications_view.js" type="BINDATA"/>
         <include name="IDR_APP_MANAGEMENT_PERMISSION_ITEM_HTML" file="resources\app_management\permission_item.html" type="BINDATA"/>
         <include name="IDR_APP_MANAGEMENT_PERMISSION_ITEM_JS" file="resources\app_management\permission_item.js" type="BINDATA"/>
+        <include name="IDR_APP_MANAGEMENT_PERMISSION_TOGGLE_HTML" file="resources\app_management\permission_toggle.html" type="BINDATA"/>
+        <include name="IDR_APP_MANAGEMENT_PERMISSION_TOGGLE_JS" file="resources\app_management\permission_toggle.js" type="BINDATA"/>
+        <include name="IDR_APP_MANAGEMENT_PERMISSION_VIEW_HEADER_HTML" file="resources\app_management\permission_view_header.html" type="BINDATA"/>
+        <include name="IDR_APP_MANAGEMENT_PERMISSION_VIEW_HEADER_JS" file="resources\app_management\permission_view_header.js" type="BINDATA"/>
         <include name="IDR_APP_MANAGEMENT_PWA_PERMISSION_VIEW_HTML" file="resources\app_management\pwa_permission_view.html" type="BINDATA"/>
         <include name="IDR_APP_MANAGEMENT_PWA_PERMISSION_VIEW_JS" file="resources\app_management\pwa_permission_view.js" type="BINDATA"/>
         <include name="IDR_APP_MANAGEMENT_REDUCERS_HTML" file="resources\app_management\reducers.html" type="BINDATA" />
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index d1b7c89..5ef0e74 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -128,6 +128,7 @@
 #include "chrome/browser/ssl/insecure_sensitive_input_driver_factory.h"
 #include "chrome/browser/ssl/ssl_blocking_page.h"
 #include "chrome/browser/ssl/ssl_cert_reporter.h"
+#include "chrome/browser/ssl/ssl_client_auth_metrics.h"
 #include "chrome/browser/ssl/ssl_client_certificate_selector.h"
 #include "chrome/browser/ssl/ssl_error_handler.h"
 #include "chrome/browser/ssl/ssl_error_navigation_throttle.h"
@@ -2837,12 +2838,14 @@
         std::move(auto_selected_identity),
         base::Bind(&content::ClientCertificateDelegate::ContinueWithCertificate,
                    base::Passed(&delegate), std::move(cert)));
+    LogClientAuthResult(ClientCertSelectionResult::kAutoSelect);
     return;
   }
 
   if (!may_show_cert_selection) {
     LOG(WARNING) << "No client cert matched by policy and user selection is "
                     "not allowed.";
+    LogClientAuthResult(ClientCertSelectionResult::kNoSelectionAllowed);
     // Continue without client certificate. We do this to mimic the case of no
     // client certificate being present in the profile's certificate store.
     delegate->ContinueWithCertificate(nullptr, nullptr);
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.cc b/chrome/browser/chromeos/login/session/user_session_manager.cc
index 2b207a9..13af0b3 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager.cc
+++ b/chrome/browser/chromeos/login/session/user_session_manager.cc
@@ -1695,6 +1695,9 @@
   if (start_session_type_ == PRIMARY_USER_SESSION) {
 #if BUILDFLAG(ENABLE_CROS_ASSISTANT)
     // Initialize Assistant early to be used in post login Oobe steps.
+    // Note: AssistantClient::MaybeInit is also called in
+    // SessionControllerClient::OnSessionStateChanged, which happends after the
+    // post login Oobe steps. Therefore Assistant is initialized here.
     if (chromeos::switches::IsAssistantEnabled())
       AssistantClient::Get()->MaybeInit(profile);
 #endif
diff --git a/chrome/browser/devtools/device/usb/android_usb_browsertest.cc b/chrome/browser/devtools/device/usb/android_usb_browsertest.cc
index 741b2cb6..02018d9 100644
--- a/chrome/browser/devtools/device/usb/android_usb_browsertest.cc
+++ b/chrome/browser/devtools/device/usb/android_usb_browsertest.cc
@@ -31,6 +31,7 @@
 #include "device/usb/public/cpp/fake_usb_device_info.h"
 #include "device/usb/public/cpp/fake_usb_device_manager.h"
 #include "device/usb/public/mojom/device.mojom.h"
+#include "device/usb/public/mojom/device_enumeration_options.mojom.h"
 #include "device/usb/public/mojom/device_manager.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/devtools/device/usb/usb_device_manager_helper.cc b/chrome/browser/devtools/device/usb/usb_device_manager_helper.cc
index 0c27b711..1c1673b 100644
--- a/chrome/browser/devtools/device/usb/usb_device_manager_helper.cc
+++ b/chrome/browser/devtools/device/usb/usb_device_manager_helper.cc
@@ -14,6 +14,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/common/service_manager_connection.h"
+#include "device/usb/public/mojom/device_enumeration_options.mojom.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
 #include "services/device/public/mojom/constants.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
diff --git a/chrome/browser/devtools/devtools_eye_dropper.cc b/chrome/browser/devtools/devtools_eye_dropper.cc
index 55822d8..b1ce3098 100644
--- a/chrome/browser/devtools/devtools_eye_dropper.cc
+++ b/chrome/browser/devtools/devtools_eye_dropper.cc
@@ -20,6 +20,7 @@
 #include "content/public/common/screen_info.h"
 #include "media/base/limits.h"
 #include "media/base/video_frame.h"
+#include "media/capture/mojom/video_capture_types.mojom.h"
 #include "third_party/blink/public/platform/web_input_event.h"
 #include "third_party/blink/public/platform/web_mouse_event.h"
 #include "third_party/skia/include/core/SkCanvas.h"
diff --git a/chrome/browser/download/download_dir_policy_handler.cc b/chrome/browser/download/download_dir_policy_handler.cc
index ce9d5e1..699358c 100644
--- a/chrome/browser/download/download_dir_policy_handler.cc
+++ b/chrome/browser/download/download_dir_policy_handler.cc
@@ -70,7 +70,7 @@
         DownloadPrefs::GetDefaultDownloadDirectory().value());
   }
   prefs->SetValue(prefs::kDownloadDefaultDirectory,
-                  std::make_unique<base::Value>(expanded_value));
+                  base::Value(expanded_value));
 
   // If the policy is mandatory, prompt for download should be disabled.
   // Otherwise, it would enable a user to bypass the mandatory policy.
diff --git a/chrome/browser/media/cast_mirroring_service_host.cc b/chrome/browser/media/cast_mirroring_service_host.cc
index 286256a..8571183 100644
--- a/chrome/browser/media/cast_mirroring_service_host.cc
+++ b/chrome/browser/media/cast_mirroring_service_host.cc
@@ -16,7 +16,10 @@
 #include "chrome/browser/media/cast_remoting_connector.h"
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "components/mirroring/browser/single_client_video_capture_host.h"
+#include "components/mirroring/mojom/cast_message_channel.mojom.h"
 #include "components/mirroring/mojom/constants.mojom.h"
+#include "components/mirroring/mojom/session_observer.mojom.h"
+#include "components/mirroring/mojom/session_parameters.mojom.h"
 #include "content/public/browser/audio_loopback_stream_creator.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
diff --git a/chrome/browser/media/cast_mirroring_service_host_browsertest.cc b/chrome/browser/media/cast_mirroring_service_host_browsertest.cc
index 1110a21..791b3ac 100644
--- a/chrome/browser/media/cast_mirroring_service_host_browsertest.cc
+++ b/chrome/browser/media/cast_mirroring_service_host_browsertest.cc
@@ -11,9 +11,17 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/in_process_browser_test.h"
+#include "components/mirroring/mojom/cast_message_channel.mojom.h"
+#include "components/mirroring/mojom/session_observer.mojom.h"
+#include "components/mirroring/mojom/session_parameters.mojom.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 "media/capture/mojom/video_capture.mojom.h"
+#include "media/capture/mojom/video_capture_types.mojom.h"
+#include "media/capture/video_capture_types.h"
+#include "media/mojo/interfaces/audio_data_pipe.mojom.h"
+#include "media/mojo/interfaces/audio_input_stream.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/base/ui_base_features.h"
diff --git a/chrome/browser/media/cast_remoting_connector.h b/chrome/browser/media/cast_remoting_connector.h
index 33597f2..4c76098 100644
--- a/chrome/browser/media/cast_remoting_connector.h
+++ b/chrome/browser/media/cast_remoting_connector.h
@@ -14,6 +14,7 @@
 #include "components/sessions/core/session_id.h"
 #include "media/mojo/interfaces/mirror_service_remoting.mojom.h"
 #include "media/mojo/interfaces/remoting.mojom.h"
+#include "media/mojo/interfaces/remoting_common.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 
 namespace content {
diff --git a/chrome/browser/payments/android/journey_logger_android.cc b/chrome/browser/payments/android/journey_logger_android.cc
index 8280ce7..bf7b757 100644
--- a/chrome/browser/payments/android/journey_logger_android.cc
+++ b/chrome/browser/payments/android/journey_logger_android.cc
@@ -78,6 +78,13 @@
   journey_logger_.SetCanMakePaymentValue(jvalue);
 }
 
+void JourneyLoggerAndroid::SetHasEnrolledInstrumentValue(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& jcaller,
+    jboolean jvalue) {
+  journey_logger_.SetHasEnrolledInstrumentValue(jvalue);
+}
+
 void JourneyLoggerAndroid::SetEventOccurred(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& jcaller,
diff --git a/chrome/browser/payments/android/journey_logger_android.h b/chrome/browser/payments/android/journey_logger_android.h
index ed9a062..fbc2598 100644
--- a/chrome/browser/payments/android/journey_logger_android.h
+++ b/chrome/browser/payments/android/journey_logger_android.h
@@ -44,6 +44,10 @@
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& jcaller,
       jboolean jvalue);
+  void SetHasEnrolledInstrumentValue(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& jcaller,
+      jboolean jvalue);
   void SetEventOccurred(JNIEnv* env,
                         const base::android::JavaParamRef<jobject>& jcaller,
                         jint jevent);
diff --git a/chrome/browser/profiling_host/memlog_browsertest.cc b/chrome/browser/profiling_host/memlog_browsertest.cc
index f3ffae1..09f81a1 100644
--- a/chrome/browser/profiling_host/memlog_browsertest.cc
+++ b/chrome/browser/profiling_host/memlog_browsertest.cc
@@ -34,7 +34,6 @@
 struct TestParam {
   Mode mode;
   mojom::StackMode stack_mode;
-  bool stream_samples;
   bool start_profiling_with_command_line_flag;
   bool should_sample;
   bool sample_everything;
@@ -93,7 +92,6 @@
 IN_PROC_BROWSER_TEST_P(MemlogBrowserTest, MAYBE_EndToEnd) {
   LOG(INFO) << "Memlog mode: " << static_cast<int>(GetParam().mode);
   LOG(INFO) << "Memlog stack mode: " << static_cast<int>(GetParam().stack_mode);
-  LOG(INFO) << "Stream samples: " << GetParam().stream_samples;
   LOG(INFO) << "Started via command line flag: "
             << GetParam().start_profiling_with_command_line_flag;
   LOG(INFO) << "Should sample: " << GetParam().should_sample;
@@ -102,7 +100,6 @@
   TestDriver::Options options;
   options.mode = GetParam().mode;
   options.stack_mode = GetParam().stack_mode;
-  options.stream_samples = GetParam().stream_samples;
   options.profiling_already_started =
       GetParam().start_profiling_with_command_line_flag;
   options.should_sample = GetParam().should_sample;
@@ -126,60 +123,54 @@
   stack_modes.push_back(mojom::StackMode::NATIVE_WITHOUT_THREAD_NAMES);
   stack_modes.push_back(mojom::StackMode::PSEUDO);
 
-  for (bool stream_samples : (bool[]){true, false}) {
-    for (const auto& mode : dynamic_start_modes) {
-      for (const auto& stack_mode : stack_modes) {
-        params.push_back({mode, stack_mode, stream_samples,
-                          false /* start_profiling_with_command_line_flag */,
-                          false /* should_sample */,
-                          false /* sample_everything*/});
-      }
+  for (const auto& mode : dynamic_start_modes) {
+    for (const auto& stack_mode : stack_modes) {
+      params.push_back(
+          {mode, stack_mode, false /* start_profiling_with_command_line_flag */,
+           false /* should_sample */, false /* sample_everything*/});
     }
+  }
 
 // For unknown reasons, renderer profiling has become flaky on ChromeOS. This is
 // likely happening because the renderers are never being given the signal to
 // start profiling. It's unclear why this happens. https://crbug.com/843843.
 // https://crbug.com/843467.
 #if !defined(OS_CHROMEOS)
-    // Non-browser processes must be profiled with a command line flag, since
-    // otherwise, profiling will start after the relevant processes have been
-    // created, thus that process will be not be profiled.
-    std::vector<Mode> command_line_start_modes;
-    command_line_start_modes.push_back(Mode::kAll);
-    command_line_start_modes.push_back(Mode::kAllRenderers);
-    for (const auto& mode : command_line_start_modes) {
-      for (const auto& stack_mode : stack_modes) {
-        params.push_back({mode, stack_mode, stream_samples,
-                          true /* start_profiling_with_command_line_flag */,
-                          false /* should_sample */,
-                          false /* sample_everything*/});
-      }
+  // Non-browser processes must be profiled with a command line flag, since
+  // otherwise, profiling will start after the relevant processes have been
+  // created, thus that process will be not be profiled.
+  std::vector<Mode> command_line_start_modes;
+  command_line_start_modes.push_back(Mode::kAll);
+  command_line_start_modes.push_back(Mode::kAllRenderers);
+  for (const auto& mode : command_line_start_modes) {
+    for (const auto& stack_mode : stack_modes) {
+      params.push_back(
+          {mode, stack_mode, true /* start_profiling_with_command_line_flag */,
+           false /* should_sample */, false /* sample_everything*/});
     }
+  }
 #endif  // defined(OS_CHROMEOS)
 
-    // Test sampling all allocations.
-    params.push_back(
-        {Mode::kBrowser, mojom::StackMode::NATIVE_WITH_THREAD_NAMES,
-         stream_samples, false /* start_profiling_with_command_line_flag */,
-         true /* should_sample */, true /* sample_everything*/});
+  // Test sampling all allocations.
+  params.push_back({Mode::kBrowser, mojom::StackMode::NATIVE_WITH_THREAD_NAMES,
+                    false /* start_profiling_with_command_line_flag */,
+                    true /* should_sample */, true /* sample_everything*/});
 
-    // Test sampling some allocations.
-    params.push_back({Mode::kBrowser, mojom::StackMode::PSEUDO, stream_samples,
-                      false /* start_profiling_with_command_line_flag */,
-                      true /* should_sample */, false /* sample_everything*/});
+  // Test sampling some allocations.
+  params.push_back({Mode::kBrowser, mojom::StackMode::PSEUDO,
+                    false /* start_profiling_with_command_line_flag */,
+                    true /* should_sample */, false /* sample_everything*/});
 
-    // Test thread names for native profiling.
-    params.push_back({Mode::kBrowser,
-                      mojom::StackMode::NATIVE_WITH_THREAD_NAMES,
-                      stream_samples, false});
+  // Test thread names for native profiling.
+  params.push_back(
+      {Mode::kBrowser, mojom::StackMode::NATIVE_WITH_THREAD_NAMES, false});
 
-    // Profile all utility processes and the browser process. The main goal is
-    // to check that there is no deadlock in the profiling process.
-    params.push_back(
-        {Mode::kUtilityAndBrowser, mojom::StackMode::NATIVE_WITH_THREAD_NAMES,
-         stream_samples, false /* start_profiling_with_command_line_flag */,
-         true /* should_sample */, false /* sample_everything*/});
-  }
+  // Profile all utility processes and the browser process. The main goal is to
+  // check that there is no deadlock in the profiling process.
+  params.push_back({Mode::kUtilityAndBrowser,
+                    mojom::StackMode::NATIVE_WITH_THREAD_NAMES,
+                    false /* start_profiling_with_command_line_flag */,
+                    true /* should_sample */, false /* sample_everything*/});
   return params;
 }
 
diff --git a/chrome/browser/resources/app_management/app.js b/chrome/browser/resources/app_management/app.js
index 2c5086a..a200919 100644
--- a/chrome/browser/resources/app_management/app.js
+++ b/chrome/browser/resources/app_management/app.js
@@ -47,7 +47,7 @@
     });
 
     this.watch('notificationsViewSelected_', (state) => {
-      return state.currentPage.pageType === PageType.NOTIFICATIONS;
+      return app_management.util.isNotificationsViewSelected(state);
     });
 
     this.updateFromStore();
diff --git a/chrome/browser/resources/app_management/browser_proxy.js b/chrome/browser/resources/app_management/browser_proxy.js
index ca9833f..b9d030cc 100644
--- a/chrome/browser/resources/app_management/browser_proxy.js
+++ b/chrome/browser/resources/app_management/browser_proxy.js
@@ -20,16 +20,16 @@
 
         const /** @type {!Array<App>}*/ appList = [
           app_management.FakePageHandler.createApp(
-              'ahfgeienlihckogmohjhadlkjgocpleb'),
+              'ahfgeienlihckogmohjhadlkjgocpleb', {title: 'Web Store'}),
           app_management.FakePageHandler.createApp(
               'aohghmighlieiainnegkcijnfilokake',
-              {type: apps.mojom.AppType.kExtension}),
+              {title: 'Docs', type: apps.mojom.AppType.kExtension}),
           app_management.FakePageHandler.createApp(
-              'blpcfgokakmgnkcojhhkbfbldkacnbeo'),
+              'blpcfgokakmgnkcojhhkbfbldkacnbeo', {title: 'Youtube'}),
           app_management.FakePageHandler.createApp(
-              'pjkljhegncpnkpknbcohdijeoejaedia'),
+              'pjkljhegncpnkpknbcohdijeoejaedia', {title: 'Gmail'}),
           app_management.FakePageHandler.createApp(
-              'aapocclcgogkmnckokdopfmhonfmgoek'),
+              'aapocclcgogkmnckokdopfmhonfmgoek', {title: 'Slide'}),
         ];
 
         this.handler.setApps(appList);
diff --git a/chrome/browser/resources/app_management/fake_page_handler.js b/chrome/browser/resources/app_management/fake_page_handler.js
index 2d62f96..b7b90aa4 100644
--- a/chrome/browser/resources/app_management/fake_page_handler.js
+++ b/chrome/browser/resources/app_management/fake_page_handler.js
@@ -13,8 +13,10 @@
      * @return {!Permission}
      */
     static createPermission(permissionId, optConfig) {
+      // Changing to kAllow to test notifications sublabel collapsibility, as it
+      // assumes all apps have notification permission.
       const permission = app_management.util.createPermission(
-          permissionId, PermissionValueType.kTriState, TriState.kBlock);
+          permissionId, PermissionValueType.kTriState, TriState.kAllow);
 
       if (optConfig) {
         Object.assign(permission, optConfig);
diff --git a/chrome/browser/resources/app_management/main_view.html b/chrome/browser/resources/app_management/main_view.html
index 96e7bf8..50347e4 100644
--- a/chrome/browser/resources/app_management/main_view.html
+++ b/chrome/browser/resources/app_management/main_view.html
@@ -80,7 +80,7 @@
         <template is="dom-repeat" items="[[collapsedApps_]]">
           <app-management-app-item app="[[item]]">
             <paper-icon-button-light slot="right-content"
-                class="subpage-arrow" actionable>
+                class="subpage-arrow app-management-item-arrow" actionable>
               <button></button>
             </paper-icon-button-light>
           </app-management-app-item>
diff --git a/chrome/browser/resources/app_management/main_view.js b/chrome/browser/resources/app_management/main_view.js
index 56b06c54..c1b75baf 100644
--- a/chrome/browser/resources/app_management/main_view.js
+++ b/chrome/browser/resources/app_management/main_view.js
@@ -58,9 +58,7 @@
   },
 
   attached: function() {
-    this.watch('apps_', function(state) {
-      return Object.values(state.apps);
-    });
+    this.watch('apps_', state => Object.values(state.apps));
     this.updateFromStore();
   },
 
@@ -94,7 +92,10 @@
     this.displayedApps_ = this.apps_.slice(0, NUMBER_OF_APPS_DISPLAYED_DEFAULT);
     this.collapsedApps_ =
         this.apps_.slice(NUMBER_OF_APPS_DISPLAYED_DEFAULT, this.apps_.length);
-    this.notificationApps_ = this.apps_.slice();
+
+    const [notificationsAllowed, notificationsBlocked] =
+        app_management.util.splitByNotificationPermission();
+    this.notificationApps_ = notificationsAllowed;
   },
 
   /**
@@ -157,7 +158,7 @@
     const textContainer = this.$['notifications-sublabel'];
     textContainer.textContent = '';
     for (const p of pieces) {
-      if (p.value.length == 0) {
+      if (!p.value || p.value.length == 0) {
         return;
       }
 
diff --git a/chrome/browser/resources/app_management/metadata_view.js b/chrome/browser/resources/app_management/metadata_view.js
index 8914ee5..93a9a4d 100644
--- a/chrome/browser/resources/app_management/metadata_view.js
+++ b/chrome/browser/resources/app_management/metadata_view.js
@@ -41,6 +41,7 @@
     return app.isPinned === OptionalBool.kTrue;
   },
 
+  /** @private */
   togglePinned_: function() {
     let newPinnedValue;
 
@@ -84,5 +85,4 @@
 
     return loadTimeData.getStringF('size', assert(app.size));
   },
-
 });
diff --git a/chrome/browser/resources/app_management/notifications_view.html b/chrome/browser/resources/app_management/notifications_view.html
index 8b748633..ae66705a 100644
--- a/chrome/browser/resources/app_management/notifications_view.html
+++ b/chrome/browser/resources/app_management/notifications_view.html
@@ -3,6 +3,7 @@
 <link rel="import" href="app_item.html">
 <link rel="import" href="shared_style.html">
 <link rel="import" href="store_client.html">
+<link rel="import" href="permission_toggle.html">
 <link rel="import" href="chrome://resources/html/icon.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
@@ -10,7 +11,7 @@
 <dom-module id="app-management-notifications-view">
   <template>
     <style include="app-management-shared-css">
-      cr-toggle {
+      app-management-permission-toggle {
         margin-inline-end: 24px;
       }
 
@@ -26,15 +27,17 @@
       }
 
       #notification-title {
-        padding-start: 12px;
+        padding-inline-start: 12px;
       }
     </style>
     <!-- TODO(ceciliani) Make this view a separate element to avoid duplicate
     code with main view -->
+    <!-- TODO(crbug.com/906508): Implement display when there is no apps at
+    all  -->
     <div id="notification-view-header">
       <paper-icon-button-light class="icon-arrow-back">
         <button id="closeButton" on-click="onClickBackButton_"
-        aria-label="$i18n{back}">
+            aria-label="$i18n{back}">
           <paper-ripple class="circle"></paper-ripple>
         </button>
       </paper-icon-button-light>
@@ -44,20 +47,27 @@
     <div class="card-container">
       <template is="dom-repeat" items="[[displayedApps_]]">
         <app-management-app-item app="[[item]]">
-          <cr-toggle slot="right-content"></cr-toggle>
+          <app-management-permission-toggle slot="right-content"
+              app="[[item]]"
+              permission-type="CONTENT_SETTINGS_TYPE_NOTIFICATIONS">
+          </app-management-permission-toggle>
         </app-management-app-item>
       </template>
 
       <iron-collapse opened="[[listExpanded_]]">
         <template is="dom-repeat" items="[[collapsedApps_]]">
           <app-management-app-item app="[[item]]">
-            <cr-toggle slot="right-content"></cr-toggle>
+            <app-management-permission-toggle slot="right-content"
+                app="[[item]]"
+                permission-type="CONTENT_SETTINGS_TYPE_NOTIFICATIONS">
+            </app-management-permission-toggle>
           </app-management-app-item>
         </template>
       </iron-collapse>
 
-      <div class="expander-list-row" on-click="toggleListExpanded_">
-        <span>[[moreAppsString_(apps_.length,listExpanded_)]]</span>
+      <div id="expander-row" class="expander-list-row"
+          on-click="toggleListExpanded_">
+        <span>[[moreAppsString_(listExpanded_, collapsedApps_)]]</span>
         <paper-icon-button-light class="expand-button">
           <button>
             <iron-icon icon="[[getCollapsedIcon_(listExpanded_)]]">
diff --git a/chrome/browser/resources/app_management/notifications_view.js b/chrome/browser/resources/app_management/notifications_view.js
index 7653db2..f1ebef8 100644
--- a/chrome/browser/resources/app_management/notifications_view.js
+++ b/chrome/browser/resources/app_management/notifications_view.js
@@ -11,35 +11,11 @@
 
   properties: {
     /**
-     * List of all apps.
-     * @private {Array<App>}
-     */
-    apps_: {
-      type: Array,
-      value: () => [],
-    },
-
-    /**
      * List of apps with notification permission
      * displayed before expanding the app list.
      * @private {Array<App>}
      */
-    displayedApps_: {
-      type: Array,
-      value: function() {
-        const /** @type {!Array<App>}*/ appList = [
-          app_management.FakePageHandler.createApp(
-              'blpcfgokakmgnkcojhhkbfbldkacnbeo'),
-          app_management.FakePageHandler.createApp(
-              'pjkljhegncpnkpknbcohdijeoejaedia'),
-          app_management.FakePageHandler.createApp(
-              'aapocclcgogkmnckokdopfmhonfmgoek'),
-          app_management.FakePageHandler.createApp(
-              'ahfgeienlihckogmohjhadlkjgocpleb'),
-        ];
-        return appList;
-      },
-    },
+    displayedApps_: Array,
 
     /**
      * List of apps without notification permission
@@ -48,14 +24,7 @@
      */
     collapsedApps_: {
       type: Array,
-      value: function() {
-        const /** @type {!Array<App>}*/ appList = [
-          app_management.FakePageHandler.createApp(
-              'aohghmighlieiainnegkcijnfilokake',
-              {type: apps.mojom.AppType.kArc}),
-        ];
-        return appList;
-      },
+      value: () => [],
     },
 
     /**
@@ -63,30 +32,53 @@
      */
     listExpanded_: {
       type: Boolean,
-      value: true,
+      value: false,
+    },
+
+    /**
+     * A boolean to identify whether current page is notifications view so that
+     * apps' list only get updated when page refreshed.
+     * @private {boolean}
+     */
+    notificationsViewSelected_: {
+      type: Boolean,
+      observer: 'onViewChanged_',
     },
   },
 
   attached: function() {
-    this.watch('apps_', (state) => {
-      return Object.values(state.apps);
+    this.watch('notificationsViewSelected_', (state) => {
+      return app_management.util.isNotificationsViewSelected(state);
     });
     this.updateFromStore();
   },
 
   /**
-   * @param {number} numApps
+   * If all the apps have / don't have notification permission, display the
+   * whole list, else display those with notification permission before
+   * expanding.
+   * @private
+   */
+  onViewChanged_: function() {
+    [this.displayedApps_, this.collapsedApps_] =
+        app_management.util.splitByNotificationPermission();
+    if (this.displayedApps_.length === 0) {
+      this.displayedApps_ = this.collapsedApps_;
+      this.collapsedApps_ = [];
+    }
+    this.$['expander-row'].hidden = this.collapsedApps_.length === 0;
+  },
+
+  /**
    * @param {boolean} listExpanded
+   * @param {Array<App>} collapsedApps
    * @return {string}
    * @private
    */
-  moreAppsString_: function(numApps, listExpanded) {
-    // TODO(ceciliani) Make view display apps with notification permission
-    // first.
+  moreAppsString_: function(listExpanded, collapsedApps) {
     return listExpanded ?
         loadTimeData.getString('lessApps') :
-        loadTimeData.getStringF(
-            'moreApps', numApps - NUMBER_OF_APPS_DISPLAYED_DEFAULT);
+        loadTimeData.getStringF('moreApps', collapsedApps.length);
   },
 
   /**
@@ -98,6 +90,7 @@
 
   /** @private */
   onClickBackButton_: function() {
+    this.listExpanded_ = false;
     if (!window.history.state) {
       this.dispatch(app_management.actions.changePage(PageType.MAIN));
     } else {
@@ -113,4 +106,17 @@
   getCollapsedIcon_: function(listExpanded) {
     return listExpanded ? 'cr:expand-less' : 'cr:expand-more';
   },
+
+  /**
+   * TODO(rekanorman) Make this function work for other apps.
+   * Returns a boolean representation of the permission value, which used to
+   * determine the position of the permission toggle.
+   * @param {App} app
+   * @return {boolean}
+   * @private
+   */
+  getNotificationValueBool_: function(app) {
+    return app_management.util.getPermissionValueBool(
+        app, 'CONTENT_SETTINGS_TYPE_NOTIFICATIONS');
+  },
 });
diff --git a/chrome/browser/resources/app_management/permission_item.html b/chrome/browser/resources/app_management/permission_item.html
index ab33c29..3b41326 100644
--- a/chrome/browser/resources/app_management/permission_item.html
+++ b/chrome/browser/resources/app_management/permission_item.html
@@ -1,6 +1,7 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="shared_style.html">
+<link rel="import" href="permission_toggle.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
 
@@ -26,10 +27,9 @@
       </template>
       <div id="permission-label">[[permissionLabel]]</div>
     </div>
-    <cr-toggle
-        checked="[[permissionValueBool_(permissionValueType_, permissionValue_)]]"
-        on-change="togglePermission_">
-    </cr-toggle>
+    <app-management-permission-toggle
+        app="[[app_]]" permission-type="[[permissionType]]">
+    </app-management-permission-toggle>
   </template>
   <script src="permission_item.js"></script>
 </dom-module>
diff --git a/chrome/browser/resources/app_management/permission_item.js b/chrome/browser/resources/app_management/permission_item.js
index 1a885f5..ae87966 100644
--- a/chrome/browser/resources/app_management/permission_item.js
+++ b/chrome/browser/resources/app_management/permission_item.js
@@ -27,24 +27,6 @@
      */
     app_: Object,
 
-    /**
-     * @private {PermissionValueType}
-     */
-    permissionValueType_: {
-      type: Number,
-      computed: 'getPermissionValueType_(app_)',
-    },
-
-    /**
-     * The semantics of permissionValue_ depend on permissionValueType_,
-     * see chrome/services/app_service/public/mojom/types.mojom
-     * @private {?number}
-     */
-    permissionValue_: {
-      type: Number,
-      computed: 'getPermissionValue_(app_, permissionType)',
-    },
-
     /** @type {string} */
     icon: String,
   },
@@ -53,128 +35,4 @@
     this.watch('app_', state => app_management.util.getSelectedApp(state));
     this.updateFromStore();
   },
-
-  /**
-   * @param {App} app
-   * @return {number}
-   * @private
-   */
-  getPermissionValueType_: function(app) {
-    // TODO(rekanorman): Change to a suitable conditional statement once the
-    // PermissionValueType corresponding to each AppType is known.
-    return PermissionValueType.kTriState;
-  },
-
-  /**
-   * @param {App} app
-   * @param {string} permissionType
-   * @return {?number}
-   * @private
-   */
-  getPermissionValue_: function(app, permissionType) {
-    if (!app) {
-      return null;
-    }
-
-    // TODO(rekanorman): Remove once permissions are implemented for all
-    // app types.
-    if (Object.keys(app.permissions).length === 0) {
-      return null;
-    }
-
-    // TODO(rekanorman): Make generic once permissions are implemented for
-    // other app types.
-    return app.permissions[PwaPermissionType[permissionType]].value;
-  },
-
-  /**
-   * Returns a boolean representation of the permission value, which used to
-   * determine the position of the permission toggle.
-   * @param {PermissionValueType} permissionValueType
-   * @param {number} permissionValue
-   * @return {boolean}
-   * @private
-   */
-  permissionValueBool_: function(permissionValueType, permissionValue) {
-    return (permissionValueType === PermissionValueType.kBool &&
-            permissionValue === Bool.kTrue) ||
-        (permissionValueType === PermissionValueType.kTriState &&
-         permissionValue === TriState.kAllow);
-  },
-
-  /**
-   * @private
-   */
-  togglePermission_: function() {
-    /** @type {!Permission} */
-    let newPermission;
-
-    switch (this.permissionValueType_) {
-      case PermissionValueType.kBool:
-        newPermission = this.getNewPermissionBoolean_();
-        break;
-      case PermissionValueType.kTriState:
-        newPermission = this.getNewPermissionTriState_();
-        break;
-      default:
-        assertNotReached();
-    }
-
-    app_management.BrowserProxy.getInstance().handler.setPermission(
-        this.app_.id, newPermission);
-  },
-
-  /**
-   * @private
-   * @return {!Permission}
-   */
-  getNewPermissionBoolean_: function() {
-    /** @type {number} */
-    let newPermissionValue;
-
-    switch (this.permissionValue_) {
-      case Bool.kFalse:
-        newPermissionValue = Bool.kTrue;
-        break;
-      case Bool.kTrue:
-        newPermissionValue = Bool.kFalse;
-        break;
-      default:
-        assertNotReached();
-    }
-
-    return app_management.util.createPermission(
-        PwaPermissionType[this.permissionType], PermissionValueType.kBool,
-        newPermissionValue);
-  },
-
-  /**
-   * @private
-   * @return {!Permission}
-   */
-  getNewPermissionTriState_: function() {
-    let newPermissionValue;
-
-    switch (this.permissionValue_) {
-      case TriState.kBlock:
-        newPermissionValue = TriState.kAllow;
-        break;
-      case TriState.kAsk:
-        newPermissionValue = TriState.kAllow;
-        break;
-      case TriState.kAllow:
-        // TODO(rekanorman): Eventually TriState.kAsk, but currently changing a
-        // permission to kAsk then opening the site settings page for the app
-        // produces the error:
-        // "Only extensions or enterprise policy can change the setting to ASK."
-        newPermissionValue = TriState.kBlock;
-        break;
-      default:
-        assertNotReached();
-    }
-
-    return app_management.util.createPermission(
-        PwaPermissionType[this.permissionType], PermissionValueType.kTriState,
-        newPermissionValue);
-  },
 });
diff --git a/chrome/browser/resources/app_management/permission_toggle.html b/chrome/browser/resources/app_management/permission_toggle.html
new file mode 100644
index 0000000..c7a3017
--- /dev/null
+++ b/chrome/browser/resources/app_management/permission_toggle.html
@@ -0,0 +1,12 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
+
+<dom-module id="app-management-permission-toggle">
+  <template>
+    <cr-toggle checked="[[getPermissionValueBool_(app, permissionType)]]"
+        on-change="togglePermission_">
+    </cr-toggle>
+  </template>
+  <script src="permission_toggle.js"></script>
+</dom-module>
diff --git a/chrome/browser/resources/app_management/permission_toggle.js b/chrome/browser/resources/app_management/permission_toggle.js
new file mode 100644
index 0000000..826b3a8
--- /dev/null
+++ b/chrome/browser/resources/app_management/permission_toggle.js
@@ -0,0 +1,113 @@
+// 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.
+Polymer({
+  is: 'app-management-permission-toggle',
+
+  properties: {
+    /**
+     * @type {App}
+     */
+    app: Object,
+
+    /**
+     * TODO(rekanorman) Change the annotation when app type can be correctly
+     * sent.
+     * A string version of the permission type, corresponding to a value of
+     * the PwaPermissionType enum.
+     * @type {string}
+     */
+    permissionType: String,
+  },
+
+  /**
+   * @param {String} appId
+   * @param {string} permissionType
+   * @return {boolean}
+   */
+  getPermissionValueBool_: function(app, permissionType) {
+    return app_management.util.getPermissionValueBool(app, permissionType);
+  },
+
+  /**
+   * @private
+   */
+  togglePermission_: function() {
+    /** @type {!Permission} */
+    let newPermission;
+
+    switch (app_management.util.getPermissionValueType(this.app)) {
+      case PermissionValueType.kBool:
+        newPermission =
+            this.getNewPermissionBoolean_(this.app, this.permissionType);
+        break;
+      case PermissionValueType.kTriState:
+        newPermission =
+            this.getNewPermissionTriState_(this.app, this.permissionType);
+        break;
+      default:
+        assertNotReached();
+    }
+
+    app_management.BrowserProxy.getInstance().handler.setPermission(
+        this.app.id, newPermission);
+  },
+
+  /**
+   * @private
+   * @param {App} app
+   * @param {string} permissionType
+   * @return {!Permission}
+   */
+  getNewPermissionBoolean_: function(app, permissionType) {
+    /** @type {number} */
+    let newPermissionValue;
+
+    switch (app_management.util.getPermissionValue(app, permissionType)) {
+      case Bool.kFalse:
+        newPermissionValue = Bool.kTrue;
+        break;
+      case Bool.kTrue:
+        newPermissionValue = Bool.kFalse;
+        break;
+      default:
+        assertNotReached();
+    }
+
+    return app_management.util.createPermission(
+        PwaPermissionType[permissionType], PermissionValueType.kBool,
+        newPermissionValue);
+  },
+
+  /**
+   * @param {App} app
+   * @param {string} permissionType
+   * @private
+   * @return {!Permission}
+   */
+  getNewPermissionTriState_: function(app, permissionType) {
+    let newPermissionValue;
+
+    switch (app_management.util.getPermissionValue(app, permissionType)) {
+      case TriState.kBlock:
+        newPermissionValue = TriState.kAllow;
+        break;
+      case TriState.kAsk:
+        newPermissionValue = TriState.kAllow;
+        break;
+      case TriState.kAllow:
+        // TODO(rekanorman): Eventually TriState.kAsk, but currently changing a
+        // permission to kAsk then opening the site settings page for the app
+        // produces the error:
+        // "Only extensions or enterprise policy can change the setting to ASK."
+        newPermissionValue = TriState.kBlock;
+        break;
+      default:
+        assertNotReached();
+    }
+
+    return app_management.util.createPermission(
+        PwaPermissionType[permissionType], PermissionValueType.kTriState,
+        newPermissionValue);
+  },
+});
diff --git a/chrome/browser/resources/app_management/util.js b/chrome/browser/resources/app_management/util.js
index c7a43c97..c30185d 100644
--- a/chrome/browser/resources/app_management/util.js
+++ b/chrome/browser/resources/app_management/util.js
@@ -16,7 +16,7 @@
       currentPage: {
         pageType: PageType.MAIN,
         selectedAppId: null,
-      }
+      },
     };
   }
 
@@ -57,6 +57,91 @@
   }
 
   /**
+   * Returns true if the current page is notification view.
+   * @param {AppManagementPageState} state
+   * @return {boolean}
+   */
+  function isNotificationsViewSelected(state) {
+    return state.currentPage.pageType === PageType.NOTIFICATIONS;
+  }
+
+  /**
+   * @return {!Array<Array<App>>}
+   */
+  function splitByNotificationPermission() {
+    const apps = Object.values(app_management.Store.getInstance().data.apps);
+    const notificationsAllowed = [];
+    const notificationsBlocked = [];
+    for (const app of apps) {
+      // Chrome app's notification permission cannot be changed and therefore
+      // are not included in notification view
+      if (app.type === AppType.kExtension) {
+        continue;
+      }
+      // TODO(rekanorman) Branch on different app type in the future.
+      if (getPermissionValueBool(app, 'CONTENT_SETTINGS_TYPE_NOTIFICATIONS')) {
+        notificationsAllowed.push(app);
+      } else {
+        notificationsBlocked.push(app);
+      }
+    }
+    return [notificationsAllowed, notificationsBlocked];
+  }
+
+  /**
+   * @param {App} app
+   * @param {string} permissionType
+   * @return {boolean}
+   */
+  function getPermissionValueBool(app, permissionType) {
+    if (getPermissionValueType(app) === PermissionValueType.kBool &&
+        getPermissionValue(app, permissionType) === Bool.kTrue) {
+      return true;
+    }
+    if (getPermissionValueType(app) === PermissionValueType.kTriState &&
+        getPermissionValue(app, permissionType) === TriState.kAllow) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * @param {App} app
+   * @return {number}
+   * @private
+   */
+  function getPermissionValueType(app) {
+    // TODO(rekanorman): Change to a suitable conditional statement once the
+    // PermissionValueType corresponding to each AppType is known.
+    return PermissionValueType.kTriState;
+  }
+
+
+  /**
+   * @param {App} app
+   * @param {string} permissionType
+   * @return {?number}
+   * @private
+   */
+  function getPermissionValue(app, permissionType) {
+    if (!app) {
+      return null;
+    }
+
+    // TODO(rekanorman): Remove once permissions are implemented for all
+    // app types.
+    if (Object.keys(app.permissions).length === 0) {
+      return null;
+    }
+
+    // TODO(rekanorman): Make generic once permissions are implemented for
+    // other app types.
+    return /** @type Object<number, Permission> */ (
+               app.permissions)[PwaPermissionType[permissionType]]
+        .value;
+  }
+
+  /**
    * If there is no selected app, returns undefined so that Polymer bindings
    * won't be updated.
    * @param {AppManagementPageState} state
@@ -74,6 +159,11 @@
     createInitialState: createInitialState,
     createPermission: createPermission,
     getAppIcon: getAppIcon,
+    getPermissionValue: getPermissionValue,
+    getPermissionValueBool: getPermissionValueBool,
+    getPermissionValueType: getPermissionValueType,
     getSelectedApp: getSelectedApp,
+    isNotificationsViewSelected: isNotificationsViewSelected,
+    splitByNotificationPermission: splitByNotificationPermission,
   };
 });
diff --git a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html
index 85072d8..452cef16 100644
--- a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html
+++ b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html
@@ -79,7 +79,9 @@
     <!-- Title section: Icon + name + connection state. -->
     <div class="section first">
       <div class="start layout horizontal center">
-        <cr-network-icon network-state="[[networkProperties]]">
+        <cr-network-icon
+            show-technology-badge="[[showTechnologyBadge_]]"
+            network-state="[[networkProperties]]">
         </cr-network-icon>
         <div id="networkName" class="title">
           [[getNameText_(networkProperties)]]
diff --git a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
index 1a2c34e6..5ecdaa20 100644
--- a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
+++ b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
@@ -30,6 +30,18 @@
       type: Object,
       value: chrome.networkingPrivate,
     },
+
+    /**
+     * Whether to show technology badge on mobile network icons.
+     * @private
+     */
+    showTechnologyBadge_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.valueExists('showTechnologyBadge') &&
+            loadTimeData.getBoolean('showTechnologyBadge');
+      }
+    },
   },
 
   /**
diff --git a/chrome/browser/resources/chromeos/login/network_select_login.html b/chrome/browser/resources/chromeos/login/network_select_login.html
index f9fc9257..a6fc8296 100644
--- a/chrome/browser/resources/chromeos/login/network_select_login.html
+++ b/chrome/browser/resources/chromeos/login/network_select_login.html
@@ -14,15 +14,15 @@
     }
   </style>
   <template>
-    <cr-network-select id="networkSelect"
+    <cr-network-select id="networkSelect" class="focus-on-show"
+        custom-items="[[getNetworkCustomItems_(isConnected)]]"
         on-default-network-changed="onDefaultNetworkChanged_"
         on-network-connect-changed="onNetworkConnectChanged_"
         on-network-list-changed="onNetworkListChanged_"
         on-network-item-selected="onNetworkListNetworkItemSelected_"
         on-custom-item-selected="onNetworkListCustomItemSelected_"
-        custom-items="[[getNetworkCustomItems_(isConnected)]]"
         no-bottom-scroll-border="[[noBottomScrollBorder]]"
-        class="focus-on-show">
+        show-technology-badge="[[showTechnologyBadge_]]">
     </cr-network-select>
   </template>
 </dom-module>
diff --git a/chrome/browser/resources/chromeos/login/network_select_login.js b/chrome/browser/resources/chromeos/login/network_select_login.js
index 2c0cd8a..329152a1 100644
--- a/chrome/browser/resources/chromeos/login/network_select_login.js
+++ b/chrome/browser/resources/chromeos/login/network_select_login.js
@@ -39,6 +39,18 @@
       type: Boolean,
       value: false,
     },
+
+    /**
+     * Whether to show technology badge on mobile network icons.
+     * @private
+     */
+    showTechnologyBadge_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.valueExists('showTechnologyBadge') &&
+            loadTimeData.getBoolean('showTechnologyBadge');
+      }
+    }
   },
 
   /**
diff --git a/chrome/browser/resources/settings/internet_page/internet_detail_page.html b/chrome/browser/resources/settings/internet_page/internet_detail_page.html
index 82b0c6e..a9ed2ea0 100644
--- a/chrome/browser/resources/settings/internet_page/internet_detail_page.html
+++ b/chrome/browser/resources/settings/internet_page/internet_detail_page.html
@@ -68,7 +68,9 @@
     <!-- Title section: Icon + name + connection state. -->
     <div id="titleDiv" class="settings-box first">
       <div class="start layout horizontal center">
-        <cr-network-icon network-state="[[networkProperties]]">
+        <cr-network-icon
+            show-technology-badge="[[showTechnologyBadge_]]"
+            network-state="[[networkProperties]]">
         </cr-network-icon>
         <div id="networkState" class="title settings-box-text"
             connected$="[[isConnectedState_(networkProperties)]]"
diff --git a/chrome/browser/resources/settings/internet_page/internet_detail_page.js b/chrome/browser/resources/settings/internet_page/internet_detail_page.js
index 3ef1fd6..3af1e32 100644
--- a/chrome/browser/resources/settings/internet_page/internet_detail_page.js
+++ b/chrome/browser/resources/settings/internet_page/internet_detail_page.js
@@ -143,6 +143,18 @@
       value: '',
     },
 
+    /**
+     * Whether to show technology badge on mobile network icons.
+     * @private
+     */
+    showTechnologyBadge_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.valueExists('showTechnologyBadge') &&
+            loadTimeData.getBoolean('showTechnologyBadge');
+      }
+    },
+
     /** @private */
     advancedExpanded_: Boolean,
 
diff --git a/chrome/browser/resources/settings/internet_page/internet_subpage.html b/chrome/browser/resources/settings/internet_page/internet_subpage.html
index 23c8468..ce1d5ac 100644
--- a/chrome/browser/resources/settings/internet_page/internet_subpage.html
+++ b/chrome/browser/resources/settings/internet_page/internet_subpage.html
@@ -125,6 +125,7 @@
         <template is="dom-if"
             if="[[shouldShowNetworkList_(networkStateList_)]]">
           <cr-network-list id="networkList" show-buttons
+              show-technology-badge="[[showTechnologyBadge_]]"
               networks="[[networkStateList_]]"
               on-selected="onNetworkSelected_">
           </cr-network-list>
diff --git a/chrome/browser/resources/settings/internet_page/internet_subpage.js b/chrome/browser/resources/settings/internet_page/internet_subpage.js
index e74c0f8..4ca6059 100644
--- a/chrome/browser/resources/settings/internet_page/internet_subpage.js
+++ b/chrome/browser/resources/settings/internet_page/internet_subpage.js
@@ -111,6 +111,18 @@
         return [];
       },
     },
+
+    /**
+     * Whether to show technology badge on mobile network icons.
+     * @private
+     */
+    showTechnologyBadge_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.valueExists('showTechnologyBadge') &&
+            loadTimeData.getBoolean('showTechnologyBadge');
+      }
+    },
   },
 
   listeners: {
diff --git a/chrome/browser/resources/settings/internet_page/network_summary_item.html b/chrome/browser/resources/settings/internet_page/network_summary_item.html
index da42463..1dbb066 100644
--- a/chrome/browser/resources/settings/internet_page/network_summary_item.html
+++ b/chrome/browser/resources/settings/internet_page/network_summary_item.html
@@ -45,7 +45,9 @@
       <div actionable class="flex layout horizontal center"
           on-click="onShowDetailsTap_">
         <div id="details" no-flex$="[[showSimInfo_(deviceState)]]">
-          <cr-network-icon network-state="[[activeNetworkState]]"
+          <cr-network-icon
+              show-technology-badge="[[showTechnologyBadge_]]"
+              network-state="[[activeNetworkState]]"
               device-state="[[deviceState]]">
           </cr-network-icon>
           <div class="flex settings-box-text">
diff --git a/chrome/browser/resources/settings/internet_page/network_summary_item.js b/chrome/browser/resources/settings/internet_page/network_summary_item.js
index 4720c74..4821799 100644
--- a/chrome/browser/resources/settings/internet_page/network_summary_item.js
+++ b/chrome/browser/resources/settings/internet_page/network_summary_item.js
@@ -59,6 +59,18 @@
      * @type {string|undefined}
      */
     networkTitleText: String,
+
+    /**
+     * Whether to show technology badge on mobile network icon.
+     * @private
+     */
+    showTechnologyBadge_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.valueExists('showTechnologyBadge') &&
+            loadTimeData.getBoolean('showTechnologyBadge');
+      }
+    },
   },
 
   /**
diff --git a/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.html b/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.html
index 6f6e258..42431eb 100644
--- a/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.html
+++ b/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.html
@@ -19,6 +19,7 @@
         subpage-route-url-search-params=
             "[[getTetherNetworkUrlSearchParams_()]]">
       <cr-network-icon slot="icon"
+          show-technology-badge="[[showTechnologyBadge_]]"
           network-state="[[activeNetworkState_]]"
           device-state="[[deviceState_]]">
       </cr-network-icon>
diff --git a/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.js b/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.js
index aef05a82..a5c84407 100644
--- a/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.js
+++ b/chrome/browser/resources/settings/multidevice_page/multidevice_tether_item.js
@@ -48,6 +48,18 @@
       type: Object,
       value: settings.routes,
     },
+
+    /**
+     * Whether to show technology badge on mobile network icon.
+     * @private
+     */
+    showTechnologyBadge_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.valueExists('showTechnologyBadge') &&
+            loadTimeData.getBoolean('showTechnologyBadge');
+      }
+    },
   },
 
   listeners: {
diff --git a/chrome/browser/ssl/ssl_client_auth_metrics.cc b/chrome/browser/ssl/ssl_client_auth_metrics.cc
new file mode 100644
index 0000000..8e01da0f
--- /dev/null
+++ b/chrome/browser/ssl/ssl_client_auth_metrics.cc
@@ -0,0 +1,11 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ssl/ssl_client_auth_metrics.h"
+
+#include "base/metrics/histogram_macros.h"
+
+void LogClientAuthResult(ClientCertSelectionResult result) {
+  UMA_HISTOGRAM_ENUMERATION(kClientCertSelectHistogramName, result);
+}
diff --git a/chrome/browser/ssl/ssl_client_auth_metrics.h b/chrome/browser/ssl/ssl_client_auth_metrics.h
new file mode 100644
index 0000000..b2d8a6d
--- /dev/null
+++ b/chrome/browser/ssl/ssl_client_auth_metrics.h
@@ -0,0 +1,25 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SSL_SSL_CLIENT_AUTH_METRICS_H_
+#define CHROME_BROWSER_SSL_SSL_CLIENT_AUTH_METRICS_H_
+
+const char kClientCertSelectHistogramName[] =
+    "Security.ClientAuth.CertificateSelectionSource";
+
+// These values are logged to UMA. Entries should not be renumbered and numeric
+// values should never be reused. Please keep in sync with
+// "ClientCertSelectionResult" in src/tools/metrics/histograms/enums.xml.
+enum class ClientCertSelectionResult {
+  kUserSelect = 0,
+  kUserCancel = 1,
+  kUserCloseTab = 2,
+  kAutoSelect = 3,
+  kNoSelectionAllowed = 4,
+  kMaxValue = kNoSelectionAllowed,
+};
+
+void LogClientAuthResult(ClientCertSelectionResult result);
+
+#endif  // CHROME_BROWSER_SSL_SSL_CLIENT_AUTH_METRICS_H_
diff --git a/chrome/browser/ssl/ssl_client_auth_observer.cc b/chrome/browser/ssl/ssl_client_auth_observer.cc
index 21a7b22..97b5424 100644
--- a/chrome/browser/ssl/ssl_client_auth_observer.cc
+++ b/chrome/browser/ssl/ssl_client_auth_observer.cc
@@ -6,6 +6,7 @@
 
 #include "base/logging.h"
 #include "base/no_destructor.h"
+#include "chrome/browser/ssl/ssl_client_auth_metrics.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/client_certificate_delegate.h"
 #include "net/cert/x509_certificate.h"
@@ -31,6 +32,14 @@
   if (!delegate_)
     return;
 
+  // CertificateSelected is called with a valid delegate any time that the
+  // selector is explicitly closed by the user. If the user closes the entire
+  // tab, CancelCertificateSelection() is called first, removing the delegate.
+  if (certificate)
+    LogClientAuthResult(ClientCertSelectionResult::kUserSelect);
+  else
+    LogClientAuthResult(ClientCertSelectionResult::kUserCancel);
+
   // Stop listening now that the delegate has been resolved. This is also to
   // avoid getting a self-notification.
   StopObserving();
@@ -51,6 +60,10 @@
   if (!delegate_)
     return;
 
+  // This code is only reached when the selector's tab is closed-- cancelling
+  // the selection box calls CertificateSelected(nullptr, nullptr) first.
+  LogClientAuthResult(ClientCertSelectionResult::kUserCloseTab);
+
   // Stop observing now that the delegate has been resolved.
   StopObserving();
   delegate_.reset();
diff --git a/chrome/browser/ui/ash/assistant/assistant_client.cc b/chrome/browser/ui/ash/assistant/assistant_client.cc
index cc3f130..492d1c4 100644
--- a/chrome/browser/ui/ash/assistant/assistant_client.cc
+++ b/chrome/browser/ui/ash/assistant/assistant_client.cc
@@ -66,6 +66,10 @@
   assistant_setup_ = std::make_unique<AssistantSetup>(connector);
 }
 
+void AssistantClient::MaybeStartAssistantOptInFlow() {
+  assistant_setup_->MaybeStartAssistantOptInFlow();
+}
+
 void AssistantClient::OnAssistantStatusChanged(bool running) {
   // |running| means assistent mojom service is running. This maps to
   // |STOPPED| and |NOT_READY|. |RUNNING| maps to UI is shown and an assistant
diff --git a/chrome/browser/ui/ash/assistant/assistant_client.h b/chrome/browser/ui/ash/assistant/assistant_client.h
index a3ac07e..eef5571 100644
--- a/chrome/browser/ui/ash/assistant/assistant_client.h
+++ b/chrome/browser/ui/ash/assistant/assistant_client.h
@@ -25,6 +25,7 @@
   ~AssistantClient() override;
 
   void MaybeInit(Profile* profile);
+  void MaybeStartAssistantOptInFlow();
 
   // assistant::mojom::Client overrides:
   void OnAssistantStatusChanged(bool running) override;
diff --git a/chrome/browser/ui/ash/assistant/assistant_setup.cc b/chrome/browser/ui/ash/assistant/assistant_setup.cc
index 719bdcc..68f5ed5 100644
--- a/chrome/browser/ui/ash/assistant/assistant_setup.cc
+++ b/chrome/browser/ui/ash/assistant/assistant_setup.cc
@@ -12,8 +12,10 @@
 #include "ash/public/interfaces/assistant_controller.mojom.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/notifications/notification_display_service.h"
 #include "chrome/browser/profiles/profile.h"
@@ -82,7 +84,7 @@
 }  // namespace
 
 AssistantSetup::AssistantSetup(service_manager::Connector* connector)
-    : connector_(connector), binding_(this) {
+    : connector_(connector), binding_(this), weak_factory_(this) {
   // Bind to the AssistantSetupController in ash.
   ash::mojom::AssistantSetupControllerPtr setup_controller;
   connector_->BindInterface(ash::mojom::kServiceName, &setup_controller);
@@ -100,8 +102,10 @@
 void AssistantSetup::StartAssistantOptInFlow(
     ash::mojom::FlowType type,
     StartAssistantOptInFlowCallback callback) {
-  if (chromeos::AssistantOptInDialog::IsActive())
+  if (chromeos::AssistantOptInDialog::IsActive()) {
+    std::move(callback).Run(false);
     return;
+  }
 
   chromeos::AssistantOptInDialog::Show(type, std::move(callback));
 }
@@ -210,3 +214,16 @@
       LOG(ERROR) << "Invalid activity control consent status.";
   }
 }
+
+void AssistantSetup::MaybeStartAssistantOptInFlow() {
+  auto* pref_service = ProfileManager::GetActiveUserProfile()->GetPrefs();
+  DCHECK(pref_service);
+  if (!pref_service->GetUserPrefValue(
+          arc::prefs::kVoiceInteractionActivityControlAccepted)) {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(&AssistantSetup::StartAssistantOptInFlow,
+                                  weak_factory_.GetWeakPtr(),
+                                  ash::mojom::FlowType::CONSENT_FLOW,
+                                  base::DoNothing::Once<bool>()));
+  }
+}
diff --git a/chrome/browser/ui/ash/assistant/assistant_setup.h b/chrome/browser/ui/ash/assistant/assistant_setup.h
index a19653a..1a16670b 100644
--- a/chrome/browser/ui/ash/assistant/assistant_setup.h
+++ b/chrome/browser/ui/ash/assistant/assistant_setup.h
@@ -7,6 +7,7 @@
 
 #include "ash/public/interfaces/assistant_setup.mojom.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client.h"
 #include "chromeos/services/assistant/public/mojom/settings.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
@@ -27,6 +28,10 @@
       ash::mojom::FlowType type,
       StartAssistantOptInFlowCallback callback) override;
 
+  // If prefs::kVoiceInteractionActivityControlAccepted is nullptr, means the
+  // pref is not set by user. Therefore we need to start OOBE.
+  void MaybeStartAssistantOptInFlow();
+
  private:
   // arc::VoiceInteractionControllerClient::Observer overrides
   void OnStateChanged(ash::mojom::VoiceInteractionState state) override;
@@ -38,6 +43,8 @@
   chromeos::assistant::mojom::AssistantSettingsManagerPtr settings_manager_;
   mojo::Binding<ash::mojom::AssistantSetup> binding_;
 
+  base::WeakPtrFactory<AssistantSetup> weak_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(AssistantSetup);
 };
 
diff --git a/chrome/browser/ui/ash/session_controller_client.cc b/chrome/browser/ui/ash/session_controller_client.cc
index c779638..e62c500 100644
--- a/chrome/browser/ui/ash/session_controller_client.cc
+++ b/chrome/browser/ui/ash/session_controller_client.cc
@@ -472,9 +472,11 @@
 
 #if BUILDFLAG(ENABLE_CROS_ASSISTANT)
     // Assistant is initialized only once when primary user logs in.
+    // Initialize Assistant when browser process restarts.
     if (chromeos::switches::IsAssistantEnabled()) {
       AssistantClient::Get()->MaybeInit(
           ProfileManager::GetPrimaryUserProfile());
+      AssistantClient::Get()->MaybeStartAssistantOptInFlow();
     }
 #endif
   }
diff --git a/chrome/browser/ui/libgtkui/BUILD.gn b/chrome/browser/ui/libgtkui/BUILD.gn
index 3a53a45..407f90ce 100644
--- a/chrome/browser/ui/libgtkui/BUILD.gn
+++ b/chrome/browser/ui/libgtkui/BUILD.gn
@@ -5,10 +5,11 @@
 assert(is_linux, "This file should only be referenced on Linux")
 
 import("//build/config/features.gni")
+import("//build/config/jumbo.gni")
 import("//build/config/linux/gtk/gtk.gni")
 import("//printing/buildflags/buildflags.gni")
 
-component("libgtkui") {
+jumbo_component("libgtkui") {
   sources = [
     "app_indicator_icon.cc",
     "app_indicator_icon.h",
diff --git a/chrome/browser/ui/libgtkui/app_indicator_icon.cc b/chrome/browser/ui/libgtkui/app_indicator_icon.cc
index 266ae5d..3e1aa95 100644
--- a/chrome/browser/ui/libgtkui/app_indicator_icon.cc
+++ b/chrome/browser/ui/libgtkui/app_indicator_icon.cc
@@ -83,7 +83,7 @@
 app_indicator_set_icon_theme_path_func app_indicator_set_icon_theme_path =
     nullptr;
 
-void EnsureMethodsLoaded() {
+void EnsureLibAppIndicatorLoaded() {
   if (g_attempted_load)
     return;
 
@@ -160,7 +160,7 @@
   std::unique_ptr<base::Environment> env(base::Environment::Create());
   desktop_env_ = base::nix::GetDesktopEnvironment(env.get());
 
-  EnsureMethodsLoaded();
+  EnsureLibAppIndicatorLoaded();
   tool_tip_ = base::UTF16ToUTF8(tool_tip);
   SetImage(image);
 }
@@ -176,7 +176,7 @@
 
 // static
 bool AppIndicatorIcon::CouldOpen() {
-  EnsureMethodsLoaded();
+  EnsureLibAppIndicatorLoaded();
   return g_opened;
 }
 
diff --git a/chrome/browser/ui/libgtkui/gtk_util.cc b/chrome/browser/ui/libgtkui/gtk_util.cc
index 42b8ac9..bb6102e 100644
--- a/chrome/browser/ui/libgtkui/gtk_util.cc
+++ b/chrome/browser/ui/libgtkui/gtk_util.cc
@@ -589,4 +589,15 @@
   return surface.GetAveragePixelValue(false);
 }
 
+std::string GetGtkSettingsStringProperty(GtkSettings* settings,
+                                         const gchar* prop_name) {
+  GValue layout = G_VALUE_INIT;
+  g_value_init(&layout, G_TYPE_STRING);
+  g_object_get_property(G_OBJECT(settings), prop_name, &layout);
+  DCHECK(G_VALUE_HOLDS_STRING(&layout));
+  std::string prop_value(g_value_get_string(&layout));
+  g_value_unset(&layout);
+  return prop_value;
+}
+
 }  // namespace libgtkui
diff --git a/chrome/browser/ui/libgtkui/gtk_util.h b/chrome/browser/ui/libgtkui/gtk_util.h
index 32b0a39..adea4a8 100644
--- a/chrome/browser/ui/libgtkui/gtk_util.h
+++ b/chrome/browser/ui/libgtkui/gtk_util.h
@@ -184,6 +184,9 @@
 // Get the color of the GtkSeparator specified by |css_selector|.
 SkColor GetSeparatorColor(const std::string& css_selector);
 
+// Get a GtkSettings property as a C++ string.
+std::string GetGtkSettingsStringProperty(GtkSettings* settings,
+                                         const gchar* prop_name);
 }  // namespace libgtkui
 
 #endif  // CHROME_BROWSER_UI_LIBGTKUI_GTK_UTIL_H_
diff --git a/chrome/browser/ui/libgtkui/native_theme_gtk.cc b/chrome/browser/ui/libgtkui/native_theme_gtk.cc
index ae8d88d..4e6196d 100644
--- a/chrome/browser/ui/libgtkui/native_theme_gtk.cc
+++ b/chrome/browser/ui/libgtkui/native_theme_gtk.cc
@@ -25,17 +25,6 @@
   BG_RENDER_RECURSIVE,
 };
 
-std::string GetGtkSettingsStringProperty(GtkSettings* settings,
-                                         const gchar* prop_name) {
-  GValue layout = G_VALUE_INIT;
-  g_value_init(&layout, G_TYPE_STRING);
-  g_object_get_property(G_OBJECT(settings), prop_name, &layout);
-  DCHECK(G_VALUE_HOLDS_STRING(&layout));
-  std::string prop_value(g_value_get_string(&layout));
-  g_value_unset(&layout);
-  return prop_value;
-}
-
 ScopedStyleContext GetTooltipContext() {
   return AppendCssNodeToStyleContext(
       nullptr, GtkVersionCheck(3, 20) ? "#tooltip.background"
diff --git a/chrome/browser/ui/libgtkui/settings_provider_gtk.cc b/chrome/browser/ui/libgtkui/settings_provider_gtk.cc
index 28ba3fb8..3b95bc9 100644
--- a/chrome/browser/ui/libgtkui/settings_provider_gtk.cc
+++ b/chrome/browser/ui/libgtkui/settings_provider_gtk.cc
@@ -14,17 +14,6 @@
 
 const char kDefaultGtkLayout[] = "menu:minimize,maximize,close";
 
-std::string GetGtkSettingsStringProperty(GtkSettings* settings,
-                                         const gchar* prop_name) {
-  GValue layout = G_VALUE_INIT;
-  g_value_init(&layout, G_TYPE_STRING);
-  g_object_get_property(G_OBJECT(settings), prop_name, &layout);
-  DCHECK(G_VALUE_HOLDS_STRING(&layout));
-  std::string prop_value(g_value_get_string(&layout));
-  g_value_unset(&layout);
-  return prop_value;
-}
-
 std::string GetDecorationLayoutFromGtkWindow() {
 #if GTK_CHECK_VERSION(3, 90, 0)
   NOTREACHED();
diff --git a/chrome/browser/ui/libgtkui/unity_service.cc b/chrome/browser/ui/libgtkui/unity_service.cc
index 9903568..4678006 100644
--- a/chrome/browser/ui/libgtkui/unity_service.cc
+++ b/chrome/browser/ui/libgtkui/unity_service.cc
@@ -51,7 +51,7 @@
 unity_launcher_entry_set_progress_visible_func entry_set_progress_visible =
     nullptr;
 
-void EnsureMethodsLoaded() {
+void EnsureLibUnityLoaded() {
   using base::nix::GetDesktopEnvironment;
 
   if (attempted_load)
@@ -119,7 +119,7 @@
 namespace unity {
 
 bool IsRunning() {
-  EnsureMethodsLoaded();
+  EnsureLibUnityLoaded();
   if (inspector && get_unity_running)
     return get_unity_running(inspector);
 
@@ -127,7 +127,7 @@
 }
 
 void SetDownloadCount(int count) {
-  EnsureMethodsLoaded();
+  EnsureLibUnityLoaded();
   if (chrome_entry && entry_set_count && entry_set_count_visible) {
     entry_set_count(chrome_entry, count);
     entry_set_count_visible(chrome_entry, count != 0);
@@ -135,7 +135,7 @@
 }
 
 void SetProgressFraction(float percentage) {
-  EnsureMethodsLoaded();
+  EnsureLibUnityLoaded();
   if (chrome_entry && entry_set_progress && entry_set_progress_visible) {
     entry_set_progress(chrome_entry, percentage);
     entry_set_progress_visible(chrome_entry,
diff --git a/chrome/browser/ui/search/local_ntp_doodle_browsertest.cc b/chrome/browser/ui/search/local_ntp_doodle_browsertest.cc
index 5e60a010..b4f8918 100644
--- a/chrome/browser/ui/search/local_ntp_doodle_browsertest.cc
+++ b/chrome/browser/ui/search/local_ntp_doodle_browsertest.cc
@@ -122,6 +122,26 @@
     return base::nullopt;
   }
 
+  void TeardownNavigatorTest(content::WebContents* tab) {
+    ASSERT_TRUE(content::ExecuteScript(tab, "window.navigator = navigatorOld"));
+  }
+
+  void SetupBeaconTest(content::WebContents* tab) {
+    ASSERT_TRUE(content::ExecuteScript(tab,
+                                       "var navigatorOld = window.navigator; "
+                                       "window.navigator = {};"
+                                       "window.navigator.sendBeacon = "
+                                       "(url) => { sentbeacon = url };"));
+  }
+
+  base::Optional<std::string> GetBeaconURL(content::WebContents* tab) {
+    std::string target_url;
+    if (instant_test_utils::GetStringFromJS(tab, "sentbeacon", &target_url)) {
+      return target_url;
+    }
+    return base::nullopt;
+  }
+
   bool ElementExists(content::WebContents* tab, const std::string& id) {
     return ExecuteBooleanJS(
         tab, base::StringPrintf("!!document.getElementById(%s)",
@@ -939,6 +959,130 @@
   EXPECT_EQ("https://g.co/", short_link);
 }
 
+IN_PROC_BROWSER_TEST_F(LocalNTPDoodleTest, ShouldLogShareClicksNoEventId) {
+  EncodedLogo cached_logo;
+  cached_logo.encoded_image = MakeRefPtr(kCachedB64);
+  cached_logo.metadata.mime_type = "image/png";
+  cached_logo.metadata.on_click_url =
+      GURL("https://www.chrotmium.org/?ct=test");
+  cached_logo.metadata.alt_text = "Chromium";
+  cached_logo.metadata.short_link = GURL("https://g.co");
+  cached_logo.metadata.share_button_x = 12;
+  cached_logo.metadata.share_button_y = 36;
+  cached_logo.metadata.share_button_opacity = 0.8;
+  cached_logo.metadata.share_button_icon = "sbimg";
+  cached_logo.metadata.share_button_bg = "#ffff00";
+
+  EXPECT_CALL(*logo_service(), GetLogoPtr(_))
+      .WillRepeatedly(DoAll(
+          ReturnCachedLogo(LogoCallbackReason::DETERMINED, cached_logo),
+          ReturnFreshLogo(LogoCallbackReason::REVALIDATED, base::nullopt)));
+
+  // Open a new blank tab, then go to NTP and listen for console messages.
+  content::WebContents* active_tab =
+      local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
+  ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
+
+  // Replace window.open so we stay in the same tab.
+  SetupWindowOpenTest(active_tab);
+  SetupBeaconTest(active_tab);
+
+  ASSERT_TRUE(content::ExecuteScript(
+      active_tab, "document.getElementById('ddlsb').click();"));
+
+  ASSERT_TRUE(content::ExecuteScript(
+      active_tab, "document.getElementById('ddlsd-fbb').click();"));
+  EXPECT_THAT(
+      GetBeaconURL(active_tab),
+      Eq<std::string>("https://www.google.com/gen_204"
+                      "?atyp=i&ct=doodle&cad=sh%2C2%2Cct%3Atest&ntp=1"));
+
+  ASSERT_TRUE(content::ExecuteScript(
+      active_tab, "document.getElementById('ddlsd-twb').click();"));
+  EXPECT_THAT(
+      GetBeaconURL(active_tab),
+      Eq<std::string>("https://www.google.com/gen_204"
+                      "?atyp=i&ct=doodle&cad=sh%2C3%2Cct%3Atest&ntp=1"));
+
+  ASSERT_TRUE(content::ExecuteScript(
+      active_tab, "document.getElementById('ddlsd-emb').click();"));
+  EXPECT_THAT(
+      GetBeaconURL(active_tab),
+      Eq<std::string>("https://www.google.com/gen_204"
+                      "?atyp=i&ct=doodle&cad=sh%2C5%2Cct%3Atest&ntp=1"));
+
+  ASSERT_TRUE(content::ExecuteScript(
+      active_tab, "document.getElementById('ddlsd-copy').click();"));
+  std::string short_link;
+  EXPECT_THAT(
+      GetBeaconURL(active_tab),
+      Eq<std::string>("https://www.google.com/gen_204"
+                      "?atyp=i&ct=doodle&cad=sh%2C6%2Cct%3Atest&ntp=1"));
+}
+
+IN_PROC_BROWSER_TEST_F(LocalNTPDoodleTest, ShouldLogShareClicksWithEventId) {
+  EncodedLogo cached_logo;
+  cached_logo.encoded_image = MakeRefPtr(kCachedB64);
+  cached_logo.metadata.mime_type = "image/png";
+  cached_logo.metadata.on_click_url =
+      GURL("https://www.chrotmium.org/?ct=test");
+  cached_logo.metadata.alt_text = "Chromium";
+  cached_logo.metadata.short_link = GURL("https://g.co");
+  cached_logo.metadata.share_button_x = 12;
+  cached_logo.metadata.share_button_y = 36;
+  cached_logo.metadata.share_button_opacity = 0.8;
+  cached_logo.metadata.share_button_icon = "sbimg";
+  cached_logo.metadata.share_button_bg = "#ffff00";
+
+  EXPECT_CALL(*logo_service(), GetLogoPtr(_))
+      .WillRepeatedly(DoAll(
+          ReturnCachedLogo(LogoCallbackReason::DETERMINED, cached_logo),
+          ReturnFreshLogo(LogoCallbackReason::REVALIDATED, base::nullopt)));
+
+  // Open a new blank tab, then go to NTP and listen for console messages.
+  content::WebContents* active_tab =
+      local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
+  ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
+
+  // Replace window.open so we stay in the same tab.
+  SetupWindowOpenTest(active_tab);
+  SetupBeaconTest(active_tab);
+
+  ASSERT_TRUE(content::ExecuteScript(active_tab, "doodles.ei = 'test_ei';"));
+
+  ASSERT_TRUE(content::ExecuteScript(
+      active_tab, "document.getElementById('ddlsb').click();"));
+
+  ASSERT_TRUE(content::ExecuteScript(
+      active_tab, "document.getElementById('ddlsd-fbb').click();"));
+  EXPECT_THAT(
+      GetBeaconURL(active_tab),
+      Eq<std::string>("https://www.google.com/gen_204?atyp=i"
+                      "&ct=doodle&cad=sh%2C2%2Cct%3Atest&ntp=1&ei=test_ei"));
+
+  ASSERT_TRUE(content::ExecuteScript(
+      active_tab, "document.getElementById('ddlsd-twb').click();"));
+  EXPECT_THAT(
+      GetBeaconURL(active_tab),
+      Eq<std::string>("https://www.google.com/gen_204?atyp=i"
+                      "&ct=doodle&cad=sh%2C3%2Cct%3Atest&ntp=1&ei=test_ei"));
+
+  ASSERT_TRUE(content::ExecuteScript(
+      active_tab, "document.getElementById('ddlsd-emb').click();"));
+  EXPECT_THAT(
+      GetBeaconURL(active_tab),
+      Eq<std::string>("https://www.google.com/gen_204?atyp=i"
+                      "&ct=doodle&cad=sh%2C5%2Cct%3Atest&ntp=1&ei=test_ei"));
+
+  ASSERT_TRUE(content::ExecuteScript(
+      active_tab, "document.getElementById('ddlsd-copy').click();"));
+  std::string short_link;
+  EXPECT_THAT(
+      GetBeaconURL(active_tab),
+      Eq<std::string>("https://www.google.com/gen_204?atyp=i"
+                      "&ct=doodle&cad=sh%2C6%2Cct%3Atest&ntp=1&ei=test_ei"));
+}
+
 IN_PROC_BROWSER_TEST_F(LocalNTPDoodleTest, ShouldAnimateLogoWhenClicked) {
   EncodedLogo cached_logo;
   cached_logo.encoded_image = MakeRefPtr(kCachedB64);
diff --git a/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc b/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
index b4ea285..209fd1b 100644
--- a/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
+++ b/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
@@ -148,17 +148,13 @@
 }
 
 void PaymentRequestBrowserTestBase::OnHasEnrolledInstrumentCalled() {
-  // TODO(https://crbug.com/915907): rename enum to HAS_ENROLLED_INSTRUMENT
-  // version when new CanMakePayment behavior is implemented.
   if (event_waiter_)
-    event_waiter_->OnEvent(DialogEvent::CAN_MAKE_PAYMENT_CALLED);
+    event_waiter_->OnEvent(DialogEvent::HAS_ENROLLED_INSTRUMENT_CALLED);
 }
 
 void PaymentRequestBrowserTestBase::OnHasEnrolledInstrumentReturned() {
-  // TODO(https://crbug.com/915907): rename enum to HAS_ENROLLED_INSTRUMENT
-  // version when new CanMakePayment behavior is implemented.
   if (event_waiter_)
-    event_waiter_->OnEvent(DialogEvent::CAN_MAKE_PAYMENT_RETURNED);
+    event_waiter_->OnEvent(DialogEvent::HAS_ENROLLED_INSTRUMENT_RETURNED);
 }
 
 void PaymentRequestBrowserTestBase::OnNotSupportedError() {
diff --git a/chrome/browser/ui/views/ssl_client_certificate_selector_browsertest.cc b/chrome/browser/ui/views/ssl_client_certificate_selector_browsertest.cc
index 8d80273..f4ede9f 100644
--- a/chrome/browser/ui/views/ssl_client_certificate_selector_browsertest.cc
+++ b/chrome/browser/ui/views/ssl_client_certificate_selector_browsertest.cc
@@ -6,8 +6,10 @@
 #include "base/files/file_path.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task/post_task.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ssl/ssl_client_auth_metrics.h"
 #include "chrome/browser/ssl/ssl_client_auth_requestor_mock.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
@@ -293,15 +295,20 @@
 #define MAYBE_Escape Escape
 #endif
 IN_PROC_BROWSER_TEST_F(SSLClientCertificateSelectorTest, MAYBE_Escape) {
+  base::HistogramTester histograms;
   EXPECT_CALL(*auth_requestor_.get(), CertificateSelected(nullptr, nullptr));
 
   EXPECT_TRUE(ui_test_utils::SendKeyPressSync(
       browser(), ui::VKEY_ESCAPE, false, false, false, false));
 
+  histograms.ExpectUniqueSample(kClientCertSelectHistogramName,
+                                ClientCertSelectionResult::kUserCancel, 1);
+
   Mock::VerifyAndClear(auth_requestor_.get());
 }
 
 IN_PROC_BROWSER_TEST_F(SSLClientCertificateSelectorTest, SelectDefault) {
+  base::HistogramTester histograms;
   EXPECT_CALL(*auth_requestor_.get(),
               CertificateSelected(cert_identity_1_->certificate(),
                                   cert_identity_1_->ssl_private_key()));
@@ -309,6 +316,21 @@
   EXPECT_TRUE(ui_test_utils::SendKeyPressSync(
       browser(), ui::VKEY_RETURN, false, false, false, false));
 
+  histograms.ExpectUniqueSample(kClientCertSelectHistogramName,
+                                ClientCertSelectionResult::kUserSelect, 1);
+
+  Mock::VerifyAndClear(auth_requestor_.get());
+}
+
+IN_PROC_BROWSER_TEST_F(SSLClientCertificateSelectorTest, CloseTab) {
+  base::HistogramTester histograms;
+  EXPECT_CALL(*auth_requestor_.get(), CancelCertificateSelection());
+
+  browser()->tab_strip_model()->CloseAllTabs();
+
+  histograms.ExpectBucketCount(kClientCertSelectHistogramName,
+                               ClientCertSelectionResult::kUserCloseTab, 1);
+
   Mock::VerifyAndClear(auth_requestor_.get());
 }
 
diff --git a/chrome/browser/ui/views/ssl_client_certificate_selector_mac_browsertest.mm b/chrome/browser/ui/views/ssl_client_certificate_selector_mac_browsertest.mm
index 6e7c86a..655c578 100644
--- a/chrome/browser/ui/views/ssl_client_certificate_selector_mac_browsertest.mm
+++ b/chrome/browser/ui/views/ssl_client_certificate_selector_mac_browsertest.mm
@@ -12,6 +12,8 @@
 #include "base/bind_helpers.h"
 #import "base/mac/mac_util.h"
 #include "base/macros.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "chrome/browser/ssl/ssl_client_auth_metrics.h"
 #include "chrome/browser/ssl/ssl_client_certificate_selector.h"
 #include "chrome/browser/ssl/ssl_client_certificate_selector_test.h"
 #include "chrome/browser/ui/browser.h"
@@ -130,6 +132,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(SSLClientCertificateSelectorMacTest, Basic) {
+  base::HistogramTester histograms;
   content::WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   WebContentsModalDialogManager* web_contents_modal_dialog_manager =
@@ -150,11 +153,15 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(web_contents_modal_dialog_manager->IsDialogActive());
 
+  histograms.ExpectUniqueSample(kClientCertSelectHistogramName,
+                                ClientCertSelectionResult::kUserCloseTab, 1);
+
   EXPECT_TRUE(results.destroyed);
   EXPECT_FALSE(results.continue_with_certificate_called);
 }
 
 IN_PROC_BROWSER_TEST_F(SSLClientCertificateSelectorMacTest, Cancel) {
+  base::HistogramTester histograms;
   content::WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   WebContentsModalDialogManager* web_contents_modal_dialog_manager =
@@ -175,6 +182,9 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(web_contents_modal_dialog_manager->IsDialogActive());
 
+  histograms.ExpectUniqueSample(kClientCertSelectHistogramName,
+                                ClientCertSelectionResult::kUserCancel, 1);
+
   // ContinueWithCertificate(nullptr, nullptr) should have been called.
   EXPECT_TRUE(results.destroyed);
   EXPECT_TRUE(results.continue_with_certificate_called);
@@ -183,6 +193,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(SSLClientCertificateSelectorMacTest, Accept) {
+  base::HistogramTester histograms;
   content::WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   WebContentsModalDialogManager* web_contents_modal_dialog_manager =
@@ -209,6 +220,9 @@
   EXPECT_EQ(client_cert1_, results.cert);
   ASSERT_TRUE(results.key);
 
+  histograms.ExpectUniqueSample(kClientCertSelectHistogramName,
+                                ClientCertSelectionResult::kUserSelect, 1);
+
   // The test keys are RSA keys.
   EXPECT_EQ(net::SSLPrivateKey::DefaultAlgorithmPreferences(
                 EVP_PKEY_RSA, base::mac::IsAtLeastOS10_13()),
diff --git a/chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.cc b/chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.cc
index e6f2e948..c35a259 100644
--- a/chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.cc
+++ b/chrome/browser/ui/views/sync/dice_bubble_sync_promo_view.cc
@@ -11,12 +11,10 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
 #include "chrome/browser/signin/account_consistency_mode_manager.h"
-#include "chrome/browser/signin/account_tracker_service_factory.h"
 #include "chrome/browser/signin/signin_ui_util.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/chrome_typography.h"
 #include "chrome/browser/ui/views/sync/dice_signin_button_view.h"
-#include "components/signin/core/browser/account_tracker_service.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/views/controls/label.h"
diff --git a/chrome/browser/ui/webui/app_management/app_management_ui.cc b/chrome/browser/ui/webui/app_management/app_management_ui.cc
index 3fbc518..a803b7ce 100644
--- a/chrome/browser/ui/webui/app_management/app_management_ui.cc
+++ b/chrome/browser/ui/webui/app_management/app_management_ui.cc
@@ -105,6 +105,10 @@
                           IDR_APP_MANAGEMENT_PERMISSION_ITEM_HTML);
   source->AddResourcePath("permission_item.js",
                           IDR_APP_MANAGEMENT_PERMISSION_ITEM_JS);
+  source->AddResourcePath("permission_toggle.html",
+                          IDR_APP_MANAGEMENT_PERMISSION_TOGGLE_HTML);
+  source->AddResourcePath("permission_toggle.js",
+                          IDR_APP_MANAGEMENT_PERMISSION_TOGGLE_JS);
   source->AddResourcePath("pwa_permission_view.html",
                           IDR_APP_MANAGEMENT_PWA_PERMISSION_VIEW_HTML);
   source->AddResourcePath("pwa_permission_view.js",
diff --git a/chrome/browser/ui/webui/chromeos/internet_detail_dialog.cc b/chrome/browser/ui/webui/chromeos/internet_detail_dialog.cc
index 11226162..c29177b 100644
--- a/chrome/browser/ui/webui/chromeos/internet_detail_dialog.cc
+++ b/chrome/browser/ui/webui/chromeos/internet_detail_dialog.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/webui/chromeos/internet_detail_dialog.h"
 
+#include "ash/public/cpp/ash_features.h"
 #include "base/json/json_writer.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/profiles/profile.h"
@@ -136,7 +137,8 @@
     : ui::WebDialogUI(web_ui) {
   content::WebUIDataSource* source = content::WebUIDataSource::Create(
       chrome::kChromeUIInternetDetailDialogHost);
-
+  source->AddBoolean("showTechnologyBadge",
+                     !ash::features::IsSeparateNetworkIconsEnabled());
   AddInternetStrings(source);
   source->AddLocalizedString("title", IDS_SETTINGS_INTERNET_DETAIL);
   source->SetJsonPath("strings.js");
diff --git a/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc b/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc
index 1c5f2ef..3ef92b7 100644
--- a/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/core_oobe_handler.cc
@@ -6,6 +6,7 @@
 
 #include <type_traits>
 
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/public/interfaces/event_rewriter_controller.mojom.h"
 #include "ash/shell.h"
@@ -186,6 +187,8 @@
                base::Value(TabletModeClient::Get()->tablet_mode_enabled()));
   dict->SetKey("isDemoModeEnabled",
                base::Value(DemoSetupController::IsDemoModeAllowed()));
+  dict->SetKey("showTechnologyBadge",
+               base::Value(!ash::features::IsSeparateNetworkIconsEnabled()));
 }
 
 void CoreOobeHandler::RegisterMessages() {
diff --git a/chrome/browser/ui/webui/settings/md_settings_ui.cc b/chrome/browser/ui/webui/settings/md_settings_ui.cc
index 6e577089..3f23bea8 100644
--- a/chrome/browser/ui/webui/settings/md_settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_ui.cc
@@ -324,6 +324,8 @@
   html_source->AddBoolean(
       "lockScreenHideSensitiveNotificationsSupported",
       ash::features::IsLockScreenHideSensitiveNotificationsSupported());
+  html_source->AddBoolean("showTechnologyBadge",
+                          !ash::features::IsSeparateNetworkIconsEnabled());
   html_source->AddBoolean("hasInternalStylus",
                           ash::stylus_utils::HasInternalStylus());
 
diff --git a/chrome/browser/web_applications/bookmark_apps/BUILD.gn b/chrome/browser/web_applications/bookmark_apps/BUILD.gn
index a0722b3..b8cfbab 100644
--- a/chrome/browser/web_applications/bookmark_apps/BUILD.gn
+++ b/chrome/browser/web_applications/bookmark_apps/BUILD.gn
@@ -24,6 +24,26 @@
   ]
 }
 
+source_set("test_support") {
+  testonly = true
+
+  sources = [
+    "test_web_app_provider.cc",
+    "test_web_app_provider.h",
+  ]
+
+  deps = [
+    ":bookmark_apps",
+    "//base",
+    "//chrome/browser",
+    "//chrome/browser/web_applications",
+    "//chrome/browser/web_applications:web_app_group",
+    "//chrome/browser/web_applications:web_applications_on_extensions",
+    "//chrome/browser/web_applications/components",
+    "//components/keyed_service/content",
+  ]
+}
+
 source_set("unit_tests") {
   testonly = true
 
@@ -35,6 +55,7 @@
 
   deps = [
     ":bookmark_apps",
+    ":test_support",
     "//base",
     "//chrome/browser",
     "//chrome/browser/web_applications",
@@ -66,12 +87,11 @@
   sources = [
     "system_web_app_manager_browsertest.cc",
     "system_web_app_manager_browsertest.h",
-    "test_web_app_provider.cc",
-    "test_web_app_provider.h",
   ]
 
   deps = [
     ":bookmark_apps",
+    ":test_support",
     "//base",
     "//base/test:test_support",
     "//chrome/app/theme:chrome_unscaled_resources_grit",
diff --git a/chrome/browser/web_applications/bookmark_apps/policy/web_app_policy_manager_unittest.cc b/chrome/browser/web_applications/bookmark_apps/policy/web_app_policy_manager_unittest.cc
index d76d588a..fbadf69 100644
--- a/chrome/browser/web_applications/bookmark_apps/policy/web_app_policy_manager_unittest.cc
+++ b/chrome/browser/web_applications/bookmark_apps/policy/web_app_policy_manager_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/run_loop.h"
 #include "base/values.h"
 #include "chrome/browser/prefs/browser_prefs.h"
+#include "chrome/browser/web_applications/bookmark_apps/test_web_app_provider.h"
 #include "chrome/browser/web_applications/components/pending_app_manager.h"
 #include "chrome/browser/web_applications/components/test_pending_app_manager.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
@@ -84,14 +85,26 @@
 
 class WebAppPolicyManagerTest : public ChromeRenderViewHostTestHarness {
  public:
-  WebAppPolicyManagerTest() = default;
+  WebAppPolicyManagerTest()
+      : test_web_app_provider_creator_(
+            base::BindOnce(&WebAppPolicyManagerTest::CreateWebAppProvider,
+                           base::Unretained(this))) {}
+
   ~WebAppPolicyManagerTest() override = default;
 
-  void SetUp() override {
-    ChromeRenderViewHostTestHarness::SetUp();
-    // Reset WebAppProvider so that its WebAppPolicyManager doesn't interfere
-    // with tests.
-    web_app::WebAppProvider::Get(profile())->Reset();
+  std::unique_ptr<KeyedService> CreateWebAppProvider(Profile* profile) {
+    auto provider = std::make_unique<TestWebAppProvider>(profile);
+
+    auto test_pending_app_manager = std::make_unique<TestPendingAppManager>();
+    test_pending_app_manager_ = test_pending_app_manager.get();
+    provider->SetPendingAppManager(std::move(test_pending_app_manager));
+
+    auto web_app_policy_manager = std::make_unique<WebAppPolicyManager>(
+        profile, test_pending_app_manager_);
+    web_app_policy_manager_ = web_app_policy_manager.get();
+    provider->SetWebAppPolicyManager(std::move(web_app_policy_manager));
+
+    return provider;
   }
 
   std::string GenerateFakeExtensionId(GURL& url) {
@@ -99,7 +112,6 @@
   }
 
   void SimulatePreviouslyInstalledApp(
-      TestPendingAppManager* pending_app_manager,
       GURL url,
       InstallSource install_source) {
     std::string id = GenerateFakeExtensionId(url);
@@ -109,21 +121,30 @@
     ExtensionIdsMap extension_ids_map(profile()->GetPrefs());
     extension_ids_map.Insert(url, id, install_source);
 
-    pending_app_manager->SimulatePreviouslyInstalledApp(url, install_source);
+    pending_app_manager()->SimulatePreviouslyInstalledApp(url, install_source);
   }
 
+ protected:
+  TestPendingAppManager* pending_app_manager() {
+    return test_pending_app_manager_;
+  }
+
+  WebAppPolicyManager* policy_manager() { return web_app_policy_manager_; }
+
  private:
+  TestWebAppProviderCreator test_web_app_provider_creator_;
+  TestPendingAppManager* test_pending_app_manager_ = nullptr;
+  WebAppPolicyManager* web_app_policy_manager_ = nullptr;
+
   DISALLOW_COPY_AND_ASSIGN(WebAppPolicyManagerTest);
 };
 
 TEST_F(WebAppPolicyManagerTest, NoForceInstalledAppsPrefValue) {
-  auto pending_app_manager = std::make_unique<TestPendingAppManager>();
-  WebAppPolicyManager web_app_policy_manager(profile(),
-                                             pending_app_manager.get());
-  web_app_policy_manager.Start();
+  policy_manager()->Start();
+
   base::RunLoop().RunUntilIdle();
 
-  const auto& apps_to_install = pending_app_manager->install_requests();
+  const auto& apps_to_install = pending_app_manager()->install_requests();
   EXPECT_TRUE(apps_to_install.empty());
 }
 
@@ -131,13 +152,10 @@
   profile()->GetPrefs()->Set(prefs::kWebAppInstallForceList,
                              base::Value(base::Value::Type::LIST));
 
-  auto pending_app_manager = std::make_unique<TestPendingAppManager>();
-  WebAppPolicyManager web_app_policy_manager(profile(),
-                                             pending_app_manager.get());
-  web_app_policy_manager.Start();
+  policy_manager()->Start();
   base::RunLoop().RunUntilIdle();
 
-  const auto& apps_to_install = pending_app_manager->install_requests();
+  const auto& apps_to_install = pending_app_manager()->install_requests();
   EXPECT_TRUE(apps_to_install.empty());
 }
 
@@ -148,13 +166,10 @@
   list.GetList().push_back(GetTabbedItem());
   profile()->GetPrefs()->Set(prefs::kWebAppInstallForceList, std::move(list));
 
-  auto pending_app_manager = std::make_unique<TestPendingAppManager>();
-  WebAppPolicyManager web_app_policy_manager(profile(),
-                                             pending_app_manager.get());
-  web_app_policy_manager.Start();
+  policy_manager()->Start();
   base::RunLoop().RunUntilIdle();
 
-  const auto& apps_to_install = pending_app_manager->install_requests();
+  const auto& apps_to_install = pending_app_manager()->install_requests();
 
   std::vector<PendingAppManager::AppInfo> expected_apps_to_install;
   expected_apps_to_install.push_back(GetWindowedAppInfo());
@@ -168,13 +183,10 @@
   list.GetList().push_back(GetDefaultContainerItem());
   profile()->GetPrefs()->Set(prefs::kWebAppInstallForceList, std::move(list));
 
-  auto pending_app_manager = std::make_unique<TestPendingAppManager>();
-  WebAppPolicyManager web_app_policy_manager(profile(),
-                                             pending_app_manager.get());
-  web_app_policy_manager.Start();
+  policy_manager()->Start();
   base::RunLoop().RunUntilIdle();
 
-  const auto& apps_to_install = pending_app_manager->install_requests();
+  const auto& apps_to_install = pending_app_manager()->install_requests();
 
   std::vector<PendingAppManager::AppInfo> expected_apps_to_install;
   expected_apps_to_install.push_back(GetDefaultContainerAppInfo());
@@ -188,13 +200,10 @@
   profile()->GetPrefs()->Set(prefs::kWebAppInstallForceList,
                              std::move(first_list));
 
-  auto pending_app_manager = std::make_unique<TestPendingAppManager>();
-  WebAppPolicyManager web_app_policy_manager(profile(),
-                                             pending_app_manager.get());
-  web_app_policy_manager.Start();
+  policy_manager()->Start();
   base::RunLoop().RunUntilIdle();
 
-  const auto& apps_to_install = pending_app_manager->install_requests();
+  const auto& apps_to_install = pending_app_manager()->install_requests();
 
   std::vector<PendingAppManager::AppInfo> expected_apps_to_install;
   expected_apps_to_install.push_back(GetWindowedAppInfo());
@@ -214,16 +223,13 @@
 }
 
 TEST_F(WebAppPolicyManagerTest, UninstallAppInstalledInPreviousSession) {
-  auto pending_app_manager = std::make_unique<TestPendingAppManager>();
-
   // Simulate two policy apps and a regular app that were installed in the
   // previous session.
-  SimulatePreviouslyInstalledApp(pending_app_manager.get(), GURL(kWindowedUrl),
+  SimulatePreviouslyInstalledApp(GURL(kWindowedUrl),
                                  InstallSource::kExternalPolicy);
-  SimulatePreviouslyInstalledApp(pending_app_manager.get(), GURL(kTabbedUrl),
+  SimulatePreviouslyInstalledApp(GURL(kTabbedUrl),
                                  InstallSource::kExternalPolicy);
-  SimulatePreviouslyInstalledApp(pending_app_manager.get(),
-                                 GURL(kDefaultContainerUrl),
+  SimulatePreviouslyInstalledApp(GURL(kDefaultContainerUrl),
                                  InstallSource::kInternal);
 
   // Push a policy with only one of the apps.
@@ -232,28 +238,24 @@
   profile()->GetPrefs()->Set(prefs::kWebAppInstallForceList,
                              std::move(first_list));
 
-  WebAppPolicyManager web_app_policy_manager(profile(),
-                                             pending_app_manager.get());
-  web_app_policy_manager.Start();
+  policy_manager()->Start();
   base::RunLoop().RunUntilIdle();
 
   // We should only try to install the app in the policy.
   std::vector<PendingAppManager::AppInfo> expected_apps_to_install;
   expected_apps_to_install.push_back(GetWindowedAppInfo());
-  EXPECT_EQ(pending_app_manager->install_requests(), expected_apps_to_install);
+  EXPECT_EQ(pending_app_manager()->install_requests(),
+            expected_apps_to_install);
 
   // We should try to uninstall the app that is no longer in the policy.
   EXPECT_EQ(std::vector<GURL>({GURL(kTabbedUrl)}),
-            pending_app_manager->uninstall_requests());
+            pending_app_manager()->uninstall_requests());
 }
 
 // Tests that we correctly uninstall an app that we installed in the same
 // session.
 TEST_F(WebAppPolicyManagerTest, UninstallAppInstalledInCurrentSession) {
-  auto pending_app_manager = std::make_unique<TestPendingAppManager>();
-  WebAppPolicyManager web_app_policy_manager(profile(),
-                                             pending_app_manager.get());
-  web_app_policy_manager.Start();
+  policy_manager()->Start();
   base::RunLoop().RunUntilIdle();
 
   // Add two sites, one that opens in a window and one that opens in a tab.
@@ -264,7 +266,7 @@
                              std::move(first_list));
   base::RunLoop().RunUntilIdle();
 
-  const auto& apps_to_install = pending_app_manager->install_requests();
+  const auto& apps_to_install = pending_app_manager()->install_requests();
 
   std::vector<PendingAppManager::AppInfo> expected_apps_to_install;
   expected_apps_to_install.push_back(GetWindowedAppInfo());
@@ -286,7 +288,7 @@
   EXPECT_EQ(apps_to_install, expected_apps_to_install);
 
   EXPECT_EQ(std::vector<GURL>({GURL(kTabbedUrl)}),
-            pending_app_manager->uninstall_requests());
+            pending_app_manager()->uninstall_requests());
 }
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/bookmark_apps/system_web_app_manager_browsertest.cc b/chrome/browser/web_applications/bookmark_apps/system_web_app_manager_browsertest.cc
index b5615984..c8fbfc9 100644
--- a/chrome/browser/web_applications/bookmark_apps/system_web_app_manager_browsertest.cc
+++ b/chrome/browser/web_applications/bookmark_apps/system_web_app_manager_browsertest.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/extensions/pending_bookmark_app_manager.h"
 #include "chrome/browser/web_applications/test/test_system_web_app_manager.h"
-#include "chrome/browser/web_applications/web_app_provider_factory.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/extensions/manifest_handlers/app_theme_color_info.h"
 #include "chrome/grit/browser_resources.h"
@@ -113,7 +112,10 @@
 };
 
 SystemWebAppManagerBrowserTest::SystemWebAppManagerBrowserTest()
-    : factory_(std::make_unique<TestWebUIControllerFactory>()) {
+    : factory_(std::make_unique<TestWebUIControllerFactory>()),
+      test_web_app_provider_creator_(
+          base::BindOnce(&SystemWebAppManagerBrowserTest::CreateWebAppProvider,
+                         base::Unretained(this))) {
   scoped_feature_list_.InitWithFeatures(
       {features::kDesktopPWAWindowing, features::kSystemWebApps}, {});
   content::WebUIControllerFactory::RegisterFactory(factory_.get());
@@ -123,36 +125,15 @@
   content::WebUIControllerFactory::UnregisterFactoryForTesting(factory_.get());
 }
 
-void SystemWebAppManagerBrowserTest::SetUpInProcessBrowserTestFixture() {
-  will_create_browser_context_services_subscription_ =
-      BrowserContextDependencyManager::GetInstance()
-          ->RegisterWillCreateBrowserContextServicesCallbackForTesting(
-              base::BindRepeating(&SystemWebAppManagerBrowserTest::
-                                      OnWillCreateBrowserContextServices,
-                                  base::Unretained(this)));
-}
-
-void SystemWebAppManagerBrowserTest::OnWillCreateBrowserContextServices(
-    content::BrowserContext* context) {
-  WebAppProviderFactory::GetInstance()->SetTestingFactory(
-      context,
-      base::BindRepeating(&SystemWebAppManagerBrowserTest::CreateWebAppProvider,
-                          base::Unretained(this)));
-}
-
 std::unique_ptr<KeyedService>
-SystemWebAppManagerBrowserTest::CreateWebAppProvider(
-    content::BrowserContext* context) {
-  Profile* profile = Profile::FromBrowserContext(context);
-
-  if (!SystemWebAppManager::IsEnabled())
-    return nullptr;
+SystemWebAppManagerBrowserTest::CreateWebAppProvider(Profile* profile) {
+  DCHECK(SystemWebAppManager::IsEnabled());
 
   auto provider = std::make_unique<TestWebAppProvider>(profile);
   // Create all real subsystems but do not start them:
   provider->Init();
 
-  // But override SystemWebAppManager with TestSystemWebAppManager:
+  // Override SystemWebAppManager with TestSystemWebAppManager:
   DCHECK(!test_system_web_app_manager_);
   auto test_system_web_app_manager = std::make_unique<TestSystemWebAppManager>(
       profile, &provider->pending_app_manager());
diff --git a/chrome/browser/web_applications/bookmark_apps/system_web_app_manager_browsertest.h b/chrome/browser/web_applications/bookmark_apps/system_web_app_manager_browsertest.h
index 83e2009..0752960 100644
--- a/chrome/browser/web_applications/bookmark_apps/system_web_app_manager_browsertest.h
+++ b/chrome/browser/web_applications/bookmark_apps/system_web_app_manager_browsertest.h
@@ -7,18 +7,14 @@
 
 #include <memory>
 
-#include "base/callback_list.h"
 #include "base/macros.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/web_applications/bookmark_apps/test_web_app_provider.h"
 #include "chrome/test/base/in_process_browser_test.h"
 
 class Browser;
 class KeyedService;
 
-namespace content {
-class BrowserContext;
-}
-
 namespace web_app {
 
 class TestSystemWebAppManager;
@@ -29,25 +25,17 @@
   SystemWebAppManagerBrowserTest();
   ~SystemWebAppManagerBrowserTest() override;
 
-  // InProcessBrowserTest:
-  void SetUpInProcessBrowserTestFixture() override;
-
  protected:
   Browser* WaitForSystemAppInstallAndLaunch();
 
  private:
-  void OnWillCreateBrowserContextServices(content::BrowserContext* context);
-  std::unique_ptr<KeyedService> CreateWebAppProvider(
-      content::BrowserContext* context);
+  std::unique_ptr<KeyedService> CreateWebAppProvider(Profile* profile);
 
   base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<TestWebUIControllerFactory> factory_;
+  TestWebAppProviderCreator test_web_app_provider_creator_;
   TestSystemWebAppManager* test_system_web_app_manager_ = nullptr;
 
-  std::unique_ptr<
-      base::CallbackList<void(content::BrowserContext*)>::Subscription>
-      will_create_browser_context_services_subscription_;
-
   DISALLOW_COPY_AND_ASSIGN(SystemWebAppManagerBrowserTest);
 };
 
diff --git a/chrome/browser/web_applications/bookmark_apps/test_web_app_provider.cc b/chrome/browser/web_applications/bookmark_apps/test_web_app_provider.cc
index fb59e9f36..9b114b56a 100644
--- a/chrome/browser/web_applications/bookmark_apps/test_web_app_provider.cc
+++ b/chrome/browser/web_applications/bookmark_apps/test_web_app_provider.cc
@@ -4,7 +4,16 @@
 
 #include "chrome/browser/web_applications/bookmark_apps/test_web_app_provider.h"
 
+#include <utility>
+
+#include "base/bind.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/components/pending_app_manager.h"
+#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
 #include "chrome/browser/web_applications/system_web_app_manager.h"
+#include "chrome/browser/web_applications/web_app_provider_factory.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
 
 namespace web_app {
 
@@ -13,9 +22,48 @@
 
 TestWebAppProvider::~TestWebAppProvider() = default;
 
+void TestWebAppProvider::SetPendingAppManager(
+    std::unique_ptr<PendingAppManager> pending_app_manager) {
+  pending_app_manager_ = std::move(pending_app_manager);
+}
+
 void TestWebAppProvider::SetSystemWebAppManager(
     std::unique_ptr<SystemWebAppManager> system_web_app_manager) {
   system_web_app_manager_ = std::move(system_web_app_manager);
 }
 
+void TestWebAppProvider::SetWebAppPolicyManager(
+    std::unique_ptr<WebAppPolicyManager> web_app_policy_manager) {
+  web_app_policy_manager_ = std::move(web_app_policy_manager);
+}
+
+TestWebAppProviderCreator::TestWebAppProviderCreator(
+    CreateWebAppProviderCallback callback)
+    : callback_(std::move(callback)) {
+  will_create_browser_context_services_subscription_ =
+      BrowserContextDependencyManager::GetInstance()
+          ->RegisterWillCreateBrowserContextServicesCallbackForTesting(
+              base::BindRepeating(&TestWebAppProviderCreator::
+                                      OnWillCreateBrowserContextServices,
+                                  base::Unretained(this)));
+}
+
+TestWebAppProviderCreator::~TestWebAppProviderCreator() = default;
+
+void TestWebAppProviderCreator::OnWillCreateBrowserContextServices(
+    content::BrowserContext* context) {
+  WebAppProviderFactory::GetInstance()->SetTestingFactory(
+      context,
+      base::BindRepeating(&TestWebAppProviderCreator::CreateWebAppProvider,
+                          base::Unretained(this)));
+}
+
+std::unique_ptr<KeyedService> TestWebAppProviderCreator::CreateWebAppProvider(
+    content::BrowserContext* context) {
+  Profile* profile = Profile::FromBrowserContext(context);
+  if (!AreWebAppsEnabled(profile) || !callback_)
+    return nullptr;
+  return std::move(callback_).Run(profile);
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/bookmark_apps/test_web_app_provider.h b/chrome/browser/web_applications/bookmark_apps/test_web_app_provider.h
index dc7ade8..fc12f2a 100644
--- a/chrome/browser/web_applications/bookmark_apps/test_web_app_provider.h
+++ b/chrome/browser/web_applications/bookmark_apps/test_web_app_provider.h
@@ -7,21 +7,53 @@
 
 #include <memory>
 
+#include "base/callback.h"
+#include "base/callback_list.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 
 class Profile;
 
+namespace content {
+class BrowserContext;
+}
+
 namespace web_app {
 
+class PendingAppManager;
 class SystemWebAppManager;
+class WebAppPolicyManager;
 
 class TestWebAppProvider : public WebAppProvider {
  public:
   explicit TestWebAppProvider(Profile* profile);
   ~TestWebAppProvider() override;
 
+  void SetPendingAppManager(
+      std::unique_ptr<PendingAppManager> pending_app_manager);
   void SetSystemWebAppManager(
       std::unique_ptr<SystemWebAppManager> system_web_app_manager);
+  void SetWebAppPolicyManager(
+      std::unique_ptr<WebAppPolicyManager> web_app_policy_manager);
+};
+
+class TestWebAppProviderCreator {
+ public:
+  using CreateWebAppProviderCallback =
+      base::OnceCallback<std::unique_ptr<KeyedService>(Profile* profile)>;
+
+  explicit TestWebAppProviderCreator(CreateWebAppProviderCallback callback);
+  ~TestWebAppProviderCreator();
+
+ private:
+  void OnWillCreateBrowserContextServices(content::BrowserContext* context);
+  std::unique_ptr<KeyedService> CreateWebAppProvider(
+      content::BrowserContext* context);
+
+  CreateWebAppProviderCallback callback_;
+
+  std::unique_ptr<
+      base::CallbackList<void(content::BrowserContext*)>::Subscription>
+      will_create_browser_context_services_subscription_;
 };
 
 }  // namespace web_app
diff --git a/chrome/services/app_service/public/mojom/types.mojom b/chrome/services/app_service/public/mojom/types.mojom
index 18cb077..ba90a38d0 100644
--- a/chrome/services/app_service/public/mojom/types.mojom
+++ b/chrome/services/app_service/public/mojom/types.mojom
@@ -80,6 +80,7 @@
 enum IconType {
   kUnknown,
   kArc,
+  kCrostini,
   kExtension,
   kResource,
 };
diff --git a/chrome/services/cups_ipp_parser/BUILD.gn b/chrome/services/cups_ipp_parser/BUILD.gn
index 0628ec0..ed87417 100644
--- a/chrome/services/cups_ipp_parser/BUILD.gn
+++ b/chrome/services/cups_ipp_parser/BUILD.gn
@@ -22,6 +22,7 @@
   ]
 
   public_deps = [
+    "//chrome/services/cups_ipp_parser/public/mojom",
     "//mojo/public/mojom/base",
     "//printing",
     "//services/service_manager/public/cpp",
@@ -31,10 +32,7 @@
   if (enable_service) {
     configs += [ "//printing:cups" ]
     sources += [ "ipp_parser.cc" ]
-    deps += [
-      "//chrome/services/cups_ipp_parser/public/cpp",
-      "//chrome/services/cups_ipp_parser/public/mojom",
-    ]
+    deps += [ "//chrome/services/cups_ipp_parser/public/cpp" ]
   } else {
     sources += [ "fake_ipp_parser.cc" ]
   }
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 309436d..69babd6 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2237,6 +2237,25 @@
   }
 }
 
+if (is_linux || is_mac || is_win) {
+  group("browser_tests_apprtc") {
+    testonly = true
+    data_deps = [
+      ":browser_tests",
+    ]
+    data = [
+      "//out/apprtc/out/app_engine/",
+      "//out/apprtc/temp/google-cloud-sdk/",
+      "//out/collider/",
+    ]
+    if (is_win) {
+      write_runtime_deps = "$root_out_dir/$target_name.exe.runtime_deps"
+    } else {
+      write_runtime_deps = "$root_out_dir/$target_name.runtime_deps"
+    }
+  }
+}
+
 group("telemetry_perf_unittests") {
   testonly = true
   deps = [
diff --git a/chromeos/services/assistant/platform/system_provider_impl.h b/chromeos/services/assistant/platform/system_provider_impl.h
index 55e922ba..e5a9dfd4 100644
--- a/chromeos/services/assistant/platform/system_provider_impl.h
+++ b/chromeos/services/assistant/platform/system_provider_impl.h
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "libassistant/shared/public/platform_system.h"
 #include "services/device/public/mojom/battery_monitor.mojom.h"
+#include "services/device/public/mojom/battery_status.mojom.h"
 
 namespace chromeos {
 namespace assistant {
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index 7df8934..fbc6997d9 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -904,7 +904,8 @@
 
     case ash::mojom::WindowStateType::MAXIMIZED:
     case ash::mojom::WindowStateType::FULLSCREEN:
-      animation_type = ash::wm::ClientControlledState::kAnimationCrossFade;
+      if (!window_state->IsPip())
+        animation_type = ash::wm::ClientControlledState::kAnimationCrossFade;
       break;
 
     default:
diff --git a/components/gwp_asan/common/BUILD.gn b/components/gwp_asan/common/BUILD.gn
index b8d77a5..dc451bb5 100644
--- a/components/gwp_asan/common/BUILD.gn
+++ b/components/gwp_asan/common/BUILD.gn
@@ -2,11 +2,15 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//testing/libfuzzer/fuzzer_test.gni")
+
 static_library("common") {
   sources = [
     "allocator_state.cc",
     "allocator_state.h",
     "crash_key_name.h",
+    "pack_stack_trace.cc",
+    "pack_stack_trace.h",
   ]
 
   deps = [
@@ -18,6 +22,7 @@
   testonly = true
   sources = [
     "allocator_state_unittest.cc",
+    "pack_stack_trace_unittest.cc",
   ]
   deps = [
     ":common",
@@ -25,3 +30,21 @@
     "//testing/gtest",
   ]
 }
+
+fuzzer_test("pack_stack_trace_unpack_fuzzer") {
+  sources = [
+    "pack_stack_trace_unpack_fuzzer.cc",
+  ]
+  deps = [
+    "//components/gwp_asan/common",
+  ]
+}
+
+fuzzer_test("pack_stack_trace_differential_fuzzer") {
+  sources = [
+    "pack_stack_trace_differential_fuzzer.cc",
+  ]
+  deps = [
+    "//components/gwp_asan/common",
+  ]
+}
diff --git a/components/gwp_asan/common/pack_stack_trace.cc b/components/gwp_asan/common/pack_stack_trace.cc
new file mode 100644
index 0000000..7ddd27b
--- /dev/null
+++ b/components/gwp_asan/common/pack_stack_trace.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/gwp_asan/common/pack_stack_trace.h"
+
+namespace gwp_asan {
+namespace internal {
+
+namespace {
+
+// Encode variable-length integer to |out|, returns 0 if there was not enough
+// space to finish writing it or the length of the encoded integer otherwise.
+size_t VarIntEncode(uintptr_t value, uint8_t* out, size_t out_len) {
+  for (size_t i = 0; i < out_len; i++) {
+    out[i] = value & 0x7f;
+    value >>= 7;
+    if (!value)
+      return i + 1;
+
+    out[i] |= 0x80;
+  }
+
+  return 0;
+}
+
+// Decode a variable-length integer to |out|, returns 0 if reading it failed or
+// the length of the decoded integer otherwise.
+size_t VarIntDecode(const uint8_t* in, size_t in_len, uintptr_t* out) {
+  uintptr_t result = 0;
+  size_t shift = 0;
+  for (size_t i = 0; i < in_len; i++) {
+    result |= static_cast<uintptr_t>(in[i] & 0x7f) << shift;
+    if (in[i] < 0x80) {
+      *out = result;
+      return i + 1;
+    }
+
+    shift += 7;
+    // Disallow overflowing the range of the output integer.
+    if (shift >= sizeof(uintptr_t) * 8)
+      return 0;
+  }
+
+  return 0;
+}
+
+uintptr_t ZigzagEncode(uintptr_t value) {
+  uintptr_t encoded = value << 1;
+  if (static_cast<intptr_t>(value) >= 0)
+    return encoded;
+  return ~encoded;
+}
+
+uintptr_t ZigzagDecode(uintptr_t value) {
+  uintptr_t decoded = value >> 1;
+  if (!(value & 1))
+    return decoded;
+  return ~decoded;
+}
+
+}  // namespace
+
+size_t Pack(const uintptr_t* unpacked,
+            size_t unpacked_size,
+            uint8_t* packed,
+            size_t packed_max_size) {
+  size_t idx = 0;
+  for (size_t cur_depth = 0; cur_depth < unpacked_size; cur_depth++) {
+    uintptr_t diff = unpacked[cur_depth];
+    if (cur_depth > 0)
+      diff -= unpacked[cur_depth - 1];
+    size_t encoded_len =
+        VarIntEncode(ZigzagEncode(diff), packed + idx, packed_max_size - idx);
+    if (!encoded_len)
+      break;
+
+    idx += encoded_len;
+  }
+
+  return idx;
+}
+
+size_t Unpack(const uint8_t* packed,
+              size_t packed_size,
+              uintptr_t* unpacked,
+              size_t unpacked_max_size) {
+  size_t cur_depth;
+  size_t idx = 0;
+  for (cur_depth = 0; cur_depth < unpacked_max_size; cur_depth++) {
+    uintptr_t encoded_diff;
+    size_t decoded_len =
+        VarIntDecode(packed + idx, packed_size - idx, &encoded_diff);
+    if (!decoded_len)
+      break;
+    idx += decoded_len;
+
+    unpacked[cur_depth] = ZigzagDecode(encoded_diff);
+    if (cur_depth > 0)
+      unpacked[cur_depth] += unpacked[cur_depth - 1];
+  }
+
+  if (idx != packed_size && cur_depth != unpacked_max_size)
+    return 0;
+
+  return cur_depth;
+}
+
+}  // namespace internal
+}  // namespace gwp_asan
diff --git a/components/gwp_asan/common/pack_stack_trace.h b/components/gwp_asan/common/pack_stack_trace.h
new file mode 100644
index 0000000..cae0584
--- /dev/null
+++ b/components/gwp_asan/common/pack_stack_trace.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_GWP_ASAN_COMMON_PACK_STACK_TRACE_H_
+#define COMPONENTS_GWP_ASAN_COMMON_PACK_STACK_TRACE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+// These routines 'compress' a stack trace by storing a stack trace as a
+// starting address, followed by offsets from the previous pointer. All values
+// are stored using variable-length integers to reduce space. Furthermore, they
+// are zigzag encoded, like in protobuf encoding, to store negative offsets
+// efficiently. On 64-bit platforms this packing can reduce space required to
+// store a stack trace by over 50%.
+
+namespace gwp_asan {
+namespace internal {
+
+// From the stack trace in |unpacked| of length |unpacked_size|, pack it into
+// the buffer |packed| with maximum length |packed_max_size|. The return value
+// is the number of bytes that were written to the output buffer.
+size_t Pack(const uintptr_t* unpacked,
+            size_t unpacked_size,
+            uint8_t* packed,
+            size_t packed_max_size);
+
+// From the packed stack trace in |packed| of length |packed_size|, write the
+// unpacked stack trace of maximum length |unpacked_max_size| into |unpacked|.
+// Returns the number of entries un packed, or 0 on error.
+size_t Unpack(const uint8_t* packed,
+              size_t packed_size,
+              uintptr_t* unpacked,
+              size_t unpacked_max_size);
+
+}  // namespace internal
+}  // namespace gwp_asan
+
+#endif  // COMPONENTS_GWP_ASAN_COMMON_PACK_STACK_TRACE_H_
diff --git a/components/gwp_asan/common/pack_stack_trace_differential_fuzzer.cc b/components/gwp_asan/common/pack_stack_trace_differential_fuzzer.cc
new file mode 100644
index 0000000..f964385
--- /dev/null
+++ b/components/gwp_asan/common/pack_stack_trace_differential_fuzzer.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/gwp_asan/common/pack_stack_trace.h"
+
+#include <string.h>
+#include <algorithm>
+
+// Tests that whatever we give to Pack() is the same as what comes out of
+// Unpack().
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
+  if (Size < sizeof(size_t) * 2)
+    return 0;
+
+  size_t unpacked_max_size = reinterpret_cast<const size_t*>(Data)[0];
+  size_t packed_max_size = reinterpret_cast<const size_t*>(Data)[1];
+  Data += sizeof(size_t) * 2;
+  Size -= sizeof(size_t) * 2;
+
+  size_t entries = Size / sizeof(uintptr_t);
+
+  // We don't need a buffer large than Size*10 as the longest variable length
+  // encoding of a 64-bit integer is 10 bytes long.)
+  size_t array_size = std::min(Size * 10, packed_max_size);
+  uint8_t packed[array_size];
+  size_t packed_size =
+      gwp_asan::internal::Pack(reinterpret_cast<const uintptr_t*>(Data),
+                               entries, packed, packed_max_size);
+  if (packed_size > sizeof(packed))
+    __builtin_trap();
+
+  uintptr_t unpacked[std::min(unpacked_max_size, Size)];
+  size_t unpacked_size = gwp_asan::internal::Unpack(
+      packed, packed_size, unpacked, unpacked_max_size);
+  // We can only be sure there was enough room to pack the entire input when
+  // packed_max_size was larger than Size*10.
+  if (packed_max_size > array_size &&
+      unpacked_size != std::min(entries, unpacked_max_size))
+    __builtin_trap();
+  if (memcmp(Data, unpacked, unpacked_size * sizeof(uintptr_t)))
+    __builtin_trap();
+  return 0;
+}
diff --git a/components/gwp_asan/common/pack_stack_trace_unittest.cc b/components/gwp_asan/common/pack_stack_trace_unittest.cc
new file mode 100644
index 0000000..98dbfdc
--- /dev/null
+++ b/components/gwp_asan/common/pack_stack_trace_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/gwp_asan/common/pack_stack_trace.h"
+
+#include "base/test/gtest_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gwp_asan {
+namespace internal {
+
+TEST(PackTest, TrivialExample) {
+  constexpr size_t kTestEntries = 3;
+
+  uintptr_t test[kTestEntries] = {1, 2, 3};
+  uint8_t packed[8];
+  uintptr_t unpacked[kTestEntries];
+
+  const size_t packed_size = Pack(test, kTestEntries, packed, sizeof(packed));
+  ASSERT_EQ(packed_size, 3U);
+  // ZigzagEncode(1) == 2
+  EXPECT_EQ(packed[0], 2U);
+  EXPECT_EQ(packed[1], 2U);
+  EXPECT_EQ(packed[2], 2U);
+  EXPECT_EQ(Unpack(packed, packed_size, unpacked, kTestEntries), kTestEntries);
+  EXPECT_EQ(unpacked[0], 1U);
+  EXPECT_EQ(unpacked[1], 2U);
+  EXPECT_EQ(unpacked[2], 3U);
+}
+
+TEST(PackTest, DecreasingSequence) {
+  constexpr size_t kTestEntries = 3;
+
+  uintptr_t test[kTestEntries] = {3, 2, 1};
+  uint8_t packed[8];
+  uintptr_t unpacked[kTestEntries];
+
+  const size_t packed_size = Pack(test, kTestEntries, packed, sizeof(packed));
+  ASSERT_EQ(packed_size, 3U);
+  // ZigzagEncode(3) == 6
+  // ZigzagEncode(-1) == 1
+  EXPECT_EQ(packed[0], 6U);
+  EXPECT_EQ(packed[1], 1U);
+  EXPECT_EQ(packed[2], 1U);
+  EXPECT_EQ(Unpack(packed, packed_size, unpacked, kTestEntries), kTestEntries);
+  EXPECT_EQ(unpacked[0], 3U);
+  EXPECT_EQ(unpacked[1], 2U);
+  EXPECT_EQ(unpacked[2], 1U);
+}
+
+TEST(PackTest, MultibyteVarInts) {
+  constexpr size_t kTestEntries = 1;
+
+  uintptr_t test[kTestEntries] = {0x40};
+  uint8_t packed[8];
+  uintptr_t unpacked[kTestEntries];
+
+  const size_t packed_size = Pack(test, kTestEntries, packed, sizeof(packed));
+  ASSERT_EQ(packed_size, 2U);
+  // ZigzagEncode(0x40) == 0x80
+  EXPECT_EQ(packed[0], 0x80U);
+  EXPECT_EQ(packed[1], 0x01U);
+  EXPECT_EQ(Unpack(packed, packed_size, unpacked, kTestEntries), kTestEntries);
+  EXPECT_EQ(unpacked[0], 0x40U);
+}
+
+TEST(PackTest, UnpackFailsOnOutOfBoundsVarInt) {
+  uint8_t packed[11] = {0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+                        0x80, 0x80, 0x80, 0x80, 0x00};
+  uintptr_t unpacked[1];
+
+  EXPECT_EQ(Unpack(packed, 11, unpacked, 1), 0U);
+}
+
+TEST(PackTest, UnpackFailsOnBufferTooSmall) {
+  uint8_t packed[2] = {0x80, 0x00};
+  uintptr_t unpacked[2];
+
+  // Fail
+  EXPECT_EQ(Unpack(packed, 1, unpacked, 1), 0U);
+  // Success
+  EXPECT_EQ(Unpack(packed, 2, unpacked, 1), 1U);
+  EXPECT_EQ(Unpack(packed, 2, unpacked, 2), 1U);
+}
+
+TEST(PackTest, PackFailsOnBufferTooSmall) {
+  uintptr_t test[] = {0x40, 0x41};
+  uint8_t packed[4];
+
+  EXPECT_EQ(Pack(test, 2, packed, 1), 0U);
+  EXPECT_EQ(Pack(test, 2, packed, 2), 2U);
+  EXPECT_EQ(Pack(test, 2, packed, 3), 3U);
+  EXPECT_EQ(Pack(test, 2, packed, 4), 3U);
+}
+
+}  // namespace internal
+}  // namespace gwp_asan
diff --git a/components/gwp_asan/common/pack_stack_trace_unpack_fuzzer.cc b/components/gwp_asan/common/pack_stack_trace_unpack_fuzzer.cc
new file mode 100644
index 0000000..c7f807f
--- /dev/null
+++ b/components/gwp_asan/common/pack_stack_trace_unpack_fuzzer.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/gwp_asan/common/pack_stack_trace.h"
+
+#include <algorithm>
+
+// Tests that Unpack() correctly handles arbitrary inputs.
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
+  // The first sizeof(size_t) bytes of the input are treated as the
+  // unpacked_max_size argument to Unpack.
+  if (Size < sizeof(size_t))
+    return 0;
+
+  size_t unpacked_max_size = *reinterpret_cast<const size_t*>(Data);
+  Data += sizeof(size_t);
+  Size -= sizeof(size_t);
+
+  // This should always be large enough to hold the output.
+  uintptr_t unpacked[std::min(Size, unpacked_max_size)];
+  gwp_asan::internal::Unpack(Data, Size, unpacked, unpacked_max_size);
+  return 0;
+}
diff --git a/components/heap_profiling/heap_profiling_test_shim.cc b/components/heap_profiling/heap_profiling_test_shim.cc
index bae0523..ca6efeb9 100644
--- a/components/heap_profiling/heap_profiling_test_shim.cc
+++ b/components/heap_profiling/heap_profiling_test_shim.cc
@@ -19,7 +19,8 @@
 }
 
 HeapProfilingTestShim::HeapProfilingTestShim(JNIEnv* env, jobject obj) {}
-HeapProfilingTestShim::~HeapProfilingTestShim() = default;
+
+HeapProfilingTestShim::~HeapProfilingTestShim() {}
 
 void HeapProfilingTestShim::Destroy(JNIEnv* env,
                                     const JavaParamRef<jobject>& obj) {
@@ -32,7 +33,6 @@
     const base::android::JavaParamRef<jstring>& mode,
     jboolean dynamically_start_profiling,
     const base::android::JavaParamRef<jstring>& stack_mode,
-    jboolean stream_samples,
     jboolean should_sample,
     jboolean sample_everything) {
   heap_profiling::TestDriver driver;
@@ -42,7 +42,6 @@
   options.stack_mode = heap_profiling::ConvertStringToStackMode(
       base::android::ConvertJavaStringToUTF8(stack_mode));
   options.profiling_already_started = !dynamically_start_profiling;
-  options.stream_samples = stream_samples;
   options.should_sample = should_sample;
   options.sample_everything = sample_everything;
   return driver.RunTest(options);
diff --git a/components/heap_profiling/heap_profiling_test_shim.h b/components/heap_profiling/heap_profiling_test_shim.h
index 558933c..134d548 100644
--- a/components/heap_profiling/heap_profiling_test_shim.h
+++ b/components/heap_profiling/heap_profiling_test_shim.h
@@ -23,7 +23,6 @@
       const base::android::JavaParamRef<jstring>& mode,
       jboolean dynamically_start_profiling,
       const base::android::JavaParamRef<jstring>& stack_mode,
-      jboolean stream_samples,
       jboolean should_sample,
       jboolean sample_everything);
 
diff --git a/components/heap_profiling/javatests/src/org/chromium/components/heap_profiling/HeapProfilingTestShim.java b/components/heap_profiling/javatests/src/org/chromium/components/heap_profiling/HeapProfilingTestShim.java
index c7f788e..f55ceaf8 100644
--- a/components/heap_profiling/javatests/src/org/chromium/components/heap_profiling/HeapProfilingTestShim.java
+++ b/components/heap_profiling/javatests/src/org/chromium/components/heap_profiling/HeapProfilingTestShim.java
@@ -23,9 +23,9 @@
      *  rather than native stacks.
      */
     public boolean runTestForMode(String mode, boolean dynamicallyStartProfiling, String stackMode,
-            boolean streamSamples, boolean shouldSample, boolean sampleEverything) {
+            boolean shouldSample, boolean sampleEverything) {
         return nativeRunTestForMode(mNativeHeapProfilingTestShim, mode, dynamicallyStartProfiling,
-                stackMode, streamSamples, shouldSample, sampleEverything);
+                stackMode, shouldSample, sampleEverything);
     }
 
     /**
@@ -43,6 +43,6 @@
     private native long nativeInit();
     private native void nativeDestroy(long nativeHeapProfilingTestShim);
     private native boolean nativeRunTestForMode(long nativeHeapProfilingTestShim, String mode,
-            boolean dynamicallyStartProfiling, String stackMode, boolean streamSamples,
-            boolean shouldSample, boolean sampleEverything);
+            boolean dynamicallyStartProfiling, String stackMode, boolean shouldSample,
+            boolean sampleEverything);
 }
diff --git a/components/heap_profiling/supervisor.cc b/components/heap_profiling/supervisor.cc
index 987902b3..70afb5f 100644
--- a/components/heap_profiling/supervisor.cc
+++ b/components/heap_profiling/supervisor.cc
@@ -60,27 +60,24 @@
 
 void Supervisor::Start(content::ServiceManagerConnection* connection,
                        base::OnceClosure closure) {
-  // TODO(alph): Obtain stream_samples from the command line / Finch.
   Start(connection, GetModeForStartup(), GetStackModeForStartup(),
-        true /* stream_samples */, GetSamplingRateForStartup(),
-        std::move(closure));
+        GetSamplingRateForStartup(), std::move(closure));
 }
 
 void Supervisor::Start(content::ServiceManagerConnection* connection,
                        Mode mode,
                        mojom::StackMode stack_mode,
-                       bool stream_samples,
                        uint32_t sampling_rate,
                        base::OnceClosure closure) {
   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
   DCHECK(!started_);
 
   base::CreateSingleThreadTaskRunnerWithTraits({content::BrowserThread::IO})
-      ->PostTask(FROM_HERE, base::BindOnce(&Supervisor::StartServiceOnIOThread,
-                                           base::Unretained(this),
-                                           connection->GetConnector()->Clone(),
-                                           mode, stack_mode, stream_samples,
-                                           sampling_rate, std::move(closure)));
+      ->PostTask(FROM_HERE,
+                 base::BindOnce(&Supervisor::StartServiceOnIOThread,
+                                base::Unretained(this),
+                                connection->GetConnector()->Clone(), mode,
+                                stack_mode, sampling_rate, std::move(closure)));
 }
 
 Mode Supervisor::GetMode() {
@@ -183,13 +180,12 @@
     std::unique_ptr<service_manager::Connector> connector,
     Mode mode,
     mojom::StackMode stack_mode,
-    bool stream_samples,
     uint32_t sampling_rate,
     base::OnceClosure closure) {
   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
 
-  controller_.reset(new Controller(std::move(connector), stack_mode,
-                                   stream_samples, sampling_rate));
+  controller_.reset(
+      new Controller(std::move(connector), stack_mode, sampling_rate));
   base::WeakPtr<Controller> controller_weak_ptr = controller_->GetWeakPtr();
 
   base::CreateSingleThreadTaskRunnerWithTraits({content::BrowserThread::UI})
diff --git a/components/heap_profiling/supervisor.h b/components/heap_profiling/supervisor.h
index 592b72e..b4b8515 100644
--- a/components/heap_profiling/supervisor.h
+++ b/components/heap_profiling/supervisor.h
@@ -72,7 +72,6 @@
   void Start(content::ServiceManagerConnection* connection,
              Mode mode,
              mojom::StackMode stack_mode,
-             bool stream_samples,
              uint32_t sampling_rate,
              base::OnceClosure callback);
 
@@ -117,7 +116,6 @@
       std::unique_ptr<service_manager::Connector> connector,
       Mode mode,
       mojom::StackMode stack_mode,
-      bool stream_samples,
       uint32_t sampling_rate,
       base::OnceClosure callback);
 
diff --git a/components/heap_profiling/test_driver.cc b/components/heap_profiling/test_driver.cc
index 6799497c..7a6decc 100644
--- a/components/heap_profiling/test_driver.cc
+++ b/components/heap_profiling/test_driver.cc
@@ -559,7 +559,7 @@
                           base::WaitableEvent::InitialState::NOT_SIGNALED) {
   partition_allocator_.init();
 }
-TestDriver::~TestDriver() = default;
+TestDriver::~TestDriver() {}
 
 bool TestDriver::RunTest(const Options& options) {
   options_ = options;
@@ -723,8 +723,8 @@
                                ? (options_.sample_everything ? 2 : kSampleRate)
                                : 1;
   Supervisor::GetInstance()->Start(connection, options_.mode,
-                                   options_.stack_mode, options_.stream_samples,
-                                   sampling_rate, std::move(start_callback));
+                                   options_.stack_mode, sampling_rate,
+                                   std::move(start_callback));
 
   return true;
 }
@@ -776,8 +776,8 @@
                                ? (options_.sample_everything ? 2 : kSampleRate)
                                : 1;
   Supervisor::GetInstance()->Start(connection, options_.mode,
-                                   options_.stack_mode, options_.stream_samples,
-                                   sampling_rate, std::move(start_callback));
+                                   options_.stack_mode, sampling_rate,
+                                   std::move(start_callback));
 
   run_loop->Run();
 
diff --git a/components/heap_profiling/test_driver.h b/components/heap_profiling/test_driver.h
index 03e447b..f897c672 100644
--- a/components/heap_profiling/test_driver.h
+++ b/components/heap_profiling/test_driver.h
@@ -11,7 +11,6 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/synchronization/waitable_event.h"
-#include "components/services/heap_profiling/public/cpp/settings.h"
 #include "components/services/heap_profiling/public/mojom/heap_profiling_client.mojom.h"
 
 namespace base {
@@ -20,6 +19,8 @@
 
 namespace heap_profiling {
 
+enum class Mode;
+
 // This class runs tests for the Heap Profiling Service, a cross-platform,
 // multi-process component.
 //
@@ -44,28 +45,23 @@
  public:
   struct Options {
     // The profiling mode to test.
-    Mode mode = Mode::kBrowser;
+    Mode mode;
 
     // The stack profiling mode to test.
-    mojom::StackMode stack_mode = mojom::StackMode::NATIVE_WITHOUT_THREAD_NAMES;
-
-    // Whether the client should stream samples as they are collected through
-    // the provided pipe. When false the samples are accumulated on the client
-    // side and can be retrieved later.
-    bool stream_samples = true;
+    mojom::StackMode stack_mode;
 
     // Whether the caller has already started profiling with the given mode.
     // When false, the test driver is responsible for starting profiling.
-    bool profiling_already_started = false;
+    bool profiling_already_started;
 
     // Whether to test sampling.
-    bool should_sample = true;
+    bool should_sample;
 
     // When set to true, the internal sampling_rate is set to 2. While this
     // doesn't record all allocations, it should record all test allocations
     // made in this file with exponentially high probability.
     // When set to false, the internal sampling rate is set to 10000.
-    bool sample_everything = false;
+    bool sample_everything;
   };
 
   TestDriver();
diff --git a/components/language/content/browser/geo_language_provider.cc b/components/language/content/browser/geo_language_provider.cc
index d924c95..78b17386 100644
--- a/components/language/content/browser/geo_language_provider.cc
+++ b/components/language/content/browser/geo_language_provider.cc
@@ -14,6 +14,7 @@
 #include "components/prefs/pref_service.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/device/public/mojom/constants.mojom.h"
+#include "services/device/public/mojom/geoposition.mojom.h"
 #include "services/device/public/mojom/public_ip_address_geolocation_provider.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
 
diff --git a/components/language/content/browser/test_utils.h b/components/language/content/browser/test_utils.h
index a0c9593..74310cab 100644
--- a/components/language/content/browser/test_utils.h
+++ b/components/language/content/browser/test_utils.h
@@ -8,6 +8,7 @@
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/device/public/mojom/constants.mojom.h"
 #include "services/device/public/mojom/geolocation.mojom.h"
+#include "services/device/public/mojom/geoposition.mojom.h"
 #include "services/device/public/mojom/public_ip_address_geolocation_provider.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "services/service_manager/public/mojom/connector.mojom.h"
diff --git a/components/mirroring/service/captured_audio_input.cc b/components/mirroring/service/captured_audio_input.cc
index 3f1d02f..c863499 100644
--- a/components/mirroring/service/captured_audio_input.cc
+++ b/components/mirroring/service/captured_audio_input.cc
@@ -5,6 +5,7 @@
 #include "components/mirroring/service/captured_audio_input.h"
 
 #include "base/logging.h"
+#include "media/mojo/interfaces/audio_data_pipe.mojom.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 
 namespace mirroring {
diff --git a/components/mirroring/service/captured_audio_input_unittest.cc b/components/mirroring/service/captured_audio_input_unittest.cc
index 588311f..59541ba 100644
--- a/components/mirroring/service/captured_audio_input_unittest.cc
+++ b/components/mirroring/service/captured_audio_input_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "media/base/audio_parameters.h"
+#include "media/mojo/interfaces/audio_data_pipe.mojom.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "mojo/public/cpp/system/buffer.h"
 #include "mojo/public/cpp/system/platform_handle.h"
diff --git a/components/mirroring/service/fake_video_capture_host.cc b/components/mirroring/service/fake_video_capture_host.cc
index 201edc7..afb423d 100644
--- a/components/mirroring/service/fake_video_capture_host.cc
+++ b/components/mirroring/service/fake_video_capture_host.cc
@@ -6,6 +6,7 @@
 
 #include "base/memory/read_only_shared_memory_region.h"
 #include "media/base/video_frame.h"
+#include "media/capture/mojom/video_capture_types.mojom.h"
 #include "mojo/public/cpp/base/shared_memory_utils.h"
 
 namespace mirroring {
diff --git a/components/mirroring/service/media_remoter.h b/components/mirroring/service/media_remoter.h
index 1910da1..388a530 100644
--- a/components/mirroring/service/media_remoter.h
+++ b/components/mirroring/service/media_remoter.h
@@ -9,6 +9,7 @@
 #include "base/macros.h"
 #include "media/cast/cast_config.h"
 #include "media/mojo/interfaces/remoting.mojom.h"
+#include "media/mojo/interfaces/remoting_common.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 
 namespace media {
diff --git a/components/mirroring/service/session.h b/components/mirroring/service/session.h
index 7ceef26d..c70c62c 100644
--- a/components/mirroring/service/session.h
+++ b/components/mirroring/service/session.h
@@ -23,6 +23,7 @@
 #include "media/cast/cast_environment.h"
 #include "media/cast/net/cast_transport_defines.h"
 #include "media/mojo/interfaces/video_encode_accelerator.mojom.h"
+#include "services/network/public/mojom/network_context.mojom.h"
 
 namespace media {
 class AudioInputDevice;
diff --git a/components/mirroring/service/video_capture_client.cc b/components/mirroring/service/video_capture_client.cc
index 21da6e7..5da9e7f 100644
--- a/components/mirroring/service/video_capture_client.cc
+++ b/components/mirroring/service/video_capture_client.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "media/base/bind_to_current_loop.h"
 #include "media/base/video_frame.h"
+#include "media/capture/mojom/video_capture_types.mojom.h"
 
 namespace mirroring {
 
diff --git a/components/mirroring/service/video_capture_client_unittest.cc b/components/mirroring/service/video_capture_client_unittest.cc
index 365c8c3..8006fc5 100644
--- a/components/mirroring/service/video_capture_client_unittest.cc
+++ b/components/mirroring/service/video_capture_client_unittest.cc
@@ -12,6 +12,7 @@
 #include "components/mirroring/service/fake_video_capture_host.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_frame_metadata.h"
+#include "media/capture/mojom/video_capture_types.mojom.h"
 #include "mojo/public/cpp/base/shared_memory_utils.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "testing/gmock/include/gmock/gmock.h"
diff --git a/components/payments/content/payment_request.cc b/components/payments/content/payment_request.cc
index b410cc7..967a875e 100644
--- a/components/payments/content/payment_request.cc
+++ b/components/payments/content/payment_request.cc
@@ -32,6 +32,7 @@
 
 namespace {
 
+using ::payments::mojom::CanMakePaymentQueryResult;
 using ::payments::mojom::HasEnrolledInstrumentQueryResult;
 
 }  // namespace
@@ -357,7 +358,7 @@
   }
 }
 
-void PaymentRequest::CanMakePayment() {
+void PaymentRequest::CanMakePayment(bool legacy_mode) {
   if (!IsInitialized()) {
     log_.Error("Attempted canMakePayment without initialization");
     OnConnectionTerminated();
@@ -371,11 +372,12 @@
 
   if (!delegate_->GetPrefService()->GetBoolean(kCanMakePaymentEnabled) ||
       !state_) {
-    CanMakePaymentCallback(/*can_make_payment=*/false);
+    CanMakePaymentCallback(legacy_mode, /*can_make_payment=*/false);
   } else {
     state_->CanMakePayment(
+        legacy_mode,
         base::BindOnce(&PaymentRequest::CanMakePaymentCallback,
-                       weak_ptr_factory_.GetWeakPtr()));
+                       weak_ptr_factory_.GetWeakPtr(), legacy_mode));
   }
 }
 
@@ -556,13 +558,33 @@
   }
 }
 
-void PaymentRequest::CanMakePaymentCallback(bool can_make_payment) {
-  client_->OnCanMakePayment(
-      can_make_payment ? mojom::CanMakePaymentQueryResult::CAN_MAKE_PAYMENT
-                       : mojom::CanMakePaymentQueryResult::CANNOT_MAKE_PAYMENT);
+void PaymentRequest::CanMakePaymentCallback(bool legacy_mode,
+                                            bool can_make_payment) {
+  // Only need to enforce query quota in legacy mode. Per-method quota not
+  // supported.
+  if (legacy_mode && spec_ &&
+      !CanMakePaymentQueryFactory::GetInstance()
+           ->GetForContext(web_contents_->GetBrowserContext())
+           ->CanQuery(top_level_origin_, frame_origin_,
+                      spec_->stringified_method_data(),
+                      /*per_method_quota=*/false)) {
+    if (OriginSecurityChecker::IsOriginLocalhostOrFile(frame_origin_)) {
+      client_->OnCanMakePayment(
+          can_make_payment
+              ? CanMakePaymentQueryResult::WARNING_CAN_MAKE_PAYMENT
+              : CanMakePaymentQueryResult::WARNING_CANNOT_MAKE_PAYMENT);
+    } else {
+      client_->OnCanMakePayment(
+          CanMakePaymentQueryResult::QUERY_QUOTA_EXCEEDED);
+    }
+  } else {
+    client_->OnCanMakePayment(
+        can_make_payment
+            ? mojom::CanMakePaymentQueryResult::CAN_MAKE_PAYMENT
+            : mojom::CanMakePaymentQueryResult::CANNOT_MAKE_PAYMENT);
+  }
 
-  // TODO(https://crbug.com/915907): emit JourneyLogger event once the event
-  // names are updated.
+  journey_logger_.SetCanMakePaymentValue(can_make_payment);
 
   if (observer_for_testing_)
     observer_for_testing_->OnCanMakePaymentReturned();
@@ -604,7 +626,7 @@
 
   client_->OnHasEnrolledInstrument(has_enrolled_instrument ? positive
                                                            : negative);
-  journey_logger_.SetCanMakePaymentValue(has_enrolled_instrument);
+  journey_logger_.SetHasEnrolledInstrumentValue(has_enrolled_instrument);
 }
 
 }  // namespace payments
diff --git a/components/payments/content/payment_request.h b/components/payments/content/payment_request.h
index 78b35a0c..ef4236b 100644
--- a/components/payments/content/payment_request.h
+++ b/components/payments/content/payment_request.h
@@ -74,7 +74,7 @@
   void NoUpdatedPaymentDetails() override;
   void Abort() override;
   void Complete(mojom::PaymentComplete result) override;
-  void CanMakePayment() override;
+  void CanMakePayment(bool legacy_mode) override;
   void HasEnrolledInstrument(bool per_method_quota) override;
 
   // PaymentRequestSpec::Observer:
@@ -144,7 +144,7 @@
 
   // The callback for PaymentRequestState::CanMakePayment. Checks for query
   // quota and may send QUERY_QUOTA_EXCEEDED.
-  void CanMakePaymentCallback(bool can_make_payment);
+  void CanMakePaymentCallback(bool legacy_mode, bool can_make_payment);
 
   // The callback for PaymentRequestState::HasEnrolledInstrument. Checks for
   // query quota and may send QUERY_QUOTA_EXCEEDED.
diff --git a/components/payments/content/payment_request_state.cc b/components/payments/content/payment_request_state.cc
index abe7946a..0f6fda0 100644
--- a/components/payments/content/payment_request_state.cc
+++ b/components/payments/content/payment_request_state.cc
@@ -149,8 +149,10 @@
   NotifyOnGetAllPaymentInstrumentsFinished();
 
   // Fulfill the pending CanMakePayment call.
-  if (can_make_payment_callback_)
-    CheckCanMakePayment(std::move(can_make_payment_callback_));
+  if (can_make_payment_callback_) {
+    CheckCanMakePayment(can_make_payment_legacy_mode_,
+                        std::move(can_make_payment_callback_));
+  }
 
   // Fulfill the pending HasEnrolledInstrument call.
   if (has_enrolled_instrument_callback_)
@@ -200,22 +202,38 @@
   UpdateIsReadyToPayAndNotifyObservers();
 }
 
-void PaymentRequestState::CanMakePayment(StatusCallback callback) {
+void PaymentRequestState::CanMakePayment(bool legacy_mode,
+                                         StatusCallback callback) {
   if (!get_all_instruments_finished_) {
     DCHECK(!can_make_payment_callback_);
     can_make_payment_callback_ = std::move(callback);
+    can_make_payment_legacy_mode_ = legacy_mode;
     return;
   }
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&PaymentRequestState::CheckCanMakePayment,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+      FROM_HERE, base::BindOnce(&PaymentRequestState::CheckCanMakePayment,
+                                weak_ptr_factory_.GetWeakPtr(), legacy_mode,
+                                std::move(callback)));
 }
 
-void PaymentRequestState::CheckCanMakePayment(StatusCallback callback) {
+void PaymentRequestState::CheckCanMakePayment(bool legacy_mode,
+                                              StatusCallback callback) {
   DCHECK(get_all_instruments_finished_);
-  std::move(callback).Run(are_requested_methods_supported_);
+  if (!legacy_mode) {
+    std::move(callback).Run(are_requested_methods_supported_);
+    return;
+  }
+
+  // Legacy mode: fall back to also checking if an instrument is enrolled.
+  bool can_make_payment_value = false;
+  for (const auto& instrument : available_instruments_) {
+    if (instrument->IsValidForCanMakePayment()) {
+      can_make_payment_value = true;
+      break;
+    }
+  }
+  std::move(callback).Run(can_make_payment_value);
 }
 
 void PaymentRequestState::HasEnrolledInstrument(StatusCallback callback) {
@@ -232,14 +250,8 @@
 }
 
 void PaymentRequestState::CheckHasEnrolledInstrument(StatusCallback callback) {
-  bool has_enrolled_instrument_value = false;
-  for (const auto& instrument : available_instruments_) {
-    if (instrument->IsValidForCanMakePayment()) {
-      has_enrolled_instrument_value = true;
-      break;
-    }
-  }
-  std::move(callback).Run(has_enrolled_instrument_value);
+  // TODO(https://crbug.com/915907): Implement hasEnrolledInstrument.
+  NOTREACHED();
 }
 
 void PaymentRequestState::AreRequestedMethodsSupported(
diff --git a/components/payments/content/payment_request_state.h b/components/payments/content/payment_request_state.h
index 8923798..f149273 100644
--- a/components/payments/content/payment_request_state.h
+++ b/components/payments/content/payment_request_state.h
@@ -99,7 +99,8 @@
   // Checks whether support for the specified payment methods exist, either
   // because the user has a registered payment handler or because the browser
   // can do just-in-time registration for a suitable payment handler.
-  void CanMakePayment(StatusCallback callback);
+  // If |legacy_mode| is true, then also checks that an instrument is enrolled.
+  void CanMakePayment(bool legacy_mode, StatusCallback callback);
 
   // Checks whether the user has at least one instrument that satisfies the
   // specified supported payment methods asynchronously.
@@ -263,7 +264,7 @@
 
   // Checks whether support for the specified payment methods exists and call
   // the |callback| to return the result.
-  void CheckCanMakePayment(StatusCallback callback);
+  void CheckCanMakePayment(bool legacy_mode, StatusCallback callback);
 
   // Checks whether the user has at least one instrument that satisfies the
   // specified supported payment methods and call the |callback| to return the
@@ -292,6 +293,10 @@
   autofill::PersonalDataManager* personal_data_manager_;
   JourneyLogger* journey_logger_;
 
+  // Whether |can_make_payment_callback_| expects the legacy canMakePayment
+  // semantic. Only meaningful when |can_make_payment_callback_| is present.
+  bool can_make_payment_legacy_mode_ = false;
+
   StatusCallback can_make_payment_callback_;
   StatusCallback has_enrolled_instrument_callback_;
   StatusCallback are_requested_methods_supported_callback_;
diff --git a/components/payments/content/payment_request_state_unittest.cc b/components/payments/content/payment_request_state_unittest.cc
index 6a986456..ffbecc82 100644
--- a/components/payments/content/payment_request_state_unittest.cc
+++ b/components/payments/content/payment_request_state_unittest.cc
@@ -140,20 +140,22 @@
   autofill::CreditCard credit_card_visa_;
 };
 
-TEST_F(PaymentRequestStateTest, HasEnrolledInstrument) {
+TEST_F(PaymentRequestStateTest, CanMakePayment) {
   // Default options.
   RecreateStateWithOptions(mojom::PaymentOptions::New());
 
-  // HasEnrolledInstrument returns true because the method data requires Visa,
+  // Legacy CanMakePayment returns true because the method data requires Visa,
   // and the user has a Visa card on file.
-  state()->HasEnrolledInstrument(
-      base::BindOnce([](bool has_enrolled_instrument) {
-        EXPECT_TRUE(has_enrolled_instrument);
+  state()->CanMakePayment(
+      /*legacy_mode=*/true, base::BindOnce([](bool can_make_payment) {
+        EXPECT_TRUE(can_make_payment);
       }));
 
   // CanMakePayment returns true because the requested method is supported.
-  state()->CanMakePayment(base::BindOnce(
-      [](bool can_make_payment) { EXPECT_TRUE(can_make_payment); }));
+  state()->CanMakePayment(/*legacy_mode=*/false,
+                          base::BindOnce([](bool can_make_payment) {
+                            EXPECT_TRUE(can_make_payment);
+                          }));
 }
 
 TEST_F(PaymentRequestStateTest, CanMakePayment_NoEnrolledInstrument) {
@@ -166,17 +168,19 @@
                                      mojom::PaymentDetails::New(),
                                      std::move(method_data));
 
-  // HasEnrolledInstrument returns false because the method data requires
+  // Legacy CanMakePayment returns false because the method data requires
   // MasterCard, and the user doesn't have such an instrument.
-  state()->HasEnrolledInstrument(
-      base::BindOnce([](bool has_enrolled_instrument) {
-        EXPECT_FALSE(has_enrolled_instrument);
+  state()->CanMakePayment(
+      /*legacy_mode=*/true, base::BindOnce([](bool can_make_payment) {
+        EXPECT_FALSE(can_make_payment);
       }));
 
   // CanMakePayment returns true because the requested method is supported, even
   // though the payment instrument is not ready to pay.
-  state()->CanMakePayment(base::BindOnce(
-      [](bool can_make_payment) { EXPECT_TRUE(can_make_payment); }));
+  state()->CanMakePayment(/*legacy_mode=*/false,
+                          base::BindOnce([](bool can_make_payment) {
+                            EXPECT_TRUE(can_make_payment);
+                          }));
 }
 
 TEST_F(PaymentRequestStateTest, CanMakePayment_UnsupportedPaymentMethod) {
@@ -188,20 +192,22 @@
                                      mojom::PaymentDetails::New(),
                                      std::move(method_data));
 
-  // HasEnrolledInstrument returns false because the method data requires
+  // Legacy CanMakePayment returns false because the method data requires
   // MasterCard, and the user doesn't have such an instrument.
-  state()->HasEnrolledInstrument(
-      base::BindOnce([](bool has_enrolled_instrument) {
-        EXPECT_FALSE(has_enrolled_instrument);
+  state()->CanMakePayment(
+      /*legacy_mode=*/true, base::BindOnce([](bool can_make_payment) {
+        EXPECT_FALSE(can_make_payment);
       }));
 
   // CanMakePayment returns true because the requested method is supported, even
   // though the payment instrument is not ready to pay.
-  state()->CanMakePayment(base::BindOnce(
-      [](bool can_make_payment) { EXPECT_FALSE(can_make_payment); }));
+  state()->CanMakePayment(/*legacy_mode=*/false,
+                          base::BindOnce([](bool can_make_payment) {
+                            EXPECT_FALSE(can_make_payment);
+                          }));
 }
 
-TEST_F(PaymentRequestStateTest, HasEnrolledInstrument_OnlyBasicCard) {
+TEST_F(PaymentRequestStateTest, CanMakePayment_OnlyBasicCard) {
   // The method data supports everything in basic-card.
   mojom::PaymentMethodDataPtr entry = mojom::PaymentMethodData::New();
   entry->supported_method = "basic-card";
@@ -211,20 +217,21 @@
                                      mojom::PaymentDetails::New(),
                                      std::move(method_data));
 
-  // HasEnrolledInstrument returns true because the method data supports
+  // Legacy CanMakePayment returns true because the method data supports
   // everything, and the user has at least one instrument.
-  state()->HasEnrolledInstrument(
-      base::BindOnce([](bool has_enrolled_instrument) {
-        EXPECT_TRUE(has_enrolled_instrument);
+  state()->CanMakePayment(
+      /*legacy_mode=*/true, base::BindOnce([](bool can_make_payment) {
+        EXPECT_TRUE(can_make_payment);
       }));
 
   // CanMakePayment returns true because the requested method is supported.
-  state()->CanMakePayment(base::BindOnce(
-      [](bool can_make_payment) { EXPECT_TRUE(can_make_payment); }));
+  state()->CanMakePayment(
+      /*legacy_mode=*/false, base::BindOnce([](bool can_make_payment) {
+        EXPECT_TRUE(can_make_payment);
+      }));
 }
 
-TEST_F(PaymentRequestStateTest,
-       HasEnrolledInstrument_BasicCard_SpecificAvailable) {
+TEST_F(PaymentRequestStateTest, CanMakePayment_BasicCard_SpecificAvailable) {
   // The method data supports visa through basic-card.
   mojom::PaymentMethodDataPtr entry = mojom::PaymentMethodData::New();
   entry->supported_method = "basic-card";
@@ -235,20 +242,22 @@
                                      mojom::PaymentDetails::New(),
                                      std::move(method_data));
 
-  // HasEnrolledInstrument returns true because the method data supports visa,
+  // Legacy CanMakePayment returns true because the method data supports visa,
   // and the user has a Visa instrument.
-  state()->HasEnrolledInstrument(
-      base::BindOnce([](bool has_enrolled_instrument) {
-        EXPECT_TRUE(has_enrolled_instrument);
+  state()->CanMakePayment(
+      /*legacy_mode=*/true, base::BindOnce([](bool can_make_payment) {
+        EXPECT_TRUE(can_make_payment);
       }));
 
   // CanMakePayment returns true because the requested method is supported.
-  state()->CanMakePayment(base::BindOnce(
-      [](bool can_make_payment) { EXPECT_TRUE(can_make_payment); }));
+  state()->CanMakePayment(
+      /*legacy_mode=*/false, base::BindOnce([](bool can_make_payment) {
+        EXPECT_TRUE(can_make_payment);
+      }));
 }
 
 TEST_F(PaymentRequestStateTest,
-       HasEnrolledInstrument_BasicCard_SpecificAvailableButInvalid) {
+       CanMakePayment_BasicCard_SpecificAvailableButInvalid) {
   // The method data supports jcb through basic-card.
   mojom::PaymentMethodDataPtr entry = mojom::PaymentMethodData::New();
   entry->supported_method = "basic-card";
@@ -259,21 +268,22 @@
                                      mojom::PaymentDetails::New(),
                                      std::move(method_data));
 
-  // HasEnrolledInstrument returns false because the method data supports jcb,
+  // Legacy CanMakePayment returns false because the method data supports jcb,
   // and the user has a JCB instrument, but it's invalid.
-  state()->HasEnrolledInstrument(
-      base::BindOnce([](bool has_enrolled_instrument) {
-        EXPECT_FALSE(has_enrolled_instrument);
+  state()->CanMakePayment(
+      /*legacy_mode=*/true, base::BindOnce([](bool can_make_payment) {
+        EXPECT_FALSE(can_make_payment);
       }));
 
   // CanMakePayment returns true because the requested method is supported, even
   // though there is no enrolled instrument.
-  state()->CanMakePayment(base::BindOnce(
-      [](bool can_make_payment) { EXPECT_TRUE(can_make_payment); }));
+  state()->CanMakePayment(
+      /*legacy_mode=*/false, base::BindOnce([](bool can_make_payment) {
+        EXPECT_TRUE(can_make_payment);
+      }));
 }
 
-TEST_F(PaymentRequestStateTest,
-       HasEnrolledInstrument_BasicCard_SpecificUnavailable) {
+TEST_F(PaymentRequestStateTest, CanMakePayment_BasicCard_SpecificUnavailable) {
   // The method data supports mastercard through basic-card.
   mojom::PaymentMethodDataPtr entry = mojom::PaymentMethodData::New();
   entry->supported_method = "basic-card";
@@ -284,17 +294,19 @@
                                      mojom::PaymentDetails::New(),
                                      std::move(method_data));
 
-  // HasEnrolledInstrument returns false because the method data supports
+  // Legacy CanMakePayment returns false because the method data supports
   // mastercard, and the user doesn't have such an instrument.
-  state()->HasEnrolledInstrument(
-      base::BindOnce([](bool has_enrolled_instrument) {
-        EXPECT_FALSE(has_enrolled_instrument);
+  state()->CanMakePayment(
+      /*legacy_mode=*/true, base::BindOnce([](bool can_make_payment) {
+        EXPECT_FALSE(can_make_payment);
       }));
 
   // CanMakePayment returns true because the requested method is supported, even
   // though there is no enrolled instrument.
-  state()->CanMakePayment(base::BindOnce(
-      [](bool can_make_payment) { EXPECT_TRUE(can_make_payment); }));
+  state()->CanMakePayment(
+      /*legacy_mode=*/false, base::BindOnce([](bool can_make_payment) {
+        EXPECT_TRUE(can_make_payment);
+      }));
 }
 
 TEST_F(PaymentRequestStateTest, ReadyToPay_DefaultSelections) {
diff --git a/components/payments/core/journey_logger.cc b/components/payments/core/journey_logger.cc
index 307337f6..8b7ed73 100644
--- a/components/payments/core/journey_logger.cc
+++ b/components/payments/core/journey_logger.cc
@@ -98,6 +98,14 @@
                          : EVENT_CAN_MAKE_PAYMENT_FALSE);
 }
 
+void JourneyLogger::SetHasEnrolledInstrumentValue(bool value) {
+  if (is_incognito_)
+    return;
+
+  SetEventOccurred(value ? EVENT_HAS_ENROLLED_INSTRUMENT_TRUE
+                         : EVENT_HAS_ENROLLED_INSTRUMENT_FALSE);
+}
+
 void JourneyLogger::SetEventOccurred(Event event) {
   events_ |= event;
 }
diff --git a/components/payments/core/journey_logger.h b/components/payments/core/journey_logger.h
index 7db5ce3..87e94f2 100644
--- a/components/payments/core/journey_logger.h
+++ b/components/payments/core/journey_logger.h
@@ -91,6 +91,11 @@
     EVENT_SELECTED_CREDIT_CARD = 1 << 18,
     EVENT_SELECTED_GOOGLE = 1 << 19,
     EVENT_SELECTED_OTHER = 1 << 20,
+    // hasEnrolledInstrument was called with a result of "true" or "false",
+    // respectively. An absence of both events means hasEnrolledInstrument was
+    // not called, or the user was in incognito mode.
+    EVENT_HAS_ENROLLED_INSTRUMENT_TRUE = 1 << 21,
+    EVENT_HAS_ENROLLED_INSTRUMENT_FALSE = 1 << 22,
     EVENT_ENUM_MAX = 2097152,
   };
 
@@ -140,10 +145,14 @@
                                    int number,
                                    bool has_valid_suggestion);
 
-  // Records the fact that the merchant called CanMakePayment and records it's
+  // Records the fact that the merchant called CanMakePayment and records its
   // return value.
   void SetCanMakePaymentValue(bool value);
 
+  // Records the fact that the merchant called HasEnrolledInstrument and records
+  // its return value.
+  void SetHasEnrolledInstrumentValue(bool value);
+
   // Records that an event occurred.
   void SetEventOccurred(Event event);
 
diff --git a/components/rlz/rlz_tracker.cc b/components/rlz/rlz_tracker.cc
index 204a6c6..184cef8 100644
--- a/components/rlz/rlz_tracker.cc
+++ b/components/rlz/rlz_tracker.cc
@@ -22,6 +22,7 @@
 #include "build/build_config.h"
 #include "components/rlz/rlz_tracker_delegate.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
 
 #if defined(OS_CHROMEOS)
 #include "base/syslog_logging.h"
diff --git a/components/services/filesystem/directory_impl_unittest.cc b/components/services/filesystem/directory_impl_unittest.cc
index ef2a1ae..3a5c9c3 100644
--- a/components/services/filesystem/directory_impl_unittest.cc
+++ b/components/services/filesystem/directory_impl_unittest.cc
@@ -10,6 +10,7 @@
 
 #include "base/stl_util.h"
 #include "components/services/filesystem/files_test_base.h"
+#include "components/services/filesystem/public/interfaces/directory.mojom.h"
 
 namespace filesystem {
 namespace {
diff --git a/components/services/filesystem/file_impl_unittest.cc b/components/services/filesystem/file_impl_unittest.cc
index 1aab2bb3..f43e30422 100644
--- a/components/services/filesystem/file_impl_unittest.cc
+++ b/components/services/filesystem/file_impl_unittest.cc
@@ -9,6 +9,7 @@
 
 #include "base/files/file.h"
 #include "components/services/filesystem/files_test_base.h"
+#include "components/services/filesystem/public/interfaces/directory.mojom.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "mojo/public/cpp/bindings/type_converter.h"
 
diff --git a/components/services/heap_profiling/connection_manager.cc b/components/services/heap_profiling/connection_manager.cc
index 2b8f8fd..2a115e1 100644
--- a/components/services/heap_profiling/connection_manager.cc
+++ b/components/services/heap_profiling/connection_manager.cc
@@ -5,7 +5,6 @@
 #include "components/services/heap_profiling/connection_manager.h"
 
 #include "base/bind.h"
-#include "base/json/string_escape.h"
 #include "base/message_loop/message_loop.h"
 #include "base/message_loop/message_loop_current.h"
 #include "base/metrics/histogram_macros.h"
@@ -75,15 +74,13 @@
              scoped_refptr<ReceiverPipe> p,
              mojom::ProcessType process_type,
              uint32_t sampling_rate,
-             mojom::StackMode stack_mode,
-             bool stream_samples)
+             mojom::StackMode stack_mode)
       : thread(base::StringPrintf("Sender %lld thread",
                                   static_cast<long long>(pid))),
         client(std::move(client)),
         pipe(p),
         process_type(process_type),
         stack_mode(stack_mode),
-        stream_samples(stream_samples),
         tracker(std::move(complete_cb), backtrace_storage),
         sampling_rate(sampling_rate) {}
 
@@ -106,7 +103,6 @@
   scoped_refptr<StreamParser> parser;
   mojom::ProcessType process_type;
   mojom::StackMode stack_mode;
-  bool stream_samples;
 
   // Danger: This lives on the |thread| member above. The connection manager
   // lives on the I/O thread, so accesses to the variable must be synchronized.
@@ -163,8 +159,7 @@
 
   auto connection = std::make_unique<Connection>(
       std::move(complete_cb), &backtrace_storage_, pid, std::move(client),
-      new_pipe, process_type, params->sampling_rate, params->stack_mode,
-      params->stream_samples);
+      new_pipe, process_type, params->sampling_rate, params->stack_mode);
 
   base::Thread::Options options;
   options.message_loop_type = base::MessageLoop::TYPE_IO;
@@ -259,13 +254,6 @@
   for (auto& it : connections_) {
     base::ProcessId pid = it.first;
     Connection* connection = it.second.get();
-    if (!connection->stream_samples) {
-      connection->client->RetrieveHeapProfile(base::BindOnce(
-          &ConnectionManager::HeapProfileRetrieved, weak_factory_.GetWeakPtr(),
-          tracking, pid, connection->process_type, keep_small_allocations,
-          strip_path_from_mapped_files, connection->sampling_rate));
-      continue;
-    }
     int barrier_id = next_barrier_id_++;
 
     // Register for callback before requesting the dump so we don't race for the
@@ -282,65 +270,6 @@
   }
 }
 
-void ConnectionManager::HeapProfileRetrieved(
-    scoped_refptr<DumpProcessesForTracingTracking> tracking,
-    base::ProcessId pid,
-    mojom::ProcessType process_type,
-    bool keep_small_allocations,
-    bool strip_path_from_mapped_files,
-    uint32_t sampling_rate,
-    mojom::HeapProfilePtr profile) {
-  AllocationCountMap counts;
-  AllocationTracker::ContextMap context_map;
-  AllocationTracker::AddressToStringMap string_map;
-  BacktraceStorage backtrace_storage;
-  BacktraceStorage::Lock backtrace_storage_lock(&backtrace_storage);
-
-  bool success = true;
-  for (const mojom::HeapProfileSamplePtr& sample : profile->samples) {
-    int context_id = 0;
-    if (sample->context_id) {
-      auto it = profile->strings.find(sample->context_id);
-      if (it == profile->strings.end()) {
-        success = false;
-        break;
-      }
-      const std::string& context = it->second;
-      // Escape the strings early, to simplify exporting a heap dump.
-      std::string escaped_context;
-      base::EscapeJSONString(context, false /* put_in_quotes */,
-                             &escaped_context);
-      context_id = context_map
-                       .emplace(std::move(escaped_context),
-                                static_cast<int>(context_map.size() + 1))
-                       .first->second;
-    }
-    const Backtrace* backtrace = backtrace_storage.Insert(
-        std::vector<Address>(sample->stack.begin(), sample->stack.end()));
-    AllocatorType allocator = static_cast<AllocatorType>(sample->allocator);
-    if (allocator >= AllocatorType::kCount) {
-      success = false;
-      break;
-    }
-    AllocationEvent alloc(allocator, Address(0), sample->size, backtrace,
-                          context_id);
-    ++counts[alloc];
-  }
-
-  for (const auto& str : profile->strings) {
-    std::string quoted_string;
-    // Escape the strings before saving them, to simplify exporting a heap dump.
-    base::EscapeJSONString(str.second, false /* put_in_quotes */,
-                           &quoted_string);
-    string_map.emplace(str.first, std::move(quoted_string));
-  }
-
-  DoDumpOneProcessForTracing(
-      tracking, pid, process_type, keep_small_allocations,
-      strip_path_from_mapped_files, sampling_rate, success, std::move(counts),
-      std::move(context_map), std::move(string_map));
-}
-
 void ConnectionManager::DoDumpOneProcessForTracing(
     scoped_refptr<DumpProcessesForTracingTracking> tracking,
     base::ProcessId pid,
diff --git a/components/services/heap_profiling/connection_manager.h b/components/services/heap_profiling/connection_manager.h
index 6571f9f..6c746b3 100644
--- a/components/services/heap_profiling/connection_manager.h
+++ b/components/services/heap_profiling/connection_manager.h
@@ -88,15 +88,6 @@
   struct Connection;
   struct DumpProcessesForTracingTracking;
 
-  void HeapProfileRetrieved(
-      scoped_refptr<DumpProcessesForTracingTracking> tracking,
-      base::ProcessId pid,
-      mojom::ProcessType process_type,
-      bool keep_small_allocations,
-      bool strip_path_from_mapped_files,
-      uint32_t sampling_rate,
-      mojom::HeapProfilePtr profile);
-
   void DoDumpOneProcessForTracing(
       scoped_refptr<DumpProcessesForTracingTracking> tracking,
       base::ProcessId pid,
diff --git a/components/services/heap_profiling/public/cpp/client.cc b/components/services/heap_profiling/public/cpp/client.cc
index 39cd6c1d..020ef5e 100644
--- a/components/services/heap_profiling/public/cpp/client.cc
+++ b/components/services/heap_profiling/public/cpp/client.cc
@@ -128,10 +128,6 @@
   SamplingProfilerWrapper::FlushPipe(barrier_id);
 }
 
-void Client::RetrieveHeapProfile(RetrieveHeapProfileCallback callback) {
-  std::move(callback).Run(sampling_profiler_->RetrieveHeapProfile());
-}
-
 void Client::StartProfilingInternal(mojom::ProfilingParamsPtr params) {
   sampling_profiler_->StartProfiling(sender_pipe_.get(), std::move(params));
 }
diff --git a/components/services/heap_profiling/public/cpp/client.h b/components/services/heap_profiling/public/cpp/client.h
index 23abac2..e06103c7 100644
--- a/components/services/heap_profiling/public/cpp/client.h
+++ b/components/services/heap_profiling/public/cpp/client.h
@@ -27,7 +27,6 @@
   // mojom::ProfilingClient overrides:
   void StartProfiling(mojom::ProfilingParamsPtr params) override;
   void FlushMemlogPipe(uint32_t barrier_id) override;
-  void RetrieveHeapProfile(RetrieveHeapProfileCallback callback) override;
 
   void BindToInterface(mojom::ProfilingClientRequest request);
 
diff --git a/components/services/heap_profiling/public/cpp/controller.cc b/components/services/heap_profiling/public/cpp/controller.cc
index 8ab69c5..afb48330 100644
--- a/components/services/heap_profiling/public/cpp/controller.cc
+++ b/components/services/heap_profiling/public/cpp/controller.cc
@@ -16,12 +16,10 @@
 
 Controller::Controller(std::unique_ptr<service_manager::Connector> connector,
                        mojom::StackMode stack_mode,
-                       bool stream_samples,
                        uint32_t sampling_rate)
     : connector_(std::move(connector)),
       sampling_rate_(sampling_rate),
       stack_mode_(stack_mode),
-      stream_samples_(stream_samples),
       weak_factory_(this) {
   DCHECK_NE(sampling_rate, 0u);
 
@@ -52,7 +50,6 @@
 
   mojom::ProfilingParamsPtr params = mojom::ProfilingParams::New();
   params->sampling_rate = sampling_rate_;
-  params->stream_samples = stream_samples_;
   params->sender_pipe = mojo::WrapPlatformHandle(pipes.PassSender());
   params->stack_mode = stack_mode_;
   heap_profiling_service_->AddProfilingClient(
diff --git a/components/services/heap_profiling/public/cpp/controller.h b/components/services/heap_profiling/public/cpp/controller.h
index 969f89e..ab077b0d 100644
--- a/components/services/heap_profiling/public/cpp/controller.h
+++ b/components/services/heap_profiling/public/cpp/controller.h
@@ -42,7 +42,6 @@
   // named |sampling_interval|.
   Controller(std::unique_ptr<service_manager::Connector> connector,
              mojom::StackMode stack_mode,
-             bool stream_samples,
              uint32_t sampling_rate);
   ~Controller();
 
@@ -72,7 +71,6 @@
   // The same sampling rate and stack mode is used for each client.
   const uint32_t sampling_rate_ = 1;
   const mojom::StackMode stack_mode_;
-  const bool stream_samples_;
 
   SEQUENCE_CHECKER(sequence_checker_);
   base::WeakPtrFactory<Controller> weak_factory_;
diff --git a/components/services/heap_profiling/public/cpp/sampling_profiler_wrapper.cc b/components/services/heap_profiling/public/cpp/sampling_profiler_wrapper.cc
index 25fbc910..b97ec3c5 100644
--- a/components/services/heap_profiling/public/cpp/sampling_profiler_wrapper.cc
+++ b/components/services/heap_profiling/public/cpp/sampling_profiler_wrapper.cc
@@ -4,8 +4,6 @@
 
 #include "components/services/heap_profiling/public/cpp/sampling_profiler_wrapper.h"
 
-#include <utility>
-
 #include "base/allocator/buildflags.h"
 #include "base/atomicops.h"
 #include "base/bind.h"
@@ -426,12 +424,8 @@
     *context = allocation_context.type_name;
 }
 
-// Captures up to |max_entries| stack frames using the buffer pointed by
-// |frames|. Puts the number of captured frames into the |count| output
-// parameters. Returns the pointer to the topmost frame.
-const void** CaptureStackTrace(const void** frames,
-                               size_t max_entries,
-                               size_t* count) {
+void SerializeFramesFromBacktrace(FrameSerializer* serializer,
+                                  const char** context) {
   // Skip 3 top frames related to the profiler itself, e.g.:
   //   base::debug::StackTrace::StackTrace
   //   heap_profiling::RecordAndSendAlloc
@@ -439,33 +433,26 @@
   size_t skip_frames = 3;
 #if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) && \
     defined(OFFICIAL_BUILD)
+  const void* frames[kMaxStackEntries - 1];
   size_t frame_count =
       base::trace_event::CFIBacktraceAndroid::GetInitializedInstance()->Unwind(
-          frames, max_entries);
+          frames, kMaxStackEntries - 1);
 #elif BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
-  size_t frame_count =
-      base::debug::TraceStackFramePointers(frames, max_entries, skip_frames);
+  const void* frames[kMaxStackEntries - 1];
+  size_t frame_count = base::debug::TraceStackFramePointers(
+      frames, kMaxStackEntries - 1, skip_frames);
   skip_frames = 0;
 #else
-  // Fall-back to capturing the stack with base::debug::CollectStackTrace,
+  // Fall-back to capturing the stack with base::debug::StackTrace,
   // which is likely slower, but more reliable.
-  // TODO(alph): Make CollectStackTrace accept const void** pointer.
-  size_t frame_count =
-      base::debug::CollectStackTrace(const_cast<void**>(frames), max_entries);
+  base::debug::StackTrace stack_trace(kMaxStackEntries - 1);
+  size_t frame_count = 0u;
+  const void* const* frames = stack_trace.Addresses(&frame_count);
 #endif
 
   skip_frames = std::min(skip_frames, frame_count);
-  *count = frame_count - skip_frames;
-  return frames + skip_frames;
-}
-
-void SerializeFramesFromBacktrace(FrameSerializer* serializer,
-                                  const char** context) {
-  const void* frames[kMaxStackEntries];
-  size_t frames_count;
-  const void** first_frame =
-      CaptureStackTrace(frames, kMaxStackEntries - 1, &frames_count);
-  serializer->AddAllInstructionPointers(frames_count, first_frame);
+  serializer->AddAllInstructionPointers(frame_count - skip_frames,
+                                        frames + skip_frames);
 
   // Both thread name and task context require access to TLS.
   if (ScopedAllowAlloc::HasTLSBeenDestroyed())
@@ -618,14 +605,9 @@
   base::PoissonAllocationSampler::Get()->RemoveSamplesObserver(this);
 }
 
-SamplingProfilerWrapper::Sample::Sample() = default;
-SamplingProfilerWrapper::Sample::Sample(Sample&&) = default;
-SamplingProfilerWrapper::Sample::~Sample() = default;
-
 void SamplingProfilerWrapper::StartProfiling(SenderPipe* sender_pipe,
                                              mojom::ProfilingParamsPtr params) {
   size_t sampling_rate = params->sampling_rate;
-  stream_samples_ = params->stream_samples;
   InitAllocationRecorder(sender_pipe, std::move(params));
   auto* sampler = base::PoissonAllocationSampler::Get();
   sampler->SetSamplingInterval(sampling_rate);
@@ -637,128 +619,17 @@
   base::PoissonAllocationSampler::Get()->Stop();
 }
 
-mojom::HeapProfilePtr SamplingProfilerWrapper::RetrieveHeapProfile() {
-  base::PoissonAllocationSampler::ScopedMuteThreadSamples no_samples_scope;
-  base::AutoLock lock(mutex_);
-  mojom::HeapProfilePtr profile = mojom::HeapProfile::New();
-  profile->samples.reserve(samples_.size());
-  for (const auto& pair : samples_) {
-    auto mojo_sample = mojom::HeapProfileSample::New();
-    mojo_sample->allocator = static_cast<uint32_t>(pair.second.allocator);
-    mojo_sample->size = pair.second.size;
-    mojo_sample->context_id = reinterpret_cast<uintptr_t>(pair.second.context);
-    mojo_sample->stack = pair.second.stack;
-    profile->samples.push_back(std::move(mojo_sample));
-  }
-  profile->strings.reserve(strings_.size());
-  for (const char* string : strings_)
-    profile->strings.emplace(reinterpret_cast<uintptr_t>(string), string);
-  return profile;
-}
-
-// The PoissonAllocationSampler that invokes this method guarantees
-// non-reentrancy, i.e. no allocations made within the scope of SampleAdded
-// will produce a sample.
 void SamplingProfilerWrapper::SampleAdded(
     void* address,
     size_t size,
     size_t total,
     base::PoissonAllocationSampler::AllocatorType type,
     const char* context) {
-  DCHECK(base::PoissonAllocationSampler::ScopedMuteThreadSamples::IsMuted());
-  if (stream_samples_) {
-    RecordAndSendAlloc(ConvertType(type), address, size, context);
-    return;
-  }
-  base::AutoLock lock(mutex_);
-  Sample sample;
-  sample.allocator = ConvertType(type);
-  sample.size = size;
-  CaptureMode capture_mode = AllocationContextTracker::capture_mode();
-  if (capture_mode == CaptureMode::PSEUDO_STACK ||
-      capture_mode == CaptureMode::MIXED_STACK) {
-    CaptureMixedStack(context, &sample);
-  } else {
-    CaptureNativeStack(context, &sample);
-  }
-  samples_.emplace(address, std::move(sample));
-}
-
-void SamplingProfilerWrapper::CaptureMixedStack(const char* context,
-                                                Sample* sample) {
-  // Allocation context is tracked in TLS. Return nothing if TLS was destroyed.
-  if (ScopedAllowAlloc::HasTLSBeenDestroyed())
-    return;
-  auto* tracker = AllocationContextTracker::GetInstanceForCurrentThread();
-  if (!tracker)
-    return;
-
-  AllocationContext allocation_context;
-  if (!tracker->GetContextSnapshot(&allocation_context))
-    return;
-
-  const base::trace_event::Backtrace& backtrace = allocation_context.backtrace;
-  CHECK_LE(backtrace.frame_count, kMaxStackEntries);
-  std::vector<uint64_t> stack;
-  stack.reserve(backtrace.frame_count);
-  for (int i = base::checked_cast<int>(backtrace.frame_count) - 1; i >= 0;
-       --i) {
-    const base::trace_event::StackFrame& frame = backtrace.frames[i];
-    if (frame.type != base::trace_event::StackFrame::Type::PROGRAM_COUNTER) {
-      const char* name = static_cast<const char*>(frame.value);
-      if (strings_.find(name) == strings_.end())
-        strings_.emplace(name);
-    }
-    stack.push_back(reinterpret_cast<uintptr_t>(frame.value));
-  }
-  sample->stack = std::move(stack);
-  if (!context)
-    context = allocation_context.type_name;
-  sample->context = RecordString(context);
-}
-
-void SamplingProfilerWrapper::CaptureNativeStack(const char* context,
-                                                 Sample* sample) {
-  const void* stack[kMaxStackEntries];
-  size_t frame_count;
-  // One frame is reserved for the thread name.
-  const void** first_frame =
-      CaptureStackTrace(stack, kMaxStackEntries - 1, &frame_count);
-  DCHECK_LT(frame_count, kMaxStackEntries);
-  sample->stack.reserve(frame_count + (g_include_thread_names ? 1 : 0));
-  sample->stack.insert(sample->stack.end(),
-                       reinterpret_cast<uint64_t*>(first_frame),
-                       reinterpret_cast<uint64_t*>(first_frame + frame_count));
-
-  // Both thread name and task context require access to TLS.
-  if (ScopedAllowAlloc::HasTLSBeenDestroyed())
-    return;
-
-  if (g_include_thread_names) {
-    sample->stack.push_back(
-        reinterpret_cast<uintptr_t>(RecordString(GetOrSetThreadName())));
-  }
-  if (!context) {
-    const auto* tracker =
-        AllocationContextTracker::GetInstanceForCurrentThread();
-    if (tracker)
-      context = tracker->TaskContext();
-  }
-  sample->context = RecordString(context);
-}
-
-const char* SamplingProfilerWrapper::RecordString(const char* string) {
-  return string ? *strings_.insert(string).first : nullptr;
+  RecordAndSendAlloc(ConvertType(type), address, size, context);
 }
 
 void SamplingProfilerWrapper::SampleRemoved(void* address) {
-  DCHECK(base::PoissonAllocationSampler::ScopedMuteThreadSamples::IsMuted());
-  if (stream_samples_) {
-    RecordAndSendFree(address);
-    return;
-  }
-  base::AutoLock lock(mutex_);
-  samples_.erase(address);
+  RecordAndSendFree(address);
 }
 
 }  // namespace heap_profiling
diff --git a/components/services/heap_profiling/public/cpp/sampling_profiler_wrapper.h b/components/services/heap_profiling/public/cpp/sampling_profiler_wrapper.h
index 19d5a29c..bee5c72 100644
--- a/components/services/heap_profiling/public/cpp/sampling_profiler_wrapper.h
+++ b/components/services/heap_profiling/public/cpp/sampling_profiler_wrapper.h
@@ -5,10 +5,6 @@
 #ifndef COMPONENTS_SERVICES_HEAP_PROFILING_PUBLIC_CPP_SAMPLING_PROFILER_WRAPPER_H_
 #define COMPONENTS_SERVICES_HEAP_PROFILING_PUBLIC_CPP_SAMPLING_PROFILER_WRAPPER_H_
 
-#include <unordered_map>
-#include <unordered_set>
-#include <vector>
-
 #include "base/sampling_heap_profiler/poisson_allocation_sampler.h"
 #include "components/services/heap_profiling/public/cpp/sender_pipe.h"
 #include "components/services/heap_profiling/public/cpp/stream.h"
@@ -48,24 +44,7 @@
   // logging process so it knows when this operation is complete.
   static void FlushPipe(uint32_t barrier_id);
 
-  mojom::HeapProfilePtr RetrieveHeapProfile();
-
  private:
-  struct Sample {
-    Sample();
-    Sample(Sample&& sample);
-    ~Sample();
-
-    Sample& operator=(Sample&&) = default;
-
-    AllocatorType allocator;
-    size_t size;
-    const char* context = nullptr;
-    std::vector<uint64_t> stack;
-
-    DISALLOW_COPY_AND_ASSIGN(Sample);
-  };
-
   // base::PoissonAllocationSampler::SamplesObserver
   void SampleAdded(void* address,
                    size_t size,
@@ -73,26 +52,6 @@
                    base::PoissonAllocationSampler::AllocatorType,
                    const char* context) override;
   void SampleRemoved(void* address) override;
-
-  void CaptureMixedStack(const char* context, Sample* sample);
-  void CaptureNativeStack(const char* context, Sample* sample);
-  const char* RecordString(const char* string);
-
-  bool stream_samples_ = false;
-
-  // Mutex to access |samples_| and |strings_|.
-  base::Lock mutex_;
-
-  // Samples of the currently live allocations.
-  std::unordered_map<void*, Sample> samples_;
-
-  // When CaptureMode::PSEUDO_STACK or CaptureMode::MIXED_STACK is enabled
-  // the call stack contents of samples may contain strings besides
-  // PC addresses.
-  // In this case each string pointer is also added to the |strings_| set.
-  // The set does only contain pointers to static strings that are never
-  // deleted.
-  std::unordered_set<const char*> strings_;
 };
 
 }  // namespace heap_profiling
diff --git a/components/services/heap_profiling/public/mojom/heap_profiling_client.mojom b/components/services/heap_profiling/public/mojom/heap_profiling_client.mojom
index 6a9607776..8b98d691 100644
--- a/components/services/heap_profiling/public/mojom/heap_profiling_client.mojom
+++ b/components/services/heap_profiling/public/mojom/heap_profiling_client.mojom
@@ -23,12 +23,6 @@
 // A wrapper for parameters that affect each client's implementation of
 // profiling.
 struct ProfilingParams {
-  // When |stream_samples| is true the samples are streamed through the
-  // provided |sender_pipe|. Otherwise, the samples are stored on
-  // the client side during recording and can be retrieved using
-  // |ProfilingClient.RetrieveHeapProfile| method.
-  bool stream_samples;
-
   // The client should record allocations into |memlog_sender_pipe|.
   handle sender_pipe;
 
@@ -43,48 +37,17 @@
   uint32 sampling_rate;
 };
 
-// A single memory allocation sample.
-struct HeapProfileSample {
-  // Allocator type.
-  uint32 allocator;
-
-  // The size in bytes accounted for the sample.
-  uint64 size;
-
-  // Id of the context string.
-  uint64 context_id;
-
-  // Program stack in top to bottom order recorded for the allocation.
-  // Each element of the |stack| is either a PC memory address or a string
-  // if it is among the |strings| map items of |HeapProfile|.
-  array<uint64> stack;
-};
-
-// Heap profile data. Can be retrieved from the client with
-// |RetrieveHeapProfile| method.
-struct HeapProfile {
-  // Samples recorded for the profile.
-  array<HeapProfileSample> samples;
-
-  // Strings used within the profile.
-  map<uint64, string> strings;
-};
-
 // This interface is implemented by "memlog clients" (profiled processes that
 // can send memory allocation events to the profiling process). These functions
 // are called by the profiling process to control the senders.
 interface ProfilingClient {
-  // Start recording allocations.
-  // Collected allocations are either streamed to the profiling process via
-  // |params.sender_pipe|, or accumulated in the profiled process depending on
-  // the |params.use_in_process_storage|.
-  // There is currently no mechanism to stop recording allocations.
+  // Start recording allocations and sending them to the profiling process via
+  // |params.sender_pipe|. There is currently no mechanism to stop recording
+  // allocations.
   StartProfiling(ProfilingParams params);
 
   // Flushes the memlog pipe associated with this client. A barrier packet is
   // set over the memlog pipe with the given identifier. This allows the
   // receiver to synchronize with the flush.
   FlushMemlogPipe(uint32 barrier_id);
-
-  RetrieveHeapProfile() => (HeapProfile profile);
 };
diff --git a/components/viz/host/DEPS b/components/viz/host/DEPS
index d8f55bb..1a6b0e0 100644
--- a/components/viz/host/DEPS
+++ b/components/viz/host/DEPS
@@ -11,6 +11,7 @@
   "+gpu/ipc/client",
   "+gpu/ipc/common",
   "+gpu/ipc/host",
+  "+media/capture/mojom/video_capture_types.mojom.h",
   "+mojo/public/cpp",
   "+services/viz/privileged/interfaces",
   "+services/viz/public/interfaces",
diff --git a/components/viz/host/client_frame_sink_video_capturer.cc b/components/viz/host/client_frame_sink_video_capturer.cc
index 7cae6723..a09ce3e 100644
--- a/components/viz/host/client_frame_sink_video_capturer.cc
+++ b/components/viz/host/client_frame_sink_video_capturer.cc
@@ -3,10 +3,12 @@
 // found in the LICENSE file.
 
 #include "components/viz/host/client_frame_sink_video_capturer.h"
-#include "base/bind.h"
 
 #include <utility>
 
+#include "base/bind.h"
+#include "media/capture/mojom/video_capture_types.mojom.h"
+
 namespace viz {
 
 namespace {
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
index 64ab61e..e718f6d 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
@@ -20,6 +20,7 @@
 #include "components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_manager.h"
 #include "media/base/limits.h"
 #include "media/base/video_util.h"
+#include "media/capture/mojom/video_capture_types.mojom.h"
 #include "services/viz/privileged/interfaces/compositing/frame_sink_video_capture.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/content/browser/devtools/devtools_video_consumer.cc b/content/browser/devtools/devtools_video_consumer.cc
index 3c67a3b4..75acd6e 100644
--- a/content/browser/devtools/devtools_video_consumer.cc
+++ b/content/browser/devtools/devtools_video_consumer.cc
@@ -13,6 +13,7 @@
 #include "components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h"
 #include "content/browser/compositor/surface_utils.h"
 #include "media/base/limits.h"
+#include "media/capture/mojom/video_capture_types.mojom.h"
 #include "media/renderers/paint_canvas_video_renderer.h"
 
 namespace content {
diff --git a/content/browser/devtools/devtools_video_consumer_unittest.cc b/content/browser/devtools/devtools_video_consumer_unittest.cc
index ab4fad3..c95590f 100644
--- a/content/browser/devtools/devtools_video_consumer_unittest.cc
+++ b/content/browser/devtools/devtools_video_consumer_unittest.cc
@@ -11,6 +11,7 @@
 #include "content/browser/devtools/devtools_video_consumer.h"
 #include "content/public/test/test_utils.h"
 #include "media/base/limits.h"
+#include "media/capture/mojom/video_capture_types.mojom.h"
 #include "mojo/public/cpp/base/shared_memory_utils.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "testing/gmock/include/gmock/gmock.h"
diff --git a/content/browser/frame_host/navigation_handle_impl.h b/content/browser/frame_host/navigation_handle_impl.h
index 47341c8..f88f1fc 100644
--- a/content/browser/frame_host/navigation_handle_impl.h
+++ b/content/browser/frame_host/navigation_handle_impl.h
@@ -541,7 +541,7 @@
   ThrottleChecksFinishedCallback complete_callback_for_testing_;
 
   // Manages the lifetime of a pre-created ServiceWorkerProviderHost until a
-  // corresponding ServiceWorkerNetworkProvider is created in the renderer.
+  // corresponding provider is created in the renderer.
   std::unique_ptr<ServiceWorkerNavigationHandle> service_worker_handle_;
 
   // Manages the lifetime of a pre-created AppCacheHost until a browser side
diff --git a/content/browser/media/session/media_session_impl_browsertest.cc b/content/browser/media/session/media_session_impl_browsertest.cc
index 120fbeb..c51de6a 100644
--- a/content/browser/media/session/media_session_impl_browsertest.cc
+++ b/content/browser/media/session/media_session_impl_browsertest.cc
@@ -818,8 +818,10 @@
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
-                       ControlsShowForTransientAndPlaybackStatePaused) {
+// TODO(https://crbug.com/925868): Fix and re-enable this.
+IN_PROC_BROWSER_TEST_P(
+    MediaSessionImplParamBrowserTest,
+    DISABLED_ControlsShowForTransientAndPlaybackStatePaused) {
   EnsureMediaSessionService();
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>(
       shell()->web_contents()->GetMainFrame());
@@ -842,8 +844,10 @@
   EXPECT_TRUE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
-                       ControlsShowForTransientAndPlaybackStatePlaying) {
+// TODO(https://crbug.com/925868): Fix and re-enable this.
+IN_PROC_BROWSER_TEST_P(
+    MediaSessionImplParamBrowserTest,
+    DISABLED_ControlsShowForTransientAndPlaybackStatePlaying) {
   EnsureMediaSessionService();
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>(
       shell()->web_contents()->GetMainFrame());
@@ -1491,8 +1495,9 @@
   EXPECT_TRUE(IsActive());
 }
 
+// TODO(https://crbug.com/925868): Fix and re-enable this.
 IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
-                       ControlsDontShowWhenOneShotIsPresent) {
+                       DISABLED_ControlsDontShowWhenOneShotIsPresent) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   {
@@ -1533,8 +1538,10 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
-                       ControlsHiddenAfterRemoveOneShotWithoutOtherPlayers) {
+// TODO(https://crbug.com/925868): Fix and re-enable this.
+IN_PROC_BROWSER_TEST_P(
+    MediaSessionImplParamBrowserTest,
+    DISABLED_ControlsHiddenAfterRemoveOneShotWithoutOtherPlayers) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   {
@@ -1559,8 +1566,10 @@
   EXPECT_FALSE(IsActive());
 }
 
-IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
-                       ControlsShowAfterRemoveOneShotWithPersistentPresent) {
+// TODO(https://crbug.com/925868): Fix and re-enable this.
+IN_PROC_BROWSER_TEST_P(
+    MediaSessionImplParamBrowserTest,
+    DISABLED_ControlsShowAfterRemoveOneShotWithPersistentPresent) {
   auto player_observer = std::make_unique<MockMediaSessionPlayerObserver>();
 
   {
@@ -2181,8 +2190,10 @@
   EXPECT_EQ(expected_metadata, observer.WaitForMetadata());
 }
 
-IN_PROC_BROWSER_TEST_P(MediaSessionImplParamBrowserTest,
-                       AddingObserverNotifiesCurrentInformation_WithInfo) {
+// TODO(https://crbug.com/925868): Fix and re-enable this.
+IN_PROC_BROWSER_TEST_P(
+    MediaSessionImplParamBrowserTest,
+    DISABLED_AddingMojoObserverNotifiesCurrentInformation_WithInfo) {
   // Set up the service and information.
   EnsureMediaSessionService();
 
diff --git a/content/browser/media/session/media_session_impl_service_routing_unittest.cc b/content/browser/media/session/media_session_impl_service_routing_unittest.cc
index b4c3d73..c25285d 100644
--- a/content/browser/media/session/media_session_impl_service_routing_unittest.cc
+++ b/content/browser/media/session/media_session_impl_service_routing_unittest.cc
@@ -286,8 +286,9 @@
   EXPECT_TRUE(observer.WaitForMetadata()->IsEmpty());
 }
 
+// TODO(https://crbug.com/925868): Fix and re-enable this.
 TEST_F(MediaSessionImplServiceRoutingTest,
-       NotifyMetadataAndActionsChangeWhenControllable) {
+       DISABLED_NotifyMetadataAndActionsChangeWhenControllable) {
   media_session::MediaMetadata expected_metadata;
   expected_metadata.title = base::ASCIIToUTF16("title");
   expected_metadata.artist = base::ASCIIToUTF16("artist");
@@ -322,8 +323,9 @@
   }
 }
 
+// TODO(https://crbug.com/925868): Fix and re-enable this.
 TEST_F(MediaSessionImplServiceRoutingTest,
-       NotifyMetadataAndActionsChangeWhenTurningControllable) {
+       DISABLED_NotifyMetadataAndActionsChangeWhenTurningControllable) {
   media_session::MediaMetadata expected_metadata;
   expected_metadata.title = base::ASCIIToUTF16("title");
   expected_metadata.artist = base::ASCIIToUTF16("artist");
@@ -359,8 +361,9 @@
   }
 }
 
+// TODO(https://crbug.com/925868): Fix and re-enable this.
 TEST_F(MediaSessionImplServiceRoutingTest,
-       NotifyActionsAndMetadataChangeWhenTurningUncontrollable) {
+       DISABLED_NotifyActionsAndMetadataChangeWhenTurningUncontrollable) {
   media_session::MediaMetadata expected_metadata;
   expected_metadata.title = base::ASCIIToUTF16("title");
   expected_metadata.artist = base::ASCIIToUTF16("artist");
@@ -659,8 +662,9 @@
             observer.actions_set());
 }
 
+// TODO(https://crbug.com/925868): Fix and re-enable this.
 TEST_F(MediaSessionImplServiceRoutingTest,
-       DefaultActionsRemovedIfUncontrollable) {
+       DISABLED_DefaultActionsRemovedIfUncontrollable) {
   CreateServiceForFrame(main_frame_);
   StartPlayerForFrame(main_frame_, media::MediaContentType::OneShot);
 
diff --git a/content/browser/renderer_host/media/old_render_frame_audio_input_stream_factory.h b/content/browser/renderer_host/media/old_render_frame_audio_input_stream_factory.h
index 01fb38b..1a7799ba 100644
--- a/content/browser/renderer_host/media/old_render_frame_audio_input_stream_factory.h
+++ b/content/browser/renderer_host/media/old_render_frame_audio_input_stream_factory.h
@@ -25,6 +25,7 @@
 #include "media/audio/audio_input_delegate.h"
 #include "media/mojo/interfaces/audio_logging.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
+#include "services/audio/public/mojom/audio_processing.mojom.h"
 
 namespace media {
 class AudioParameters;
diff --git a/content/browser/renderer_host/media/old_render_frame_audio_input_stream_factory_unittest.cc b/content/browser/renderer_host/media/old_render_frame_audio_input_stream_factory_unittest.cc
index 76f0cae..5fc0454 100644
--- a/content/browser/renderer_host/media/old_render_frame_audio_input_stream_factory_unittest.cc
+++ b/content/browser/renderer_host/media/old_render_frame_audio_input_stream_factory_unittest.cc
@@ -22,8 +22,10 @@
 #include "media/audio/mock_audio_manager.h"
 #include "media/audio/test_audio_thread.h"
 #include "media/base/audio_parameters.h"
+#include "media/mojo/interfaces/audio_data_pipe.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/system/platform_handle.h"
+#include "services/audio/public/mojom/audio_processing.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/content/browser/renderer_host/media/old_render_frame_audio_output_stream_factory_unittest.cc b/content/browser/renderer_host/media/old_render_frame_audio_output_stream_factory_unittest.cc
index 8277d2d..b3f6212 100644
--- a/content/browser/renderer_host/media/old_render_frame_audio_output_stream_factory_unittest.cc
+++ b/content/browser/renderer_host/media/old_render_frame_audio_output_stream_factory_unittest.cc
@@ -18,6 +18,7 @@
 #include "content/public/test/test_browser_context.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "media/base/audio_parameters.h"
+#include "media/mojo/interfaces/audio_data_pipe.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "testing/gmock/include/gmock/gmock.h"
diff --git a/content/browser/renderer_host/media/render_frame_audio_input_stream_factory_unittest.cc b/content/browser/renderer_host/media/render_frame_audio_input_stream_factory_unittest.cc
index 6719d24..d8efbcd6 100644
--- a/content/browser/renderer_host/media/render_frame_audio_input_stream_factory_unittest.cc
+++ b/content/browser/renderer_host/media/render_frame_audio_input_stream_factory_unittest.cc
@@ -31,6 +31,7 @@
 #include "media/audio/fake_audio_manager.h"
 #include "media/audio/test_audio_thread.h"
 #include "media/base/audio_parameters.h"
+#include "media/mojo/interfaces/audio_data_pipe.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "services/audio/public/cpp/fake_stream_factory.h"
diff --git a/content/browser/renderer_host/media/service_video_capture_provider_unittest.cc b/content/browser/renderer_host/media/service_video_capture_provider_unittest.cc
index 93cdd6d..7220d9d 100644
--- a/content/browser/renderer_host/media/service_video_capture_provider_unittest.cc
+++ b/content/browser/renderer_host/media/service_video_capture_provider_unittest.cc
@@ -19,6 +19,7 @@
 #include "services/video_capture/public/cpp/mock_device_factory.h"
 #include "services/video_capture/public/cpp/mock_device_factory_provider.h"
 #include "services/video_capture/public/mojom/constants.mojom.h"
+#include "services/video_capture/public/mojom/producer.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/content/browser/renderer_host/native_web_keyboard_event_mac.mm b/content/browser/renderer_host/native_web_keyboard_event_mac.mm
index 4d48c96..9ac6998 100644
--- a/content/browser/renderer_host/native_web_keyboard_event_mac.mm
+++ b/content/browser/renderer_host/native_web_keyboard_event_mac.mm
@@ -62,7 +62,11 @@
   size_t unmod_text_length =
       WebKeyboardEventTextLength(web_event.unmodified_text);
 
-  if (text_length == 0)
+  // Perform the reverse operation on type that was done in
+  // UnmodifiedTextFromEvent(). Avoid using text_length as the control key may
+  // cause Mac to set [NSEvent characters] to "\0" which for us is
+  // indistinguishable from "".
+  if (unmod_text_length == 0)
     type = NSFlagsChanged;
 
   NSString* text =
diff --git a/content/browser/renderer_host/native_web_keyboard_event_mac_unittest.mm b/content/browser/renderer_host/native_web_keyboard_event_mac_unittest.mm
new file mode 100644
index 0000000..f30a913
--- /dev/null
+++ b/content/browser/renderer_host/native_web_keyboard_event_mac_unittest.mm
@@ -0,0 +1,42 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "content/public/browser/native_web_keyboard_event.h"
+
+#import <AppKit/AppKit.h>
+#import <Carbon/Carbon.h>
+
+#include "base/mac/scoped_nsobject.h"
+#include "content/browser/renderer_host/input/web_input_event_builders_mac.h"
+#import "testing/gtest_mac.h"
+#include "ui/events/blink/web_input_event.h"
+
+// Going from NSEvent to WebKeyboardEvent and back should round trip.
+TEST(NativeWebKeyboardEventMac, CtrlCmdSpaceKeyDownRoundTrip) {
+  base::scoped_nsobject<NSWindow> window([NSWindow alloc]);
+
+  NSEvent* ns_event =
+      [[NSEvent keyEventWithType:NSEventTypeKeyDown
+                             location:NSZeroPoint
+                        modifierFlags:NSEventModifierFlagControl |
+                                      NSEventModifierFlagCommand
+                            timestamp:0
+                         windowNumber:[window windowNumber]
+                              context:nil
+                           characters:@"\0"  // The control modifier results in
+                                             // ' ' being bitmasked with 0x1F
+                                             // which == 0x00.
+          charactersIgnoringModifiers:@" "
+                            isARepeat:NO
+                              keyCode:kVK_Space] retain];
+  blink::WebKeyboardEvent web_event =
+      content::WebKeyboardEventBuilder::Build(ns_event);
+  content::NativeWebKeyboardEvent native_event(
+      web_event, gfx::NativeView([window contentView]));
+
+  NSEvent* round_trip_ns_event = native_event.os_event;
+  EXPECT_EQ([round_trip_ns_event type], [ns_event type]);
+  EXPECT_EQ([round_trip_ns_event modifierFlags], [ns_event modifierFlags]);
+  EXPECT_EQ([round_trip_ns_event keyCode], [ns_event keyCode]);
+}
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index fcab5b30..90c0a16 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -233,6 +233,11 @@
 #include "content/browser/compositor/image_transport_factory.h"
 #endif
 
+#if defined(OS_LINUX)
+#include <sys/resource.h>
+#include <sys/time.h>
+#endif
+
 #if defined(OS_MACOSX)
 #include "content/browser/mach_broker_mac.h"
 #endif
@@ -1264,6 +1269,28 @@
       base::BindOnce(&AddCorbExceptionForPluginOnUIThread, process_id));
 }
 
+#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+static constexpr size_t kUnknownPlatformProcessLimit = 0;
+
+// Returns the process limit from the system. Use |kUnknownPlatformProcessLimit|
+// to indicate failure and std::numeric_limits<size_t>::max() to indicate
+// unlimited.
+size_t GetPlatformProcessLimit() {
+#if defined(OS_LINUX)
+  struct rlimit limit;
+  if (getrlimit(RLIMIT_NPROC, &limit) != 0)
+    return kUnknownPlatformProcessLimit;
+
+  if (limit.rlim_cur == RLIM_INFINITY)
+    return std::numeric_limits<size_t>::max();
+  return base::saturated_cast<size_t>(limit.rlim_cur);
+#else
+  // TODO(https://crbug.com/104689): Implement on other platforms.
+  return kUnknownPlatformProcessLimit;
+#endif  // defined(OS_LINUX)
+}
+#endif  // !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+
 }  // namespace
 
 // Held by the RPH and used to control an (unowned) ConnectionFilterImpl from
@@ -1369,8 +1396,15 @@
 #if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
 // static
 size_t RenderProcessHostImpl::GetPlatformMaxRendererProcessCount() {
-  static constexpr size_t kMaxRendererProcessCount = 82;
-  return kMaxRendererProcessCount;
+  // Set the limit to half of the system limit to leave room for other programs.
+  size_t limit = GetPlatformProcessLimit() / 2;
+
+  // If the system limit is unavailable, use a fallback value instead.
+  if (limit == kUnknownPlatformProcessLimit) {
+    static constexpr size_t kMaxRendererProcessCount = 82;
+    limit = kMaxRendererProcessCount;
+  }
+  return limit;
 }
 #endif
 
@@ -1425,9 +1459,12 @@
     max_count /= kEstimatedWebContentsMemoryUsage;
 
     static constexpr size_t kMinRendererProcessCount = 3;
-    max_count = base::ClampToRange(
-        max_count, kMinRendererProcessCount,
-        RenderProcessHostImpl::GetPlatformMaxRendererProcessCount());
+    static const size_t kMaxRendererProcessCount =
+        RenderProcessHostImpl::GetPlatformMaxRendererProcessCount();
+    DCHECK_LE(kMinRendererProcessCount, kMaxRendererProcessCount);
+
+    max_count = base::ClampToRange(max_count, kMinRendererProcessCount,
+                                   kMaxRendererProcessCount);
   }
   return max_count;
 #endif
diff --git a/content/browser/renderer_host/render_view_host_delegate.h b/content/browser/renderer_host/render_view_host_delegate.h
index 7bc59f08..27e5c470 100644
--- a/content/browser/renderer_host/render_view_host_delegate.h
+++ b/content/browser/renderer_host/render_view_host_delegate.h
@@ -15,6 +15,7 @@
 #include "content/browser/dom_storage/session_storage_namespace_impl.h"
 #include "content/common/content_export.h"
 #include "content/common/render_message_filter.mojom.h"
+#include "content/common/widget.mojom.h"
 #include "net/base/load_states.h"
 
 class GURL;
diff --git a/content/browser/service_worker/service_worker_provider_host.h b/content/browser/service_worker/service_worker_provider_host.h
index 4f15e953..d3d6b42 100644
--- a/content/browser/service_worker/service_worker_provider_host.h
+++ b/content/browser/service_worker/service_worker_provider_host.h
@@ -132,7 +132,7 @@
   using ExecutionReadyCallback = base::OnceClosure;
 
   // Used to pre-create a ServiceWorkerProviderHost for a navigation. The
-  // ServiceWorkerNetworkProvider will later be created in the renderer, should
+  // ServiceWorkerProviderContext will later be created in the renderer, should
   // the navigation succeed. |are_ancestors_secure| should be true for main
   // frames. Otherwise it is true iff all ancestor frames of this frame have a
   // secure origin. |web_contents_getter| indicates the tab where the navigation
@@ -197,10 +197,10 @@
 
   // Returns whether this provider host is secure enough to have a service
   // worker controller.
-  // Analogous to Blink's Document::isSecureContext. Because of how service
+  // Analogous to Blink's Document::IsSecureContext. Because of how service
   // worker intercepts main resource requests, this check must be done
   // browser-side once the URL is known (see comments in
-  // ServiceWorkerNetworkProvider::CreateForNavigation). This function uses
+  // WebServiceWorkerNetworkProviderImplForFrame::Create). This function uses
   // |url_| and |is_parent_frame_secure_| to determine context security, so they
   // must be set properly before calling this function.
   bool IsContextSecureForServiceWorker() const;
diff --git a/content/browser/service_worker/service_worker_script_loader_factory.h b/content/browser/service_worker/service_worker_script_loader_factory.h
index 85781d9..ca286493 100644
--- a/content/browser/service_worker/service_worker_script_loader_factory.h
+++ b/content/browser/service_worker/service_worker_script_loader_factory.h
@@ -21,10 +21,11 @@
 
 // S13nServiceWorker:
 // Created per one running service worker for loading its scripts. This is kept
-// alive while ServiceWorkerNetworkProvider in the renderer process is alive.
+// alive while the WebServiceWorkerNetworkProvider in the renderer process is
+// alive.
 //
-// This factory handles requests for scripts from service workers that were
-// new (non-installed) when they started. For service workers that were already
+// This factory handles requests for scripts from service workers that were new
+// (non-installed) when they started. For service workers that were already
 // installed when they started, ServiceWorkerInstalledScriptsManager is used
 // instead.
 //
diff --git a/content/browser/web_package/signed_exchange_url_loader_factory_for_non_network_service.cc b/content/browser/web_package/signed_exchange_url_loader_factory_for_non_network_service.cc
index e1c39730..55b9b36a 100644
--- a/content/browser/web_package/signed_exchange_url_loader_factory_for_non_network_service.cc
+++ b/content/browser/web_package/signed_exchange_url_loader_factory_for_non_network_service.cc
@@ -12,6 +12,7 @@
 #include "content/public/common/content_features.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
 
 namespace content {
 
diff --git a/content/common/service_worker/embedded_worker.mojom b/content/common/service_worker/embedded_worker.mojom
index e113968..88bb5a3b 100644
--- a/content/common/service_worker/embedded_worker.mojom
+++ b/content/common/service_worker/embedded_worker.mojom
@@ -70,7 +70,7 @@
   blink.mojom.ServiceWorkerInstalledScriptsInfo? installed_scripts_info;
   // Interface for the renderer to send the status updates to the browser.
   associated EmbeddedWorkerInstanceHost instance_host;
-  // Information for creating ServiceWorkerNetworkProvider on the renderer.
+  // Information for creating ServiceWorkerProviderContext on the renderer.
   blink.mojom.ServiceWorkerProviderInfoForStartWorker provider_info;
   // Interface for the renderer to query the content settings in the browser.
   blink.mojom.WorkerContentSettingsProxy content_settings_proxy;
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index 88b4c986..ce6ce32 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -532,8 +532,6 @@
     "service_worker/service_worker_type_converters.h",
     "service_worker/service_worker_type_util.cc",
     "service_worker/service_worker_type_util.h",
-    "service_worker/web_service_worker_network_provider_base_impl.cc",
-    "service_worker/web_service_worker_network_provider_base_impl.h",
     "service_worker/web_service_worker_network_provider_impl_for_frame.cc",
     "service_worker/web_service_worker_network_provider_impl_for_frame.h",
     "service_worker/web_service_worker_provider_impl.cc",
diff --git a/content/renderer/loader/web_worker_fetch_context_impl.cc b/content/renderer/loader/web_worker_fetch_context_impl.cc
index 974f65ce..c8e78c1 100644
--- a/content/renderer/loader/web_worker_fetch_context_impl.cc
+++ b/content/renderer/loader/web_worker_fetch_context_impl.cc
@@ -28,9 +28,9 @@
 #include "content/renderer/loader/web_url_loader_impl.h"
 #include "content/renderer/loader/web_url_request_util.h"
 #include "content/renderer/service_worker/controller_service_worker_connector.h"
-#include "content/renderer/service_worker/service_worker_network_provider.h"
 #include "content/renderer/service_worker/service_worker_provider_context.h"
 #include "content/renderer/service_worker/service_worker_subresource_loader.h"
+#include "content/renderer/shared_worker/web_service_worker_network_provider_impl_for_worker.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
@@ -119,8 +119,8 @@
  private:
   bool CanCreateServiceWorkerURLLoader(const blink::WebURLRequest& request) {
     // TODO(horo): Unify this code path with
-    // ServiceWorkerNetworkProvider::CreateURLLoader that is used for document
-    // cases.
+    // WebServiceWorkerNetworkProviderImplForFrame::CreateURLLoader that is used
+    // for document cases.
 
     // We need the service worker loader factory populated in order to create
     // our own URLLoader for subresource loading via a service worker.
@@ -153,13 +153,12 @@
 };
 
 scoped_refptr<WebWorkerFetchContextImpl> WebWorkerFetchContextImpl::Create(
-    ServiceWorkerNetworkProvider* network_provider,
+    ServiceWorkerProviderContext* provider_context,
     blink::mojom::RendererPreferences renderer_preferences,
     blink::mojom::RendererPreferenceWatcherRequest watcher_request,
     std::unique_ptr<network::SharedURLLoaderFactoryInfo> loader_factory_info,
     std::unique_ptr<network::SharedURLLoaderFactoryInfo>
         fallback_factory_info) {
-  DCHECK(network_provider);
   blink::mojom::ServiceWorkerWorkerClientRequest service_worker_client_request;
   blink::mojom::ServiceWorkerWorkerClientRegistryPtrInfo
       service_worker_worker_client_registry_ptr_info;
@@ -167,7 +166,6 @@
 
   // Some sandboxed iframes are not allowed to use service worker so don't have
   // a real service worker provider, so the provider context is null.
-  ServiceWorkerProviderContext* provider_context = network_provider->context();
   if (provider_context) {
     provider_context->CloneWorkerClientRegistry(
         mojo::MakeRequest(&service_worker_worker_client_registry_ptr_info));
@@ -194,12 +192,18 @@
               ->CreateWebSocketHandshakeThrottleProvider(),
           ChildThreadImpl::current()->thread_safe_sender(),
           ChildThreadImpl::current()->GetConnector()->Clone()));
-  worker_fetch_context->set_service_worker_provider_id(
-      network_provider->provider_id());
-  worker_fetch_context->set_is_controlled_by_service_worker(
-      network_provider->IsControlledByServiceWorker());
-  if (provider_context)
+  if (provider_context) {
+    worker_fetch_context->set_service_worker_provider_id(
+        provider_context->provider_id());
+    worker_fetch_context->set_is_controlled_by_service_worker(
+        provider_context->IsControlledByServiceWorker());
     worker_fetch_context->set_client_id(provider_context->client_id());
+  } else {
+    worker_fetch_context->set_service_worker_provider_id(
+        kInvalidServiceWorkerProviderId);
+    worker_fetch_context->set_is_controlled_by_service_worker(
+        blink::mojom::ControllerServiceWorkerMode::kNoController);
+  }
   return worker_fetch_context;
 }
 
diff --git a/content/renderer/loader/web_worker_fetch_context_impl.h b/content/renderer/loader/web_worker_fetch_context_impl.h
index 11e2fc9b..88b05dc 100644
--- a/content/renderer/loader/web_worker_fetch_context_impl.h
+++ b/content/renderer/loader/web_worker_fetch_context_impl.h
@@ -20,6 +20,7 @@
 #include "third_party/blink/public/mojom/blob/blob_registry.mojom.h"
 #include "third_party/blink/public/mojom/renderer_preference_watcher.mojom.h"
 #include "third_party/blink/public/mojom/renderer_preferences.mojom.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_container.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
 #include "third_party/blink/public/platform/web_application_cache_host.h"
@@ -35,7 +36,7 @@
 
 class FrameRequestBlocker;
 class ResourceDispatcher;
-class ServiceWorkerNetworkProvider;
+class ServiceWorkerProviderContext;
 class ThreadSafeSender;
 class URLLoaderThrottleProvider;
 class WebSocketHandshakeThrottleProvider;
@@ -51,8 +52,8 @@
  public:
   // Creates a new fetch context for a worker.
   //
-  // |network_provider| is the ServiceWorkerNetworkProvider of the worker and is
-  // used to route requests to its controller service worker.
+  // |provider_context| is used to route requests to the worker's controller
+  // service worker.
   // |loader_factory_info| is used for regular loading by the worker.
   //
   // S13nServiceWorker:
@@ -68,7 +69,7 @@
   // because it might additionally support non-NetworkService schemes (e.g.,
   // chrome-extension://).
   static scoped_refptr<WebWorkerFetchContextImpl> Create(
-      ServiceWorkerNetworkProvider* network_provider,
+      ServiceWorkerProviderContext* provider_context,
       blink::mojom::RendererPreferences renderer_preferences,
       blink::mojom::RendererPreferenceWatcherRequest watcher_request,
       std::unique_ptr<network::SharedURLLoaderFactoryInfo> loader_factory_info,
diff --git a/content/renderer/mus/renderer_window_tree_client.cc b/content/renderer/mus/renderer_window_tree_client.cc
index 7b939ec..15b479a 100644
--- a/content/renderer/mus/renderer_window_tree_client.cc
+++ b/content/renderer/mus/renderer_window_tree_client.cc
@@ -18,6 +18,7 @@
 #include "content/renderer/mus/mus_embedded_frame.h"
 #include "content/renderer/mus/mus_embedded_frame_delegate.h"
 #include "content/renderer/render_frame_proxy.h"
+#include "services/ws/public/mojom/window_tree_constants.mojom.h"
 #include "ui/base/ui_base_features.h"
 
 namespace content {
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 87d6fc2..0a9b65d 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -143,7 +143,7 @@
 #include "content/renderer/renderer_webapplicationcachehost_impl.h"
 #include "content/renderer/resource_timing_info_conversions.h"
 #include "content/renderer/savable_resources.h"
-#include "content/renderer/service_worker/service_worker_network_provider.h"
+#include "content/renderer/service_worker/web_service_worker_network_provider_impl_for_frame.h"
 #include "content/renderer/service_worker/web_service_worker_provider_impl.h"
 #include "content/renderer/skia_benchmarking_extension.h"
 #include "content/renderer/stats_collection_controller.h"
@@ -3702,12 +3702,10 @@
 
 scoped_refptr<blink::WebWorkerFetchContext>
 RenderFrameImpl::CreateWorkerFetchContext() {
-  blink::WebServiceWorkerNetworkProvider* web_provider =
-      frame_->GetDocumentLoader()->GetServiceWorkerNetworkProvider();
-  DCHECK(web_provider);
-  ServiceWorkerNetworkProvider* provider =
-      ServiceWorkerNetworkProvider::FromWebServiceWorkerNetworkProvider(
-          web_provider);
+  WebServiceWorkerNetworkProviderImplForFrame* provider =
+      static_cast<WebServiceWorkerNetworkProviderImplForFrame*>(
+          frame_->GetDocumentLoader()->GetServiceWorkerNetworkProvider());
+  DCHECK(provider);
 
   blink::mojom::RendererPreferenceWatcherPtr watcher;
   blink::mojom::RendererPreferenceWatcherRequest watcher_request =
@@ -3716,7 +3714,7 @@
 
   scoped_refptr<WebWorkerFetchContextImpl> worker_fetch_context =
       WebWorkerFetchContextImpl::Create(
-          provider, render_view_->renderer_preferences(),
+          provider->context(), render_view_->renderer_preferences(),
           std::move(watcher_request), GetLoaderFactoryBundle()->Clone(),
           GetLoaderFactoryBundle()->CloneWithoutAppCacheFactory());
 
@@ -3780,8 +3778,8 @@
   // At this point we should have non-null data source.
   if (!ChildThreadImpl::current())
     return nullptr;  // May be null in some tests.
-  ServiceWorkerNetworkProvider* provider =
-      ServiceWorkerNetworkProvider::FromWebServiceWorkerNetworkProvider(
+  WebServiceWorkerNetworkProviderImplForFrame* provider =
+      static_cast<WebServiceWorkerNetworkProviderImplForFrame*>(
           frame_->GetDocumentLoader()->GetServiceWorkerNetworkProvider());
   if (!provider->context()) {
     // The context can be null when the frame is sandboxed.
@@ -7177,18 +7175,6 @@
 #endif
 }
 
-blink::mojom::ControllerServiceWorkerMode
-RenderFrameImpl::IsControlledByServiceWorker() {
-  blink::WebServiceWorkerNetworkProvider* web_provider =
-      frame_->GetDocumentLoader()->GetServiceWorkerNetworkProvider();
-  if (!web_provider)
-    return blink::mojom::ControllerServiceWorkerMode::kNoController;
-  ServiceWorkerNetworkProvider* provider =
-      ServiceWorkerNetworkProvider::FromWebServiceWorkerNetworkProvider(
-          web_provider);
-  return provider->IsControlledByServiceWorker();
-}
-
 void RenderFrameImpl::BindWidget(mojom::WidgetRequest request) {
   GetLocalRootRenderWidget()->SetWidgetBinding(std::move(request));
 }
@@ -7251,7 +7237,7 @@
   scoped_refptr<network::SharedURLLoaderFactory> fallback_factory =
       network::SharedURLLoaderFactory::Create(
           GetLoaderFactoryBundle()->CloneWithoutAppCacheFactory());
-  return ServiceWorkerNetworkProvider::CreateForNavigation(
+  return WebServiceWorkerNetworkProviderImplForFrame::Create(
       this, commit_params, std::move(controller_service_worker_info),
       std::move(fallback_factory));
 }
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index 1ef0b77a..20bc4c9 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -1237,9 +1237,6 @@
 
   void ShowDeferredContextMenu(const ContextMenuParams& params);
 
-  // Whether the frame is controlled by a service worker.
-  blink::mojom::ControllerServiceWorkerMode IsControlledByServiceWorker();
-
   // Build DidCommitProvisionalLoad_Params based on the frame internal state.
   std::unique_ptr<FrameHostMsg_DidCommitProvisionalLoad_Params>
   MakeDidCommitProvisionalLoadParams(blink::WebHistoryCommitType commit_type,
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index ad1fa7d..5174eb1 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -2163,8 +2163,7 @@
 
 void RenderThreadImpl::SetUpEmbeddedWorkerChannelForServiceWorker(
     mojom::EmbeddedWorkerInstanceClientRequest client_request) {
-  EmbeddedWorkerInstanceClientImpl::Create(GetIOTaskRunner(),
-                                           std::move(client_request));
+  EmbeddedWorkerInstanceClientImpl::Create(std::move(client_request));
 }
 
 void RenderThreadImpl::OnNetworkConnectionChanged(
diff --git a/content/renderer/render_view_browsertest.cc b/content/renderer/render_view_browsertest.cc
index 9cdecad..4a94050 100644
--- a/content/renderer/render_view_browsertest.cc
+++ b/content/renderer/render_view_browsertest.cc
@@ -51,7 +51,7 @@
 #include "content/renderer/render_frame_proxy.h"
 #include "content/renderer/render_process.h"
 #include "content/renderer/render_view_impl.h"
-#include "content/renderer/service_worker/service_worker_network_provider.h"
+#include "content/renderer/service_worker/web_service_worker_network_provider_impl_for_frame.h"
 #include "content/shell/browser/shell.h"
 #include "content/shell/browser/shell_browser_context.h"
 #include "content/test/fake_compositor_dependencies.h"
@@ -2159,30 +2159,23 @@
   // Service workers require https.
   GURL example_url("https://example.com");
 
-  blink::WebServiceWorkerNetworkProvider* webprovider = nullptr;
-  ServiceWorkerNetworkProvider* provider = nullptr;
+  WebServiceWorkerNetworkProviderImplForFrame* provider = nullptr;
   RequestExtraData* extra_data = nullptr;
 
   // Make sure each new document has a new provider and
   // that the main request is tagged with the provider's id.
   LoadHTMLWithUrlOverride("<b>A Document</b>", example_url.spec().c_str());
   ASSERT_TRUE(GetMainFrame()->GetDocumentLoader());
-  webprovider =
-      GetMainFrame()->GetDocumentLoader()->GetServiceWorkerNetworkProvider();
-  ASSERT_TRUE(webprovider);
-  provider = ServiceWorkerNetworkProvider::FromWebServiceWorkerNetworkProvider(
-      webprovider);
+  provider = static_cast<WebServiceWorkerNetworkProviderImplForFrame*>(
+      GetMainFrame()->GetDocumentLoader()->GetServiceWorkerNetworkProvider());
   ASSERT_TRUE(provider);
   int provider1_id = provider->provider_id();
 
   LoadHTMLWithUrlOverride("<b>New Document B Goes Here</b>",
                           example_url.spec().c_str());
   ASSERT_TRUE(GetMainFrame()->GetDocumentLoader());
-  webprovider =
-      GetMainFrame()->GetDocumentLoader()->GetServiceWorkerNetworkProvider();
-  ASSERT_TRUE(provider);
-  provider = ServiceWorkerNetworkProvider::FromWebServiceWorkerNetworkProvider(
-      webprovider);
+  provider = static_cast<WebServiceWorkerNetworkProviderImplForFrame*>(
+      GetMainFrame()->GetDocumentLoader()->GetServiceWorkerNetworkProvider());
   ASSERT_TRUE(provider);
   EXPECT_NE(provider1_id, provider->provider_id());
 
@@ -2191,7 +2184,7 @@
   blink::WebURLRequest request(GURL("http://foo.com"));
   request.SetRequestContext(blink::mojom::RequestContextType::SUBRESOURCE);
   blink::WebURLResponse redirect_response;
-  webprovider->WillSendRequest(request);
+  provider->WillSendRequest(request);
   extra_data = static_cast<RequestExtraData*>(request.GetExtraData());
   ASSERT_TRUE(extra_data);
   EXPECT_EQ(extra_data->service_worker_provider_id(), provider->provider_id());
diff --git a/content/renderer/service_worker/embedded_worker_instance_client_impl.cc b/content/renderer/service_worker/embedded_worker_instance_client_impl.cc
index 3ba445f..2ec6f562 100644
--- a/content/renderer/service_worker/embedded_worker_instance_client_impl.cc
+++ b/content/renderer/service_worker/embedded_worker_instance_client_impl.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
+#include "content/child/child_thread_impl.h"
 #include "content/child/scoped_child_process_reference.h"
 #include "content/common/service_worker/service_worker_utils.h"
 #include "content/public/common/content_client.h"
@@ -27,20 +28,20 @@
 
 // static
 void EmbeddedWorkerInstanceClientImpl::Create(
-    scoped_refptr<base::SingleThreadTaskRunner> io_thread_runner,
     mojom::EmbeddedWorkerInstanceClientRequest request) {
   // This won't be leaked because the lifetime will be managed internally.
   // See the class documentation for detail.
-  new EmbeddedWorkerInstanceClientImpl(std::move(io_thread_runner),
-                                       std::move(request));
+  // We can't use MakeStrongBinding because must give the worker thread
+  // a chance to stop by calling TerminateWorkerContext() and waiting
+  // before destructing.
+  new EmbeddedWorkerInstanceClientImpl(std::move(request));
 }
 
 void EmbeddedWorkerInstanceClientImpl::WorkerContextDestroyed() {
   DCHECK(worker_);
   TRACE_EVENT0("ServiceWorker",
                "EmbeddedWorkerInstanceClientImpl::WorkerContextDestroyed");
-  // Destroys |this|.
-  worker_.reset();
+  delete this;
 }
 
 void EmbeddedWorkerInstanceClientImpl::StartWorker(
@@ -69,8 +70,8 @@
       std::move(params->renderer_preferences),
       std::move(params->service_worker_request),
       std::move(params->controller_request), std::move(params->instance_host),
-      std::move(params->provider_info), std::move(temporal_self_),
-      std::move(start_timing), std::move(params->preference_watcher_request),
+      std::move(params->provider_info), this, std::move(start_timing),
+      std::move(params->preference_watcher_request),
       std::move(params->subresource_loader_factories),
       RenderThreadImpl::current()
           ->GetWebMainThreadScheduler()
@@ -89,11 +90,11 @@
 
 void EmbeddedWorkerInstanceClientImpl::StopWorker() {
   // StopWorker must be called after StartWorker is called.
-  DCHECK(ChildThreadImpl::current());
   DCHECK(worker_);
 
   TRACE_EVENT0("ServiceWorker", "EmbeddedWorkerInstanceClientImpl::StopWorker");
   worker_->TerminateWorkerContext();
+  // We continue in WorkerContextDestroyed() after the worker thread is stopped.
 }
 
 void EmbeddedWorkerInstanceClientImpl::ResumeAfterDownload() {
@@ -117,11 +118,8 @@
 }
 
 EmbeddedWorkerInstanceClientImpl::EmbeddedWorkerInstanceClientImpl(
-    scoped_refptr<base::SingleThreadTaskRunner> io_thread_runner,
     mojom::EmbeddedWorkerInstanceClientRequest request)
-    : binding_(this, std::move(request)),
-      temporal_self_(this),
-      io_thread_runner_(std::move(io_thread_runner)) {
+    : binding_(this, std::move(request)) {
   binding_.set_connection_error_handler(base::BindOnce(
       &EmbeddedWorkerInstanceClientImpl::OnError, base::Unretained(this)));
 }
@@ -129,9 +127,16 @@
 EmbeddedWorkerInstanceClientImpl::~EmbeddedWorkerInstanceClientImpl() {}
 
 void EmbeddedWorkerInstanceClientImpl::OnError() {
-  // Destroys |this| if |temporal_self_| still owns this (i.e., StartWorker()
-  // was not yet called).
-  temporal_self_.reset();
+  // The connection to the browser process broke.
+  if (worker_) {
+    // The worker is running, so tell it to stop. We continue in
+    // WorkerContextDestroyed().
+    StopWorker();
+    return;
+  }
+
+  // Nothing left to do.
+  delete this;
 }
 
 std::unique_ptr<blink::WebEmbeddedWorker>
diff --git a/content/renderer/service_worker/embedded_worker_instance_client_impl.h b/content/renderer/service_worker/embedded_worker_instance_client_impl.h
index 12524e4..3304d28 100644
--- a/content/renderer/service_worker/embedded_worker_instance_client_impl.h
+++ b/content/renderer/service_worker/embedded_worker_instance_client_impl.h
@@ -7,9 +7,8 @@
 
 #include <memory>
 
-#include "base/containers/id_map.h"
-#include "base/single_thread_task_runner.h"
 #include "content/child/child_thread_impl.h"
+#include "content/common/content_export.h"
 #include "content/common/service_worker/embedded_worker.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "third_party/blink/public/common/privacy_preferences.h"
@@ -26,24 +25,14 @@
 
 class ServiceWorkerContextClient;
 
-// This class exposes interfaces of WebEmbeddedWorker to the browser process.
-// Unless otherwise noted, all methods should be called on the main thread.
 // EmbeddedWorkerInstanceClientImpl is created in order to start a service
-// worker, and lives as long as the service worker is running.
+// worker. The browser processes sends a Mojo request that creates an instance
+// of this class. The instance deletes itself when the service worker stops.  If
+// the Mojo connection to the browser breaks first, the instance waits for the
+// service worker to stop and then deletes itself.
 //
-// This class deletes itself when the worker stops (or if start failed). The
-// ownership graph is a cycle like this:
-// EmbeddedWorkerInstanceClientImpl -(owns)-> WorkerWrapper -(owns)->
-// WebEmbeddedWorkerImpl -(owns)-> ServiceWorkerContextClient -(owns)->
-// EmbeddedWorkerInstanceClientImpl. Therefore, an instance can delete itself by
-// releasing its WorkerWrapper.
-//
-// Since starting/stopping service workers is initiated by the browser process,
-// the browser process effectively controls the lifetime of this class.
-//
-// TODO(shimazu): Let EmbeddedWorkerInstanceClientImpl own itself instead of
-// the big reference cycle.
-class EmbeddedWorkerInstanceClientImpl
+// All methods are called on the main thread.
+class CONTENT_EXPORT EmbeddedWorkerInstanceClientImpl
     : public mojom::EmbeddedWorkerInstanceClient {
  public:
   // Enum for UMA to record when StartWorker is received.
@@ -58,13 +47,11 @@
   // documentation.
   // TODO(shimazu): Create a service worker's execution context by this method
   // instead of just creating an instance of EmbeddedWorkerInstanceClient.
-  static void Create(
-      scoped_refptr<base::SingleThreadTaskRunner> io_thread_runner,
-      mojom::EmbeddedWorkerInstanceClientRequest request);
+  static void Create(mojom::EmbeddedWorkerInstanceClientRequest request);
 
   ~EmbeddedWorkerInstanceClientImpl() override;
 
-  // Called from ServiceWorkerContextClient.
+  // Destroys |this|. Called from ServiceWorkerContextClient.
   void WorkerContextDestroyed();
 
   // mojom::EmbeddedWorkerInstanceClient implementation (partially exposed to
@@ -72,8 +59,9 @@
   void StopWorker() override;
 
  private:
-  EmbeddedWorkerInstanceClientImpl(
-      scoped_refptr<base::SingleThreadTaskRunner> io_thread_runner,
+  friend class ServiceWorkerContextClientTest;
+
+  explicit EmbeddedWorkerInstanceClientImpl(
       mojom::EmbeddedWorkerInstanceClientRequest request);
 
   // mojom::EmbeddedWorkerInstanceClient implementation
@@ -97,15 +85,9 @@
 
   mojo::Binding<mojom::EmbeddedWorkerInstanceClient> binding_;
 
-  // This is valid before StartWorker is called. After that, this object
-  // will be passed to ServiceWorkerContextClient.
-  std::unique_ptr<EmbeddedWorkerInstanceClientImpl> temporal_self_;
-
   // nullptr means the worker is not running.
   std::unique_ptr<blink::WebEmbeddedWorker> worker_;
 
-  scoped_refptr<base::SingleThreadTaskRunner> io_thread_runner_;
-
   DISALLOW_COPY_AND_ASSIGN(EmbeddedWorkerInstanceClientImpl);
 };
 
diff --git a/content/renderer/service_worker/service_worker_context_client.cc b/content/renderer/service_worker/service_worker_context_client.cc
index 6cac5c3..2f7c3a2 100644
--- a/content/renderer/service_worker/service_worker_context_client.cc
+++ b/content/renderer/service_worker/service_worker_context_client.cc
@@ -523,7 +523,7 @@
     blink::mojom::ControllerServiceWorkerRequest controller_request,
     mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
     blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
-    std::unique_ptr<EmbeddedWorkerInstanceClientImpl> embedded_worker_client,
+    EmbeddedWorkerInstanceClientImpl* owner,
     mojom::EmbeddedWorkerStartTimingPtr start_timing,
     blink::mojom::RendererPreferenceWatcherRequest preference_watcher_request,
     std::unique_ptr<blink::URLLoaderFactoryBundleInfo> subresource_loaders,
@@ -539,8 +539,10 @@
       proxy_(nullptr),
       pending_service_worker_request_(std::move(service_worker_request)),
       pending_controller_request_(std::move(controller_request)),
-      embedded_worker_client_(std::move(embedded_worker_client)),
+      owner_(owner),
       start_timing_(std::move(start_timing)) {
+  DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(owner_);
   instance_host_ =
       mojom::ThreadSafeEmbeddedWorkerInstanceHostAssociatedPtr::Create(
           std::move(instance_host), main_thread_task_runner_);
@@ -579,6 +581,7 @@
 }
 
 ServiceWorkerContextClient::~ServiceWorkerContextClient() {
+  DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
   // TODO(crbug.com/907311): Remove this instrumentation after we identified
   // the cause of crash.
   if (report_debug_log_ && context_) {
@@ -607,8 +610,7 @@
   TRACE_EVENT_NESTABLE_ASYNC_END1("ServiceWorker", "ServiceWorkerContextClient",
                                   this, "Status", "WorkerContextFailedToStart");
 
-  DCHECK(embedded_worker_client_);
-  embedded_worker_client_->WorkerContextDestroyed();
+  owner_->WorkerContextDestroyed();
 }
 
 void ServiceWorkerContextClient::FailedToLoadInstalledClassicScript() {
@@ -639,10 +641,15 @@
 }
 
 void ServiceWorkerContextClient::WorkerScriptLoaded() {
-  // This could be called both from the main thread and worker thread as the
-  // comment in WebServiceWorkerContextClient describes.
-  if (!is_starting_installed_worker_)
-    (*instance_host_)->OnScriptLoaded();
+  DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(!is_starting_installed_worker_);
+  (*instance_host_)->OnScriptLoaded();
+  TRACE_EVENT_NESTABLE_ASYNC_END0("ServiceWorker", "LOAD_SCRIPT", this);
+}
+
+void ServiceWorkerContextClient::InstalledWorkerScriptLoaded() {
+  DCHECK(worker_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(is_starting_installed_worker_);
   TRACE_EVENT_NESTABLE_ASYNC_END0("ServiceWorker", "LOAD_SCRIPT", this);
 }
 
@@ -760,26 +767,14 @@
   DCHECK(g_worker_client_tls.Pointer()->Get() == nullptr);
   RecordDebugLog("WorkerContextDestroyed");
 
-  // TODO(shimazu): The signals to the browser should be in the order:
-  // (1) WorkerStopped (via mojo call EmbeddedWorkerInstanceHost.OnStopped())
-  // (2) ProviderDestroyed (via mojo call
-  // ServiceWorkerDispatcherHost.OnProviderDestroyed()), this is triggered by
-  // the following EmbeddedWorkerInstanceClientImpl::WorkerContextDestroyed(),
-  // which will eventually lead to destruction of the service worker provider.
-  // But currently EmbeddedWorkerInstanceHost interface is associated with
-  // EmbeddedWorkerInstanceClient interface, and ServiceWorkerDispatcherHost
-  // interface is associated with the IPC channel, since they are using
-  // different mojo message pipes, the FIFO ordering can not be guaranteed now.
-  // This will be solved once ServiceWorkerProvider{Host,Client} are mojoified
-  // and they are also associated with EmbeddedWorkerInstanceClient in other CLs
-  // (https://crrev.com/2653493009 and https://crrev.com/2779763004).
   (*instance_host_)->OnStopped();
 
-  DCHECK(embedded_worker_client_);
+  // base::Unretained is safe because |owner_| does not destroy itself until
+  // WorkerContextDestroyed is called.
   main_thread_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&EmbeddedWorkerInstanceClientImpl::WorkerContextDestroyed,
-                     std::move(embedded_worker_client_)));
+                     base::Unretained(owner_)));
 }
 
 void ServiceWorkerContextClient::CountFeature(
@@ -1882,8 +1877,7 @@
 void ServiceWorkerContextClient::StopWorker() {
   RecordDebugLog("StopWorker");
   DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
-  if (embedded_worker_client_)
-    embedded_worker_client_->StopWorker();
+  owner_->StopWorker();
 }
 
 base::WeakPtr<ServiceWorkerContextClient>
diff --git a/content/renderer/service_worker/service_worker_context_client.h b/content/renderer/service_worker/service_worker_context_client.h
index 04ae5a3..4f991d88 100644
--- a/content/renderer/service_worker/service_worker_context_client.h
+++ b/content/renderer/service_worker/service_worker_context_client.h
@@ -62,10 +62,11 @@
 // context. It enables communication between the embedder and Blink's
 // ServiceWorkerGlobalScope. It is created when the service worker begins
 // starting up, and destroyed when the service worker stops. It is owned by
-// EmbeddedWorkerInstanceClientImpl's internal WorkerWrapper class.
+// WebEmbeddedWorkerImpl (which is owned by EmbeddedWorkerInstanceClientImpl).
 //
-// Unless otherwise noted (here or in base class documentation), all methods are
-// called on the worker thread.
+// This class is created and destroyed on the main thread. Unless otherwise
+// noted (here or in base class documentation), all methods are called on the
+// worker thread.
 class CONTENT_EXPORT ServiceWorkerContextClient
     : public blink::WebServiceWorkerContextClient,
       public blink::mojom::ServiceWorker {
@@ -75,12 +76,12 @@
   static ServiceWorkerContextClient* ThreadSpecificInstance();
 
   // Called on the main thread.
-  // |is_starting_installed_worker| is true if the script is already installed
-  // and will be streamed from the browser process.
-  //
-  // |start_timing| should be initially populated with
-  // |start_worker_received_time|. This instance will fill in the rest during
-  // startup.
+  // - |is_starting_installed_worker| is true if the script is already installed
+  //   and will be streamed from the browser process.
+  // - |owner| must outlive this new instance.
+  // - |start_timing| should be initially populated with
+  //   |start_worker_received_time|. This instance will fill in the rest during
+  //   startup.
   ServiceWorkerContextClient(
       int embedded_worker_id,
       int64_t service_worker_version_id,
@@ -92,11 +93,12 @@
       blink::mojom::ControllerServiceWorkerRequest controller_request,
       mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
       blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
-      std::unique_ptr<EmbeddedWorkerInstanceClientImpl> embedded_worker_client,
+      EmbeddedWorkerInstanceClientImpl* owner,
       mojom::EmbeddedWorkerStartTimingPtr start_timing,
       blink::mojom::RendererPreferenceWatcherRequest preference_watcher_request,
       std::unique_ptr<blink::URLLoaderFactoryBundleInfo> subresource_loaders,
       scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner);
+  // Called on the main thread.
   ~ServiceWorkerContextClient() override;
 
   // WebServiceWorkerContextClient overrides.
@@ -105,6 +107,7 @@
   void FailedToLoadInstalledClassicScript() override;
   void FailedToFetchModuleScript() override;
   void WorkerScriptLoaded() override;
+  void InstalledWorkerScriptLoaded() override;
   void WorkerContextStarted(
       blink::WebServiceWorkerContextProxy* proxy) override;
   void WillEvaluateScript() override;
@@ -404,8 +407,8 @@
   blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr
       service_worker_provider_info_;
 
-  // This is valid from the ctor to WorkerContextDestroyed.
-  std::unique_ptr<EmbeddedWorkerInstanceClientImpl> embedded_worker_client_;
+  // Must be accessed on the main thread only.
+  EmbeddedWorkerInstanceClientImpl* owner_;
 
   blink::mojom::BlobRegistryPtr blob_registry_;
 
diff --git a/content/renderer/service_worker/service_worker_context_client_unittest.cc b/content/renderer/service_worker/service_worker_context_client_unittest.cc
index 6d4cc63..d0950b4 100644
--- a/content/renderer/service_worker/service_worker_context_client_unittest.cc
+++ b/content/renderer/service_worker/service_worker_context_client_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/time/tick_clock.h"
 #include "base/time/time.h"
 #include "content/child/thread_safe_sender.h"
+#include "content/common/service_worker/embedded_worker.mojom.h"
 #include "content/common/service_worker/service_worker_types.h"
 #include "content/public/common/content_client.h"
 #include "content/public/renderer/content_renderer_client.h"
@@ -43,6 +44,7 @@
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/platform/web_url_response.h"
 #include "third_party/blink/public/web/modules/service_worker/web_service_worker_context_proxy.h"
+#include "third_party/blink/public/web/web_embedded_worker.h"
 
 namespace content {
 
@@ -50,6 +52,9 @@
 
 // Pipes connected to the context client.
 struct ContextClientPipes {
+  // From the browser to EmbeddedWorkerInstanceClientImpl.
+  mojom::EmbeddedWorkerInstanceClientPtr embedded_worker_instance_client;
+
   // From the browser to ServiceWorkerContextClient.
   blink::mojom::ServiceWorkerPtr service_worker;
   blink::mojom::ControllerServiceWorkerPtr controller;
@@ -63,6 +68,29 @@
       registration_host_request;
 };
 
+// A fake WebEmbeddedWorker. This is needed to ensure WorkerContextDestroyed()
+// is called on EmbeddedWorkerInstanceClientImpl so it can self-destruct.
+class FakeWebEmbeddedWorker : public blink::WebEmbeddedWorker {
+ public:
+  explicit FakeWebEmbeddedWorker(
+      std::unique_ptr<ServiceWorkerContextClient> context_client)
+      : context_client_(std::move(context_client)) {}
+
+  void StartWorkerContext(const blink::WebEmbeddedWorkerStartData&) override {}
+  void TerminateWorkerContext() override {
+    v8::Local<v8::Context> local;
+    context_client_->WillDestroyWorkerContext(local);
+    context_client_->WorkerContextDestroyed();
+  }
+  void ResumeAfterDownload() override {}
+  void AddMessageToConsole(const blink::WebConsoleMessage&) override {}
+  void BindDevToolsAgent(mojo::ScopedInterfaceEndpointHandle,
+                         mojo::ScopedInterfaceEndpointHandle) override {}
+
+ private:
+  std::unique_ptr<ServiceWorkerContextClient> context_client_;
+};
+
 class MockWebServiceWorkerContextProxy
     : public blink::WebServiceWorkerContextProxy {
  public:
@@ -240,6 +268,7 @@
     message_loop_.SetTaskRunner(task_runner_);
     // Use this thread as the worker thread.
     WorkerThreadRegistry::Instance()->DidStartCurrentWorkerThread();
+    old_client_ = SetRendererClientForTesting(&renderer_client_);
   }
 
   void TearDown() override {
@@ -247,6 +276,7 @@
     // Unregister this thread from worker threads.
     WorkerThreadRegistry::Instance()->WillStopCurrentWorkerThread();
     task_runner_->RunUntilIdle();
+    SetRendererClientForTesting(old_client_);
   }
 
   void EnableServicification() {
@@ -264,9 +294,16 @@
 
   // Creates an ContextClient, whose pipes are connected to |out_pipes|, then
   // simulates that the service worker thread has started with |proxy|.
-  std::unique_ptr<ServiceWorkerContextClient> CreateContextClient(
+  //
+  // The client's lifetime is tied to
+  // |out_pipes->embedded_worker_instance_client|.
+  ServiceWorkerContextClient* CreateContextClient(
       ContextClientPipes* out_pipes,
       blink::WebServiceWorkerContextProxy* proxy) {
+    EmbeddedWorkerInstanceClientImpl* embedded_worker_instance_client =
+        new EmbeddedWorkerInstanceClientImpl(
+            mojo::MakeRequest(&out_pipes->embedded_worker_instance_client));
+
     auto service_worker_request = mojo::MakeRequest(&out_pipes->service_worker);
     auto controller_request = mojo::MakeRequest(&out_pipes->controller);
     mojom::EmbeddedWorkerInstanceHostAssociatedPtr embedded_worker_host_ptr;
@@ -274,21 +311,23 @@
         mojo::MakeRequestAssociatedWithDedicatedPipe(&embedded_worker_host_ptr);
     const GURL kScope("https://example.com");
     const GURL kScript("https://example.com/SW.js");
-    std::unique_ptr<ServiceWorkerContextClient> context_client =
-        std::make_unique<ServiceWorkerContextClient>(
-            1 /* embedded_worker_id */, 1 /* service_worker_version_id */,
-            kScope, kScript, false /* is_script_streaming */,
-            blink::mojom::RendererPreferences::New(),
-            std::move(service_worker_request), std::move(controller_request),
-            embedded_worker_host_ptr.PassInterface(), CreateProviderInfo(),
-            nullptr /* embedded_worker_client */,
-            mojom::EmbeddedWorkerStartTiming::New(),
-            nullptr /* preference_watcher_request */,
-            nullptr /* subresource_loaders */,
-            blink::scheduler::GetSingleThreadTaskRunnerForTesting());
+    auto context_client = std::make_unique<ServiceWorkerContextClient>(
+        1 /* embedded_worker_id */, 1 /* service_worker_version_id */, kScope,
+        kScript, false /* is_script_streaming */,
+        blink::mojom::RendererPreferences::New(),
+        std::move(service_worker_request), std::move(controller_request),
+        embedded_worker_host_ptr.PassInterface(), CreateProviderInfo(),
+        embedded_worker_instance_client,
+        mojom::EmbeddedWorkerStartTiming::New(),
+        nullptr /* preference_watcher_request */,
+        nullptr /* subresource_loaders */,
+        blink::scheduler::GetSingleThreadTaskRunnerForTesting());
+    auto* context_client_raw = context_client.get();
     context_client->SetReportDebugLogForTesting(false);
 
-    context_client->WorkerContextStarted(proxy);
+    embedded_worker_instance_client->worker_ =
+        std::make_unique<FakeWebEmbeddedWorker>(std::move(context_client));
+    context_client_raw->WorkerContextStarted(proxy);
 
     blink::mojom::ServiceWorkerHostAssociatedPtrInfo service_worker_host;
     out_pipes->service_worker_host_request =
@@ -305,13 +344,13 @@
     out_pipes->service_worker->InitializeGlobalScope(
         std::move(service_worker_host), std::move(registration_info));
     task_runner()->RunUntilIdle();
-    return context_client;
+    return context_client_raw;
   }
 
-  std::unique_ptr<ServiceWorkerContextClient> CreateIdleContextClient(
+  ServiceWorkerContextClient* CreateIdleContextClient(
       ContextClientPipes* pipes,
       MockWebServiceWorkerContextProxy* mock_proxy) {
-    std::unique_ptr<ServiceWorkerContextClient> context_client =
+    ServiceWorkerContextClient* context_client =
         CreateContextClient(pipes, mock_proxy);
     context_client->DidEvaluateScript(true /* success */);
     task_runner()->RunUntilIdle();
@@ -341,13 +380,14 @@
   scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
   base::test::ScopedFeatureList feature_list_;
   bool is_idle_;
+  ContentRendererClient renderer_client_;
+  ContentRendererClient* old_client_;
 };
 
 TEST_F(ServiceWorkerContextClientTest, Ping) {
   ContextClientPipes pipes;
   MockWebServiceWorkerContextProxy mock_proxy;
-  std::unique_ptr<ServiceWorkerContextClient> context_client =
-      CreateContextClient(&pipes, &mock_proxy);
+  CreateContextClient(&pipes, &mock_proxy);
 
   bool is_called = false;
   pipes.service_worker->Ping(CreateCallbackWithCalledFlag(&is_called));
@@ -358,7 +398,7 @@
 TEST_F(ServiceWorkerContextClientTest, DispatchFetchEvent) {
   ContextClientPipes pipes;
   MockWebServiceWorkerContextProxy mock_proxy;
-  std::unique_ptr<ServiceWorkerContextClient> context_client =
+  ServiceWorkerContextClient* context_client =
       CreateContextClient(&pipes, &mock_proxy);
   context_client->DidEvaluateScript(true /* success */);
   task_runner()->RunUntilIdle();
@@ -395,7 +435,7 @@
 
   ContextClientPipes pipes;
   MockWebServiceWorkerContextProxy mock_proxy;
-  std::unique_ptr<ServiceWorkerContextClient> context_client =
+  ServiceWorkerContextClient* context_client =
       CreateContextClient(&pipes, &mock_proxy);
   context_client->DidEvaluateScript(true /* success */);
   task_runner()->RunUntilIdle();
@@ -436,7 +476,7 @@
   EnableServicification();
   ContextClientPipes pipes;
   MockWebServiceWorkerContextProxy mock_proxy;
-  std::unique_ptr<ServiceWorkerContextClient> context_client =
+  ServiceWorkerContextClient* context_client =
       CreateContextClient(&pipes, &mock_proxy);
   context_client->DidEvaluateScript(true /* success */);
   task_runner()->RunUntilIdle();
@@ -473,8 +513,7 @@
   EnableServicification();
   ContextClientPipes pipes;
   MockWebServiceWorkerContextProxy mock_proxy;
-  std::unique_ptr<ServiceWorkerContextClient> context_client =
-      CreateIdleContextClient(&pipes, &mock_proxy);
+  CreateIdleContextClient(&pipes, &mock_proxy);
 
   const GURL expected_url("https://example.com/expected");
 
@@ -496,7 +535,8 @@
   EXPECT_TRUE(mock_proxy.fetch_events().empty());
 
   // Destruction of |context_client| should not hit any DCHECKs.
-  context_client.reset();
+  pipes.embedded_worker_instance_client.reset();
+  task_runner()->RunUntilIdle();
 }
 
 TEST_F(ServiceWorkerContextClientTest,
@@ -504,7 +544,7 @@
   EnableServicification();
   ContextClientPipes pipes;
   MockWebServiceWorkerContextProxy mock_proxy;
-  std::unique_ptr<ServiceWorkerContextClient> context_client =
+  ServiceWorkerContextClient* context_client =
       CreateIdleContextClient(&pipes, &mock_proxy);
 
   const GURL expected_url_1("https://example.com/expected_1");
@@ -558,7 +598,7 @@
   EnableServicification();
   ContextClientPipes pipes;
   MockWebServiceWorkerContextProxy mock_proxy;
-  std::unique_ptr<ServiceWorkerContextClient> context_client =
+  ServiceWorkerContextClient* context_client =
       CreateIdleContextClient(&pipes, &mock_proxy);
 
   int task_id = context_client->WillStartTask();
diff --git a/content/renderer/service_worker/service_worker_network_provider.cc b/content/renderer/service_worker/service_worker_network_provider.cc
index 6727910..1f90baeb 100644
--- a/content/renderer/service_worker/service_worker_network_provider.cc
+++ b/content/renderer/service_worker/service_worker_network_provider.cc
@@ -5,238 +5,13 @@
 #include "content/renderer/service_worker/service_worker_network_provider.h"
 
 #include "base/atomic_sequence_num.h"
-#include "content/child/child_thread_impl.h"
-#include "content/common/navigation_params.h"
-#include "content/common/service_worker/service_worker_utils.h"
-#include "content/renderer/render_frame_impl.h"
-#include "content/renderer/renderer_blink_platform_impl.h"
-#include "content/renderer/service_worker/web_service_worker_network_provider_base_impl.h"
-#include "content/renderer/service_worker/web_service_worker_network_provider_impl_for_frame.h"
-#include "ipc/ipc_sync_channel.h"
-#include "mojo/public/cpp/bindings/associated_group.h"
-#include "services/network/public/cpp/shared_url_loader_factory.h"
-#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
-#include "third_party/blink/public/common/frame/sandbox_flags.h"
-#include "third_party/blink/public/common/service_worker/service_worker_utils.h"
-#include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h"
-#include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
-#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h"
-#include "third_party/blink/public/platform/web_security_origin.h"
-#include "third_party/blink/public/web/web_local_frame.h"
 
 namespace content {
 
-namespace {
-
 // Must be unique in the child process.
-int GetNextProviderId() {
+int GetNextServiceWorkerProviderId() {
   static base::AtomicSequenceNumber sequence;
   return sequence.GetNext();  // We start at zero.
 }
 
-// Returns whether it's possible for a document whose frame is a descendant of
-// |frame| to be a secure context, not considering scheme exceptions (since any
-// document can be a secure context if it has a scheme exception). See
-// Document::isSecureContextImpl for more details.
-bool IsFrameSecure(blink::WebFrame* frame) {
-  while (frame) {
-    if (!frame->GetSecurityOrigin().IsPotentiallyTrustworthy())
-      return false;
-    frame = frame->Parent();
-  }
-  return true;
-}
-
-}  // namespace
-
-std::unique_ptr<blink::WebServiceWorkerNetworkProvider>
-ServiceWorkerNetworkProvider::CreateInvalidInstanceForNavigation() {
-  return std::make_unique<WebServiceWorkerNetworkProviderImplForFrame>(
-      base::WrapUnique(new ServiceWorkerNetworkProvider()), nullptr);
-}
-
-// static
-std::unique_ptr<blink::WebServiceWorkerNetworkProvider>
-ServiceWorkerNetworkProvider::CreateForNavigation(
-    RenderFrameImpl* frame,
-    const CommitNavigationParams* commit_params,
-    blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
-    scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory) {
-  blink::WebLocalFrame* web_frame = frame->GetWebFrame();
-  // Determine if a ServiceWorkerNetworkProvider should be created and properly
-  // initialized for the navigation. A default ServiceWorkerNetworkProvider
-  // will always be created since it is expected in a certain number of places,
-  // however it will have an invalid id.
-  bool should_create_provider = false;
-  int provider_id = kInvalidServiceWorkerProviderId;
-  if (commit_params) {
-    should_create_provider = commit_params->should_create_service_worker;
-    provider_id = commit_params->service_worker_provider_id;
-  } else {
-    // It'd be convenient to check web_frame->GetSecurityOrigin().IsOpaque()
-    // here instead of just looking at the sandbox flags, but
-    // GetSecurityOrigin() crashes because the frame does not yet have a
-    // security context.
-    should_create_provider =
-        ((web_frame->EffectiveSandboxFlags() &
-          blink::WebSandboxFlags::kOrigin) != blink::WebSandboxFlags::kOrigin);
-  }
-
-  // If we shouldn't create a real ServiceWorkerNetworkProvider, return one with
-  // an invalid id.
-  if (!should_create_provider) {
-    return CreateInvalidInstanceForNavigation();
-  }
-
-  // Otherwise, create the ServiceWorkerNetworkProvider.
-
-  // Ideally Document::IsSecureContext would be called here, but the document is
-  // not created yet, and due to redirects the URL may change. So pass
-  // is_parent_frame_secure to the browser process, so it can determine the
-  // context security when deciding whether to allow a service worker to control
-  // the document.
-  const bool is_parent_frame_secure = IsFrameSecure(web_frame->Parent());
-
-  // If the browser process did not assign a provider id already, assign one
-  // now (see class comments for content::ServiceWorkerProviderHost).
-  DCHECK(ServiceWorkerUtils::IsBrowserAssignedProviderId(provider_id) ||
-         provider_id == kInvalidServiceWorkerProviderId);
-  if (provider_id == kInvalidServiceWorkerProviderId)
-    provider_id = GetNextProviderId();
-
-  auto provider = base::WrapUnique(new ServiceWorkerNetworkProvider(
-      frame->GetRoutingID(),
-      blink::mojom::ServiceWorkerProviderType::kForWindow, provider_id,
-      is_parent_frame_secure, std::move(controller_info),
-      std::move(fallback_loader_factory)));
-  return std::make_unique<WebServiceWorkerNetworkProviderImplForFrame>(
-      std::move(provider), frame);
-}
-
-// static
-std::unique_ptr<ServiceWorkerNetworkProvider>
-ServiceWorkerNetworkProvider::CreateForSharedWorker(
-    blink::mojom::ServiceWorkerProviderInfoForSharedWorkerPtr info,
-    network::mojom::URLLoaderFactoryAssociatedPtrInfo
-        script_loader_factory_info,
-    blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
-    scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory) {
-  // S13nServiceWorker: |info| holds info about the precreated provider host.
-  if (info) {
-    DCHECK(blink::ServiceWorkerUtils::IsServicificationEnabled());
-    return base::WrapUnique(new ServiceWorkerNetworkProvider(
-        std::move(info), std::move(script_loader_factory_info),
-        std::move(controller_info), std::move(fallback_loader_factory)));
-  }
-
-  return base::WrapUnique(new ServiceWorkerNetworkProvider(
-      MSG_ROUTING_NONE,
-      blink::mojom::ServiceWorkerProviderType::kForSharedWorker,
-      GetNextProviderId(), true /* is_parent_frame_secure */,
-      nullptr /* controller_service_worker */,
-      std::move(fallback_loader_factory)));
-}
-
-// static
-ServiceWorkerNetworkProvider*
-ServiceWorkerNetworkProvider::FromWebServiceWorkerNetworkProvider(
-    blink::WebServiceWorkerNetworkProvider* provider) {
-  if (!provider) {
-    DCHECK(blink::ServiceWorkerUtils::IsServicificationEnabled());
-    return nullptr;
-  }
-  return static_cast<WebServiceWorkerNetworkProviderBaseImpl*>(provider)
-      ->provider();
-}
-
-ServiceWorkerNetworkProvider::~ServiceWorkerNetworkProvider() {
-  if (context()) {
-    context()->OnNetworkProviderDestroyed();
-  }
-}
-
-int ServiceWorkerNetworkProvider::provider_id() const {
-  if (!context())
-    return kInvalidServiceWorkerProviderId;
-  return context()->provider_id();
-}
-
-blink::mojom::ControllerServiceWorkerMode
-ServiceWorkerNetworkProvider::IsControlledByServiceWorker() const {
-  if (!context())
-    return blink::mojom::ControllerServiceWorkerMode::kNoController;
-  return context()->IsControlledByServiceWorker();
-}
-
-void ServiceWorkerNetworkProvider::DispatchNetworkQuiet() {
-  if (!context())
-    return;
-  context()->DispatchNetworkQuiet();
-}
-
-// Creates an invalid instance (provider_id() returns
-// kInvalidServiceWorkerProviderId).
-ServiceWorkerNetworkProvider::ServiceWorkerNetworkProvider() {}
-
-// Constructor for service worker clients.
-ServiceWorkerNetworkProvider::ServiceWorkerNetworkProvider(
-    int route_id,
-    blink::mojom::ServiceWorkerProviderType provider_type,
-    int provider_id,
-    bool is_parent_frame_secure,
-    blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
-    scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory) {
-  DCHECK_NE(provider_id, kInvalidServiceWorkerProviderId);
-  DCHECK(provider_type == blink::mojom::ServiceWorkerProviderType::kForWindow ||
-         provider_type ==
-             blink::mojom::ServiceWorkerProviderType::kForSharedWorker);
-
-  auto host_info = blink::mojom::ServiceWorkerProviderHostInfo::New(
-      provider_id, route_id, provider_type, is_parent_frame_secure,
-      nullptr /* host_request */, nullptr /* client_ptr_info */);
-  blink::mojom::ServiceWorkerContainerAssociatedRequest client_request =
-      mojo::MakeRequest(&host_info->client_ptr_info);
-  blink::mojom::ServiceWorkerContainerHostAssociatedPtrInfo host_ptr_info;
-  host_info->host_request = mojo::MakeRequest(&host_ptr_info);
-  DCHECK(host_info->host_request.is_pending());
-  DCHECK(host_info->host_request.handle().is_valid());
-
-  context_ = base::MakeRefCounted<ServiceWorkerProviderContext>(
-      provider_id, provider_type, std::move(client_request),
-      std::move(host_ptr_info), std::move(controller_info),
-      std::move(fallback_loader_factory));
-
-  if (ChildThreadImpl::current()) {
-    ChildThreadImpl::current()->channel()->GetRemoteAssociatedInterface(
-        &dispatcher_host_);
-    dispatcher_host_->OnProviderCreated(std::move(host_info));
-  } else {
-    // current() may be null in tests. Silently drop messages sent over
-    // ServiceWorkerContainerHost since we couldn't set it up correctly due to
-    // this test limitation. This way we don't crash when the associated
-    // interface ptr is used.
-    //
-    // TODO(falken): Just give SWPC a null interface ptr and make the callsites
-    // deal with it. They are supposed to anyway because
-    // OnNetworkProviderDestroyed() can reset the ptr to null at any time.
-    mojo::AssociateWithDisconnectedPipe(host_info->host_request.PassHandle());
-  }
-}
-
-// Constructor for precreated shared worker.
-ServiceWorkerNetworkProvider::ServiceWorkerNetworkProvider(
-    blink::mojom::ServiceWorkerProviderInfoForSharedWorkerPtr info,
-    network::mojom::URLLoaderFactoryAssociatedPtrInfo
-        script_loader_factory_info,
-    blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
-    scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory) {
-  context_ = base::MakeRefCounted<ServiceWorkerProviderContext>(
-      info->provider_id,
-      blink::mojom::ServiceWorkerProviderType::kForSharedWorker,
-      std::move(info->client_request), std::move(info->host_ptr_info),
-      std::move(controller_info), std::move(fallback_loader_factory));
-  if (script_loader_factory_info.is_valid())
-    script_loader_factory_.Bind(std::move(script_loader_factory_info));
-}
-
 }  // namespace content
diff --git a/content/renderer/service_worker/service_worker_network_provider.h b/content/renderer/service_worker/service_worker_network_provider.h
index 9d7d57d2..c6dde50 100644
--- a/content/renderer/service_worker/service_worker_network_provider.h
+++ b/content/renderer/service_worker/service_worker_network_provider.h
@@ -5,159 +5,9 @@
 #ifndef CONTENT_RENDERER_SERVICE_WORKER_SERVICE_WORKER_NETWORK_PROVIDER_H_
 #define CONTENT_RENDERER_SERVICE_WORKER_SERVICE_WORKER_NETWORK_PROVIDER_H_
 
-#include <stdint.h>
-
-#include <memory>
-
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "base/supports_user_data.h"
-#include "content/common/content_export.h"
-#include "content/renderer/service_worker/service_worker_provider_context.h"
-#include "third_party/blink/public/mojom/service_worker/controller_service_worker.mojom.h"
-#include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
-#include "third_party/blink/public/mojom/service_worker/service_worker_provider_type.mojom.h"
-
-namespace blink {
-class WebServiceWorkerNetworkProvider;
-}  // namespace blink
-
-namespace network {
-class SharedURLLoaderFactory;
-}  // namespace network
-
 namespace content {
 
-namespace mojom {
-class URLLoaderFactory;
-}
-
-struct CommitNavigationParams;
-class RenderFrameImpl;
-class ServiceWorkerProviderContext;
-
-// ServiceWorkerNetworkProvider enables the browser process to recognize
-// resource requests from Blink that should be handled by service worker
-// machinery rather than the usual network stack.
-//
-// All requests associated with a network provider are tagged with its
-// |provider_id| (from ServiceWorkerProviderContext). The browser
-// process can then route the request to this provider's corresponding
-// ServiceWorkerProviderHost.
-//
-// It is created for service worker clients (documents and shared workers). It
-// is instantiated prior to the main resource load being started and remains
-// allocated until after the last subresource load has occurred. It is owned by
-// the appropriate DocumentLoader for the provider (i.e., the loader for a
-// document, or the shadow page's loader for a shared worker).
-// Each request coming from the DocumentLoader is tagged with the provider_id in
-// WillSendRequest.
-class CONTENT_EXPORT ServiceWorkerNetworkProvider {
- public:
-  // Creates a ServiceWorkerNetworkProvider for navigation and wraps it
-  // with WebServiceWorkerNetworkProvider to be owned by Blink.
-  //
-  // |commit_params| are navigation parameters that were transmitted to the
-  // renderer by the browser on a navigation commit. It is null if we have not
-  // yet heard from the browser (currently only during the time it takes from
-  // having the renderer initiate a navigation until the browser commits it).
-  // Note: in particular, provisional load failure do not provide
-  // |commit_params|.
-  // TODO(ahemery): Update this comment when do not create placeholder document
-  // loaders for renderer-initiated navigations. In this case, this should never
-  // be null.
-  //
-  // For S13nServiceWorker:
-  // |controller_info| contains the endpoint and object info that is needed to
-  // set up the controller service worker for the client.
-  // |fallback_loader_factory| is a default loader factory for fallback
-  // requests, and is used when we create a subresource loader for controllees.
-  // This is non-null only if the provider is created for controllees, and if
-  // the loading context, e.g. a frame, provides it.
-  static std::unique_ptr<blink::WebServiceWorkerNetworkProvider>
-  CreateForNavigation(
-      RenderFrameImpl* frame,
-      const CommitNavigationParams* commit_params,
-      blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
-      scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory);
-
-  // Creates a "null" network provider for a frame (it has an invalid provider
-  // id). Useful when the frame should not use service worker.
-  static std::unique_ptr<blink::WebServiceWorkerNetworkProvider>
-  CreateInvalidInstanceForNavigation();
-
-  // Creates a ServiceWorkerNetworkProvider for a shared worker (as a
-  // non-document service worker client).
-  //
-  // For NetworkService (PlzWorker):
-  // |controller_info| contains the endpoint and object info that is needed to
-  // set up the controller service worker for the client.
-  static std::unique_ptr<ServiceWorkerNetworkProvider> CreateForSharedWorker(
-      blink::mojom::ServiceWorkerProviderInfoForSharedWorkerPtr info,
-      network::mojom::URLLoaderFactoryAssociatedPtrInfo
-          script_loader_factory_info,
-      blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
-      scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory);
-
-  static ServiceWorkerNetworkProvider* FromWebServiceWorkerNetworkProvider(
-      blink::WebServiceWorkerNetworkProvider*);
-
-  ~ServiceWorkerNetworkProvider();
-
-  int provider_id() const;
-  ServiceWorkerProviderContext* context() const { return context_.get(); }
-
-  network::mojom::URLLoaderFactory* script_loader_factory() {
-    return script_loader_factory_.get();
-  }
-
-  // Returns whether the context this provider is for is controlled by a service
-  // worker.
-  blink::mojom::ControllerServiceWorkerMode IsControlledByServiceWorker() const;
-
-  // Called when blink::IdlenessDetector emits its network idle signal.
-  void DispatchNetworkQuiet();
-
- private:
-  // Creates an invalid instance (provider_id() returns
-  // kInvalidServiceWorkerProviderId).
-  ServiceWorkerNetworkProvider();
-
-  // |is_parent_frame_secure| is only relevant when the
-  // |type| is kForWindow.
-  //
-  // For S13nServiceWorker:
-  // See the comment at CreateForNavigation() for |controller_info| and
-  // |fallback_loader_factory|.
-  ServiceWorkerNetworkProvider(
-      int route_id,
-      blink::mojom::ServiceWorkerProviderType type,
-      int provider_id,
-      bool is_parent_frame_secure,
-      blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
-      scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory);
-
-  ServiceWorkerNetworkProvider(
-      blink::mojom::ServiceWorkerProviderInfoForSharedWorkerPtr info,
-      network::mojom::URLLoaderFactoryAssociatedPtrInfo
-          script_loader_factory_info,
-      blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
-      scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory);
-
-  // |context_| is null if |this| is an invalid instance, in which case there is
-  // no connection to the browser process.
-  scoped_refptr<ServiceWorkerProviderContext> context_;
-
-  blink::mojom::ServiceWorkerDispatcherHostAssociatedPtr dispatcher_host_;
-
-  // For shared worker contexts. The URL loader factory for loading the worker's
-  // scripts.
-  network::mojom::URLLoaderFactoryAssociatedPtr script_loader_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(ServiceWorkerNetworkProvider);
-};
+int GetNextServiceWorkerProviderId();
 
 }  // namespace content
 
diff --git a/content/renderer/service_worker/service_worker_provider_context.h b/content/renderer/service_worker/service_worker_provider_context.h
index 57e6bc1..a8ed9398 100644
--- a/content/renderer/service_worker/service_worker_provider_context.h
+++ b/content/renderer/service_worker/service_worker_provider_context.h
@@ -136,9 +136,9 @@
   // host.
   blink::mojom::ServiceWorkerContainerHostPtrInfo CloneContainerHostPtrInfo();
 
-  // Called when ServiceWorkerNetworkProvider is destructed. This function
+  // Called when WebServiceWorkerNetworkProvider is destructed. This function
   // severs the Mojo binding to the browser-side ServiceWorkerProviderHost. The
-  // reason ServiceWorkerNetworkProvider is special compared to the other
+  // reason WebServiceWorkerNetworkProvider is special compared to the other
   // providers, is that it is destructed synchronously when a service worker
   // client (Document) is removed from the DOM. Once this happens, the
   // ServiceWorkerProviderHost must destruct quickly in order to remove the
diff --git a/content/renderer/service_worker/web_service_worker_network_provider_base_impl.cc b/content/renderer/service_worker/web_service_worker_network_provider_base_impl.cc
deleted file mode 100644
index ac19ee5f..0000000
--- a/content/renderer/service_worker/web_service_worker_network_provider_base_impl.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/renderer/service_worker/web_service_worker_network_provider_base_impl.h"
-
-#include "content/renderer/service_worker/service_worker_network_provider.h"
-
-namespace content {
-
-WebServiceWorkerNetworkProviderBaseImpl::
-    WebServiceWorkerNetworkProviderBaseImpl(
-        std::unique_ptr<ServiceWorkerNetworkProvider> provider)
-    : provider_(std::move(provider)) {}
-
-WebServiceWorkerNetworkProviderBaseImpl::
-    ~WebServiceWorkerNetworkProviderBaseImpl() = default;
-
-blink::mojom::ControllerServiceWorkerMode
-WebServiceWorkerNetworkProviderBaseImpl::IsControlledByServiceWorker() {
-  return provider_->IsControlledByServiceWorker();
-}
-
-int64_t WebServiceWorkerNetworkProviderBaseImpl::ControllerServiceWorkerID() {
-  if (provider_->context())
-    return provider_->context()->GetControllerVersionId();
-  return blink::mojom::kInvalidServiceWorkerVersionId;
-}
-
-}  // namespace content
diff --git a/content/renderer/service_worker/web_service_worker_network_provider_base_impl.h b/content/renderer/service_worker/web_service_worker_network_provider_base_impl.h
deleted file mode 100644
index df10352a..0000000
--- a/content/renderer/service_worker/web_service_worker_network_provider_base_impl.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_RENDERER_SERVICE_WORKER_WEB_SERVICE_WORKER_NETWORK_PROVIDER_BASE_IMPL_H_
-#define CONTENT_RENDERER_SERVICE_WORKER_WEB_SERVICE_WORKER_NETWORK_PROVIDER_BASE_IMPL_H_
-
-#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h"
-
-namespace content {
-
-class ServiceWorkerNetworkProvider;
-
-// This is the base implementation of WebServiceWorkerNetworkProvider. All
-// functions are called on the main thread only.
-class WebServiceWorkerNetworkProviderBaseImpl
-    : public blink::WebServiceWorkerNetworkProvider {
- public:
-  explicit WebServiceWorkerNetworkProviderBaseImpl(
-      std::unique_ptr<ServiceWorkerNetworkProvider>);
-  ~WebServiceWorkerNetworkProviderBaseImpl() override;
-
-  // Implement blink::WebServiceWorkerNetworkProvider.
-  blink::mojom::ControllerServiceWorkerMode IsControlledByServiceWorker() final;
-  int64_t ControllerServiceWorkerID() final;
-
-  ServiceWorkerNetworkProvider* provider() { return provider_.get(); }
-
- private:
-  std::unique_ptr<ServiceWorkerNetworkProvider> provider_;
-};
-
-}  // namespace content
-
-#endif  // CONTENT_RENDERER_SERVICE_WORKER_WEB_SERVICE_WORKER_NETWORK_PROVIDER_BASE_IMPL_H_
diff --git a/content/renderer/service_worker/web_service_worker_network_provider_impl_for_frame.cc b/content/renderer/service_worker/web_service_worker_network_provider_impl_for_frame.cc
index f86037f..89c1a888 100644
--- a/content/renderer/service_worker/web_service_worker_network_provider_impl_for_frame.cc
+++ b/content/renderer/service_worker/web_service_worker_network_provider_impl_for_frame.cc
@@ -6,6 +6,8 @@
 
 #include <utility>
 
+#include "content/common/navigation_params.h"
+#include "content/common/service_worker/service_worker_utils.h"
 #include "content/public/common/origin_util.h"
 #include "content/public/renderer/render_frame_observer.h"
 #include "content/renderer/loader/request_extra_data.h"
@@ -18,6 +20,21 @@
 
 namespace content {
 
+namespace {
+// Returns whether it's possible for a document whose frame is a descendant of
+// |frame| to be a secure context, not considering scheme exceptions (since any
+// document can be a secure context if it has a scheme exception). See
+// Document::isSecureContextImpl for more details.
+bool IsFrameSecure(blink::WebFrame* frame) {
+  while (frame) {
+    if (!frame->GetSecurityOrigin().IsPotentiallyTrustworthy())
+      return false;
+    frame = frame->Parent();
+  }
+  return true;
+}
+}  // namespace
+
 class WebServiceWorkerNetworkProviderImplForFrame::NewDocumentObserver
     : public RenderFrameObserver {
  public:
@@ -36,7 +53,7 @@
       // service workers so created the network provider, but it turns out it is
       // not eligible because it is CSP sandboxed.
       web_loader->SetServiceWorkerNetworkProvider(
-          ServiceWorkerNetworkProvider::CreateInvalidInstanceForNavigation());
+          WebServiceWorkerNetworkProviderImplForFrame::CreateInvalidInstance());
       // |this| and its owner are destroyed.
       return;
     }
@@ -53,24 +70,114 @@
   WebServiceWorkerNetworkProviderImplForFrame* owner_;
 };
 
+// static
+std::unique_ptr<WebServiceWorkerNetworkProviderImplForFrame>
+WebServiceWorkerNetworkProviderImplForFrame::Create(
+    RenderFrameImpl* frame,
+    const CommitNavigationParams* commit_params,
+    blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
+    scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory) {
+  blink::WebLocalFrame* web_frame = frame->GetWebFrame();
+  // Determine if a provider should be created and properly initialized for the
+  // navigation. A default provider will always be created since it is expected
+  // in a certain number of places, however it will have an invalid id.
+  bool should_create_provider = false;
+  int provider_id = kInvalidServiceWorkerProviderId;
+  if (commit_params) {
+    should_create_provider = commit_params->should_create_service_worker;
+    provider_id = commit_params->service_worker_provider_id;
+  } else {
+    // It'd be convenient to check web_frame->GetSecurityOrigin().IsOpaque()
+    // here instead of just looking at the sandbox flags, but
+    // GetSecurityOrigin() crashes because the frame does not yet have a
+    // security context.
+    should_create_provider =
+        ((web_frame->EffectiveSandboxFlags() &
+          blink::WebSandboxFlags::kOrigin) != blink::WebSandboxFlags::kOrigin);
+  }
+
+  // If we shouldn't create a real provider, return one with an invalid id.
+  if (!should_create_provider) {
+    return CreateInvalidInstance();
+  }
+
+  // Otherwise, create the provider.
+
+  // Ideally Document::IsSecureContext would be called here, but the document is
+  // not created yet, and due to redirects the URL may change. So pass
+  // is_parent_frame_secure to the browser process, so it can determine the
+  // context security when deciding whether to allow a service worker to control
+  // the document.
+  const bool is_parent_frame_secure = IsFrameSecure(web_frame->Parent());
+
+  // If the browser process did not assign a provider id already, assign one
+  // now (see class comments for content::ServiceWorkerProviderHost).
+  DCHECK(ServiceWorkerUtils::IsBrowserAssignedProviderId(provider_id) ||
+         provider_id == kInvalidServiceWorkerProviderId);
+  if (provider_id == kInvalidServiceWorkerProviderId)
+    provider_id = GetNextServiceWorkerProviderId();
+
+  auto provider =
+      base::WrapUnique(new WebServiceWorkerNetworkProviderImplForFrame(frame));
+
+  auto host_info = blink::mojom::ServiceWorkerProviderHostInfo::New(
+      provider_id, frame->GetRoutingID(),
+      blink::mojom::ServiceWorkerProviderType::kForWindow,
+      is_parent_frame_secure, nullptr /* host_request */,
+      nullptr /* client_ptr_info */);
+  blink::mojom::ServiceWorkerContainerAssociatedRequest client_request =
+      mojo::MakeRequest(&host_info->client_ptr_info);
+  blink::mojom::ServiceWorkerContainerHostAssociatedPtrInfo host_ptr_info;
+  host_info->host_request = mojo::MakeRequest(&host_ptr_info);
+
+  provider->context_ = base::MakeRefCounted<ServiceWorkerProviderContext>(
+      provider_id, blink::mojom::ServiceWorkerProviderType::kForWindow,
+      std::move(client_request), std::move(host_ptr_info),
+      std::move(controller_info), std::move(fallback_loader_factory));
+
+  if (ChildThreadImpl::current()) {
+    ChildThreadImpl::current()->channel()->GetRemoteAssociatedInterface(
+        &provider->dispatcher_host_);
+    provider->dispatcher_host_->OnProviderCreated(std::move(host_info));
+  } else {
+    // current() may be null in tests. Silently drop messages sent over
+    // ServiceWorkerContainerHost since we couldn't set it up correctly due to
+    // this test limitation. This way we don't crash when the associated
+    // interface ptr is used.
+    //
+    // TODO(falken): Just give ServiceWorkerProviderContext a null interface ptr
+    // and make the callsites deal with it. They are supposed to anyway because
+    // OnNetworkProviderDestroyed() can reset the ptr to null at any time.
+    mojo::AssociateWithDisconnectedPipe(host_info->host_request.PassHandle());
+  }
+  return provider;
+}
+
+// static
+std::unique_ptr<WebServiceWorkerNetworkProviderImplForFrame>
+WebServiceWorkerNetworkProviderImplForFrame::CreateInvalidInstance() {
+  return base::WrapUnique(
+      new WebServiceWorkerNetworkProviderImplForFrame(nullptr));
+}
+
 WebServiceWorkerNetworkProviderImplForFrame::
-    WebServiceWorkerNetworkProviderImplForFrame(
-        std::unique_ptr<ServiceWorkerNetworkProvider> provider,
-        RenderFrameImpl* frame)
-    : WebServiceWorkerNetworkProviderBaseImpl(std::move(provider)) {
+    WebServiceWorkerNetworkProviderImplForFrame(RenderFrameImpl* frame) {
   if (frame)
     observer_ = std::make_unique<NewDocumentObserver>(this, frame);
 }
 
 WebServiceWorkerNetworkProviderImplForFrame::
-    ~WebServiceWorkerNetworkProviderImplForFrame() = default;
+    ~WebServiceWorkerNetworkProviderImplForFrame() {
+  if (context())
+    context()->OnNetworkProviderDestroyed();
+}
 
 void WebServiceWorkerNetworkProviderImplForFrame::WillSendRequest(
     blink::WebURLRequest& request) {
   if (!request.GetExtraData())
     request.SetExtraData(std::make_unique<RequestExtraData>());
   auto* extra_data = static_cast<RequestExtraData*>(request.GetExtraData());
-  extra_data->set_service_worker_provider_id(provider()->provider_id());
+  extra_data->set_service_worker_provider_id(provider_id());
 
   // If the provider does not have a controller at this point, the renderer
   // expects the request to never be handled by a service worker, so call
@@ -92,8 +199,22 @@
   // requests or non-S13nSW case, the browser process sets the id on the
   // request when dispatching the fetch event to the service worker. But it
   // doesn't hurt to set it always.
-  if (provider()->context())
-    request.SetFetchWindowId(provider()->context()->fetch_request_window_id());
+  if (context())
+    request.SetFetchWindowId(context()->fetch_request_window_id());
+}
+
+blink::mojom::ControllerServiceWorkerMode
+WebServiceWorkerNetworkProviderImplForFrame::IsControlledByServiceWorker() {
+  if (!context())
+    return blink::mojom::ControllerServiceWorkerMode::kNoController;
+  return context()->IsControlledByServiceWorker();
+}
+
+int64_t
+WebServiceWorkerNetworkProviderImplForFrame::ControllerServiceWorkerID() {
+  if (!context())
+    return blink::mojom::kInvalidServiceWorkerVersionId;
+  return context()->GetControllerVersionId();
 }
 
 std::unique_ptr<blink::WebURLLoader>
@@ -112,8 +233,7 @@
 
   // We need SubresourceLoaderFactory populated in order to create our own
   // URLLoader for subresource loading.
-  if (!provider()->context() ||
-      !provider()->context()->GetSubresourceLoaderFactory())
+  if (!context() || !context()->GetSubresourceLoaderFactory())
     return nullptr;
 
   // If the URL is not http(s) or otherwise whitelisted, do not intercept the
@@ -138,16 +258,24 @@
       RenderThreadImpl::current()->resource_dispatcher(),
       std::move(task_runner_handle),
       base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
-          provider()->context()->GetSubresourceLoaderFactory()));
+          context()->GetSubresourceLoaderFactory()));
 }
 
 void WebServiceWorkerNetworkProviderImplForFrame::DispatchNetworkQuiet() {
-  provider()->DispatchNetworkQuiet();
+  if (!context())
+    return;
+  context()->DispatchNetworkQuiet();
+}
+
+int WebServiceWorkerNetworkProviderImplForFrame::provider_id() const {
+  if (!context_)
+    return kInvalidServiceWorkerProviderId;
+  return context_->provider_id();
 }
 
 void WebServiceWorkerNetworkProviderImplForFrame::NotifyExecutionReady() {
-  if (provider()->context())
-    provider()->context()->NotifyExecutionReady();
+  if (context())
+    context()->NotifyExecutionReady();
 }
 
 }  // namespace content
diff --git a/content/renderer/service_worker/web_service_worker_network_provider_impl_for_frame.h b/content/renderer/service_worker/web_service_worker_network_provider_impl_for_frame.h
index 9baad27..a742ee10 100644
--- a/content/renderer/service_worker/web_service_worker_network_provider_impl_for_frame.h
+++ b/content/renderer/service_worker/web_service_worker_network_provider_impl_for_frame.h
@@ -7,36 +7,80 @@
 
 #include <memory>
 
-#include "content/renderer/service_worker/service_worker_network_provider.h"
-#include "content/renderer/service_worker/web_service_worker_network_provider_base_impl.h"
+#include "content/common/content_export.h"
+#include "content/renderer/service_worker/service_worker_provider_context.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "third_party/blink/public/mojom/service_worker/controller_service_worker.mojom.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
+#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h"
 
 namespace content {
-
+struct CommitNavigationParams;
 class RenderFrameImpl;
 
-// An WebServiceWorkerNetworkProvider for frame. This wraps
-// ServiceWorkerNetworkProvider implementation and is owned by blink.
-class WebServiceWorkerNetworkProviderImplForFrame final
-    : public WebServiceWorkerNetworkProviderBaseImpl {
+// The WebServiceWorkerNetworkProvider implementation used for frames.
+class CONTENT_EXPORT WebServiceWorkerNetworkProviderImplForFrame final
+    : public blink::WebServiceWorkerNetworkProvider {
  public:
-  WebServiceWorkerNetworkProviderImplForFrame(
-      std::unique_ptr<ServiceWorkerNetworkProvider> provider,
-      RenderFrameImpl* frame);
+  // Creates a network provider for |frame|.
+  //
+  // |commit_params| are navigation parameters that were transmitted to the
+  // renderer by the browser on a navigation commit. It is null if we have not
+  // yet heard from the browser (currently only during the time it takes from
+  // having the renderer initiate a navigation until the browser commits it).
+  // Note: in particular, provisional load failure do not provide
+  // |commit_params|.
+  // TODO(ahemery): Update this comment when do not create placeholder document
+  // loaders for renderer-initiated navigations. In this case, this should never
+  // be null.
+  //
+  // For S13nServiceWorker:
+  // |controller_info| contains the endpoint and object info that is needed to
+  // set up the controller service worker for the client.
+  // |fallback_loader_factory| is a default loader factory for fallback
+  // requests, and is used when we create a subresource loader for controllees.
+  // This is non-null only if the provider is created for controllees, and if
+  // the loading context, e.g. a frame, provides it.
+  static std::unique_ptr<WebServiceWorkerNetworkProviderImplForFrame> Create(
+      RenderFrameImpl* frame,
+      const CommitNavigationParams* commit_params,
+      blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
+      scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory);
+
+  // Creates an invalid instance. It has a null |context()|.
+  // TODO(falken): Just use null instead of this.
+  static std::unique_ptr<WebServiceWorkerNetworkProviderImplForFrame>
+  CreateInvalidInstance();
+
   ~WebServiceWorkerNetworkProviderImplForFrame() override;
 
-  // Implements WebServiceWorkerNetworkProviderBaseImpl.
+  // Implements WebServiceWorkerNetworkProvider.
   void WillSendRequest(blink::WebURLRequest& request) override;
+  blink::mojom::ControllerServiceWorkerMode IsControlledByServiceWorker()
+      override;
+  int64_t ControllerServiceWorkerID() override;
   std::unique_ptr<blink::WebURLLoader> CreateURLLoader(
       const blink::WebURLRequest& request,
       std::unique_ptr<blink::scheduler::WebResourceLoadingTaskRunnerHandle>
           task_runner_handle) override;
   void DispatchNetworkQuiet() override;
 
+  int provider_id() const;
+  ServiceWorkerProviderContext* context() { return context_.get(); }
+
  private:
   class NewDocumentObserver;
 
+  explicit WebServiceWorkerNetworkProviderImplForFrame(RenderFrameImpl* frame);
+
   void NotifyExecutionReady();
 
+  // |context_| is null if |this| is an invalid instance, in which case there is
+  // no connection to the browser process.
+  scoped_refptr<ServiceWorkerProviderContext> context_;
+
+  blink::mojom::ServiceWorkerDispatcherHostAssociatedPtr dispatcher_host_;
+
   std::unique_ptr<NewDocumentObserver> observer_;
 };
 
diff --git a/content/renderer/shared_worker/embedded_shared_worker_stub.cc b/content/renderer/shared_worker/embedded_shared_worker_stub.cc
index 5481f706..479b262 100644
--- a/content/renderer/shared_worker/embedded_shared_worker_stub.cc
+++ b/content/renderer/shared_worker/embedded_shared_worker_stub.cc
@@ -253,21 +253,18 @@
 
 std::unique_ptr<blink::WebServiceWorkerNetworkProvider>
 EmbeddedSharedWorkerStub::CreateServiceWorkerNetworkProvider() {
-  std::unique_ptr<ServiceWorkerNetworkProvider> provider =
-      ServiceWorkerNetworkProvider::CreateForSharedWorker(
-          std::move(service_worker_provider_info_),
-          std::move(main_script_loader_factory_), std::move(controller_info_),
-          subresource_loader_factories_);
-
-  return std::make_unique<WebServiceWorkerNetworkProviderImplForWorker>(
-      std::move(provider), IsOriginSecure(url_), std::move(response_override_));
+  return WebServiceWorkerNetworkProviderImplForWorker::Create(
+      std::move(service_worker_provider_info_),
+      std::move(main_script_loader_factory_), std::move(controller_info_),
+      subresource_loader_factories_, IsOriginSecure(url_),
+      std::move(response_override_));
 }
 
 void EmbeddedSharedWorkerStub::WaitForServiceWorkerControllerInfo(
     blink::WebServiceWorkerNetworkProvider* web_network_provider,
     base::OnceClosure callback) {
   ServiceWorkerProviderContext* context =
-      ServiceWorkerNetworkProvider::FromWebServiceWorkerNetworkProvider(
+      static_cast<WebServiceWorkerNetworkProviderImplForWorker*>(
           web_network_provider)
           ->context();
   context->PingContainerHost(std::move(callback));
@@ -277,8 +274,8 @@
 EmbeddedSharedWorkerStub::CreateWorkerFetchContext(
     blink::WebServiceWorkerNetworkProvider* web_network_provider) {
   DCHECK(web_network_provider);
-  ServiceWorkerNetworkProvider* network_provider =
-      ServiceWorkerNetworkProvider::FromWebServiceWorkerNetworkProvider(
+  WebServiceWorkerNetworkProviderImplForWorker* network_provider =
+      static_cast<WebServiceWorkerNetworkProviderImplForWorker*>(
           web_network_provider);
 
   // Make the factory used for service worker network fallback (that should
@@ -288,7 +285,7 @@
 
   scoped_refptr<WebWorkerFetchContextImpl> worker_fetch_context =
       WebWorkerFetchContextImpl::Create(
-          network_provider, std::move(renderer_preferences_),
+          network_provider->context(), std::move(renderer_preferences_),
           std::move(preference_watcher_request_),
           subresource_loader_factories_->Clone(), std::move(fallback_factory));
 
diff --git a/content/renderer/shared_worker/web_service_worker_network_provider_impl_for_worker.cc b/content/renderer/shared_worker/web_service_worker_network_provider_impl_for_worker.cc
index 91863a1b..9d183e1 100644
--- a/content/renderer/shared_worker/web_service_worker_network_provider_impl_for_worker.cc
+++ b/content/renderer/shared_worker/web_service_worker_network_provider_impl_for_worker.cc
@@ -4,6 +4,8 @@
 
 #include "content/renderer/shared_worker/web_service_worker_network_provider_impl_for_worker.h"
 
+#include <utility>
+
 #include "base/feature_list.h"
 #include "content/public/common/origin_util.h"
 #include "content/renderer/loader/navigation_response_override_parameters.h"
@@ -17,22 +19,76 @@
 
 namespace content {
 
-WebServiceWorkerNetworkProviderImplForWorker::
-    WebServiceWorkerNetworkProviderImplForWorker(
-        std::unique_ptr<ServiceWorkerNetworkProvider> provider,
-        bool is_secure_context,
-        std::unique_ptr<NavigationResponseOverrideParameters> response_override)
-    : WebServiceWorkerNetworkProviderBaseImpl(std::move(provider)),
-      is_secure_context_(is_secure_context),
-      response_override_(std::move(response_override)) {}
+std::unique_ptr<WebServiceWorkerNetworkProviderImplForWorker>
+WebServiceWorkerNetworkProviderImplForWorker::Create(
+    blink::mojom::ServiceWorkerProviderInfoForSharedWorkerPtr info,
+    network::mojom::URLLoaderFactoryAssociatedPtrInfo
+        script_loader_factory_info,
+    blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
+    scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory,
+    bool is_secure_context,
+    std::unique_ptr<NavigationResponseOverrideParameters> response_override) {
+  auto provider =
+      base::WrapUnique(new WebServiceWorkerNetworkProviderImplForWorker(
+          is_secure_context, std::move(response_override)));
+  if (blink::ServiceWorkerUtils::IsServicificationEnabled()) {
+    DCHECK(info);
+    provider->context_ = base::MakeRefCounted<ServiceWorkerProviderContext>(
+        info->provider_id,
+        blink::mojom::ServiceWorkerProviderType::kForSharedWorker,
+        std::move(info->client_request), std::move(info->host_ptr_info),
+        std::move(controller_info), std::move(fallback_loader_factory));
+    if (script_loader_factory_info.is_valid()) {
+      provider->script_loader_factory_.Bind(
+          std::move(script_loader_factory_info));
+    }
+  } else {
+    DCHECK(!info);
+    int provider_id = GetNextServiceWorkerProviderId();
+    auto host_info = blink::mojom::ServiceWorkerProviderHostInfo::New(
+        provider_id, MSG_ROUTING_NONE,
+        blink::mojom::ServiceWorkerProviderType::kForSharedWorker,
+        true /* is_parent_frame_secure */, nullptr /* host_request */,
+        nullptr /* client_ptr_info */);
+    blink::mojom::ServiceWorkerContainerAssociatedRequest client_request =
+        mojo::MakeRequest(&host_info->client_ptr_info);
+    blink::mojom::ServiceWorkerContainerHostAssociatedPtrInfo host_ptr_info;
+    host_info->host_request = mojo::MakeRequest(&host_ptr_info);
+
+    provider->context_ = base::MakeRefCounted<ServiceWorkerProviderContext>(
+        provider_id, blink::mojom::ServiceWorkerProviderType::kForSharedWorker,
+        std::move(client_request), std::move(host_ptr_info),
+        std::move(controller_info), std::move(fallback_loader_factory));
+    if (ChildThreadImpl::current()) {
+      ChildThreadImpl::current()->channel()->GetRemoteAssociatedInterface(
+          &provider->dispatcher_host_);
+      provider->dispatcher_host_->OnProviderCreated(std::move(host_info));
+    } else {
+      // current() may be null in tests. Silently drop messages sent over
+      // ServiceWorkerContainerHost since we couldn't set it up correctly due
+      // to this test limitation. This way we don't crash when the associated
+      // interface ptr is used.
+      //
+      // TODO(falken): Just give ServiceWorkerProviderContext a null interface
+      // ptr and make the callsites deal with it. They are supposed to anyway
+      // because OnNetworkProviderDestroyed() can reset the ptr to null at any
+      // time.
+      mojo::AssociateWithDisconnectedPipe(host_info->host_request.PassHandle());
+    }
+  }
+  return provider;
+}
 
 WebServiceWorkerNetworkProviderImplForWorker::
-    ~WebServiceWorkerNetworkProviderImplForWorker() = default;
+    ~WebServiceWorkerNetworkProviderImplForWorker() {
+  if (context())
+    context()->OnNetworkProviderDestroyed();
+}
 
 void WebServiceWorkerNetworkProviderImplForWorker::WillSendRequest(
     blink::WebURLRequest& request) {
   auto extra_data = std::make_unique<RequestExtraData>();
-  extra_data->set_service_worker_provider_id(provider()->provider_id());
+  extra_data->set_service_worker_provider_id(provider_id());
   extra_data->set_initiated_in_secure_context(is_secure_context_);
   if (response_override_) {
     DCHECK(base::FeatureList::IsEnabled(network::features::kNetworkService));
@@ -50,12 +106,26 @@
   // request and break the assumptions of the renderer.
   if (request.GetRequestContext() !=
           blink::mojom::RequestContextType::SHARED_WORKER &&
-      provider()->IsControlledByServiceWorker() ==
+      IsControlledByServiceWorker() ==
           blink::mojom::ControllerServiceWorkerMode::kNoController) {
     request.SetSkipServiceWorker(true);
   }
 }
 
+blink::mojom::ControllerServiceWorkerMode
+WebServiceWorkerNetworkProviderImplForWorker::IsControlledByServiceWorker() {
+  if (!context())
+    return blink::mojom::ControllerServiceWorkerMode::kNoController;
+  return context()->IsControlledByServiceWorker();
+}
+
+int64_t
+WebServiceWorkerNetworkProviderImplForWorker::ControllerServiceWorkerID() {
+  if (!context())
+    return blink::mojom::kInvalidServiceWorkerVersionId;
+  return context()->GetControllerVersionId();
+}
+
 std::unique_ptr<blink::WebURLLoader>
 WebServiceWorkerNetworkProviderImplForWorker::CreateURLLoader(
     const blink::WebURLRequest& request,
@@ -72,7 +142,7 @@
     return nullptr;
   }
   // If the request is for the main script, use the script_loader_factory.
-  if (provider()->script_loader_factory() &&
+  if (script_loader_factory_ &&
       request.GetRequestContext() ==
           blink::mojom::RequestContextType::SHARED_WORKER) {
     // TODO(crbug.com/796425): Temporarily wrap the raw
@@ -80,13 +150,12 @@
     return std::make_unique<WebURLLoaderImpl>(
         render_thread->resource_dispatcher(), std::move(task_runner_handle),
         base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
-            provider()->script_loader_factory()));
+            script_loader_factory_.get()));
   }
 
   // Otherwise, it's an importScript. Use the subresource loader factory if it
   // exists (we are controlled by a service worker).
-  if (!provider()->context() ||
-      !provider()->context()->GetSubresourceLoaderFactory()) {
+  if (!context() || !context()->GetSubresourceLoaderFactory()) {
     return nullptr;
   }
 
@@ -113,7 +182,20 @@
       RenderThreadImpl::current()->resource_dispatcher(),
       std::move(task_runner_handle),
       base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
-          provider()->context()->GetSubresourceLoaderFactory()));
+          context()->GetSubresourceLoaderFactory()));
 }
 
+int WebServiceWorkerNetworkProviderImplForWorker::provider_id() const {
+  if (!context_)
+    return kInvalidServiceWorkerProviderId;
+  return context_->provider_id();
+}
+
+WebServiceWorkerNetworkProviderImplForWorker::
+    WebServiceWorkerNetworkProviderImplForWorker(
+        bool is_secure_context,
+        std::unique_ptr<NavigationResponseOverrideParameters> response_override)
+    : is_secure_context_(is_secure_context),
+      response_override_(std::move(response_override)) {}
+
 }  // namespace content
diff --git a/content/renderer/shared_worker/web_service_worker_network_provider_impl_for_worker.h b/content/renderer/shared_worker/web_service_worker_network_provider_impl_for_worker.h
index 580b1777..1c31ae2 100644
--- a/content/renderer/shared_worker/web_service_worker_network_provider_impl_for_worker.h
+++ b/content/renderer/shared_worker/web_service_worker_network_provider_impl_for_worker.h
@@ -5,37 +5,77 @@
 #ifndef CONTENT_RENDERER_SHARED_WORKER_WEB_SERVICE_WORKER_NETWORK_PROVIDER_IMPL_FOR_WORKER_H_
 #define CONTENT_RENDERER_SHARED_WORKER_WEB_SERVICE_WORKER_NETWORK_PROVIDER_IMPL_FOR_WORKER_H_
 
-#include "content/renderer/service_worker/web_service_worker_network_provider_base_impl.h"
+#include <memory>
+
+#include "base/memory/ref_counted.h"
+#include "content/renderer/service_worker/service_worker_provider_context.h"
+#include "third_party/blink/public/mojom/service_worker/controller_service_worker.mojom.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_provider_type.mojom.h"
+#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h"
 
 namespace content {
 
-class ServiceWorkerNetworkProvider;
 struct NavigationResponseOverrideParameters;
 
-// This is the implementation of WebServiceWorkerNetworkProvider used for
-// shared workers and owned by Blink. All functions are called on the main
-// thread only.
+// The WebServiceWorkerNetworkProvider implementation used for shared
+// workers.
 class WebServiceWorkerNetworkProviderImplForWorker final
-    : public WebServiceWorkerNetworkProviderBaseImpl {
+    : public blink::WebServiceWorkerNetworkProvider {
  public:
+  // Creates a new instance. Some params might only be used in S13nServiceWorker
+  // or PlzSharedWorker.
+  // - |info|: provider info from the browser
+  // - |script_loader_factory_info|: the factory for loading the worker's
+  //   scripts
+  // - |controller_info|: info about controller service worker
+  // - |fallback_loader_factory|: the factory to use when a service worker falls
+  //   back to network (unlike the default factory of this renderer, it skips
+  //   AppCache)
+  // - |is_secure_context|: whether this context is secure
+  // - |response_override|: the main script response
+  static std::unique_ptr<WebServiceWorkerNetworkProviderImplForWorker> Create(
+      blink::mojom::ServiceWorkerProviderInfoForSharedWorkerPtr info,
+      network::mojom::URLLoaderFactoryAssociatedPtrInfo
+          script_loader_factory_info,
+      blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
+      scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory,
+      bool is_secure_context,
+      std::unique_ptr<NavigationResponseOverrideParameters> response_override);
+
   WebServiceWorkerNetworkProviderImplForWorker(
-      std::unique_ptr<ServiceWorkerNetworkProvider> provider,
       bool is_secure_context,
       std::unique_ptr<NavigationResponseOverrideParameters> response_override);
   ~WebServiceWorkerNetworkProviderImplForWorker() override;
 
+  // Implements WebServiceWorkerNetworkProvider.
   // Blink calls this method for each request starting with the main script,
   // we tag them with the provider id.
   void WillSendRequest(blink::WebURLRequest& request) override;
-
+  blink::mojom::ControllerServiceWorkerMode IsControlledByServiceWorker()
+      override;
+  int64_t ControllerServiceWorkerID() override;
   std::unique_ptr<blink::WebURLLoader> CreateURLLoader(
       const blink::WebURLRequest& request,
       std::unique_ptr<blink::scheduler::WebResourceLoadingTaskRunnerHandle>
           task_runner_handle) override;
 
+  int provider_id() const;
+  ServiceWorkerProviderContext* context() { return context_.get(); }
+
  private:
   const bool is_secure_context_;
   std::unique_ptr<NavigationResponseOverrideParameters> response_override_;
+
+  // |context_| is null if |this| is an invalid instance, in which case there is
+  // no connection to the browser process.
+  scoped_refptr<ServiceWorkerProviderContext> context_;
+
+  // Used in non-s13nsw.
+  blink::mojom::ServiceWorkerDispatcherHostAssociatedPtr dispatcher_host_;
+
+  // The URL loader factory for loading the worker's scripts.
+  network::mojom::URLLoaderFactoryAssociatedPtr script_loader_factory_;
 };
 
 }  // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 139a6d5..0d5edc2 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1595,6 +1595,7 @@
     "../browser/renderer_host/media/video_capture_controller_unittest.cc",
     "../browser/renderer_host/media/video_capture_manager_unittest.cc",
     "../browser/renderer_host/media/video_capture_unittest.cc",
+    "../browser/renderer_host/native_web_keyboard_event_mac_unittest.mm",
     "../browser/renderer_host/overscroll_controller_unittest.cc",
     "../browser/renderer_host/render_process_host_unittest.cc",
     "../browser/renderer_host/render_view_host_unittest.cc",
@@ -2094,7 +2095,10 @@
       "//third_party/ocmock",
       "//ui/accelerated_widget_mac",
     ]
-    libs = [ "IOSurface.framework" ]
+    libs = [
+      "Carbon.framework",
+      "IOSurface.framework",
+    ]
   }
   if (is_chromeos) {
     deps += [ "//chromeos/audio" ]
diff --git a/content/test/gpu/gpu_tests/pixel_expectations.py b/content/test/gpu/gpu_tests/pixel_expectations.py
index 49a1a0c9..8a61b577 100644
--- a/content/test/gpu/gpu_tests/pixel_expectations.py
+++ b/content/test/gpu/gpu_tests/pixel_expectations.py
@@ -49,16 +49,11 @@
     # TODO(vmiura) check / generate reference images for Android devices
     self.Fail('Pixel_SolidColorBackground', ['mac', 'android'], bug=624256)
 
-    # TODO(wangxianzhu): This is commented out temporarily because of the entry
-    # for crbug.com/836884.
-    # self.Fail('Pixel_CSSFilterEffects', ['mac', ('nvidia', 0xfe9)],
-    #     bug=690277)
+    self.Fail('Pixel_CSSFilterEffects', ['mac', ('nvidia', 0xfe9)], bug=690277)
 
     # Became flaky on 10.13.6. When it flakes, it flakes 3 times, so
     # mark failing, unfortunately.
-    # TODO(wangxianzhu): This is commented out temporarily because of the entry
-    # for crbug.com/836884.
-    # self.Fail('Pixel_CSSFilterEffects', ['highsierra', 'amd'], bug=872423)
+    self.Fail('Pixel_CSSFilterEffects', ['highsierra', 'amd'], bug=872423)
 
     # TODO(kbr): flakily timing out on this configuration.
     self.Flaky('*', ['linux', 'intel', 'debug'], bug=648369)
@@ -121,12 +116,6 @@
     self.Flaky('Pixel_BackgroundImage',
         ['android', ('qualcomm', 'Adreno (TM) 418')], bug=883500)
 
-    # TODO(wangxianzhu): Re-enable after and rebaselining
-    self.Fail('Pixel_CSSFilterEffects', bug=836884)
-    self.Fail('Pixel_CSSFilterEffects_NoOverlays', bug=836884)
-    self.Fail('Pixel_2DCanvasWebGL', bug=836884)
-    self.Fail('Pixel_CSS3DBlueBox', bug=836884)
-
     # We do not have software H.264 decoding on Android, so it can't survive a
     # context loss which results in hardware decoder loss.
     self.Skip('Pixel_Video_Context_Loss_MP4', ['android'], bug=580386)
@@ -145,6 +134,9 @@
     self.Fail('Pixel_Video_MP4_FourColors_Rot_270',
         ['mac', ('amd', 0x679e)], bug=911413)
 
+    # Fails on multiple Android devices.
+    self.Fail('Pixel_CSS3DBlueBox', ['android'], bug=927107)
+
     # Fail on Nexus 5, 5X, 6, 6P, 9 and Shield TV.
     self.Fail('Pixel_Video_MP4', ['android'], bug=925744)
     self.Fail('Pixel_Video_MP4_FourColors_Aspect_4x3', ['android'], bug=925744)
diff --git a/device/bluetooth/bluetooth_gatt_discoverer_winrt.cc b/device/bluetooth/bluetooth_gatt_discoverer_winrt.cc
index d40f1ce2..a53422c2d 100644
--- a/device/bluetooth/bluetooth_gatt_discoverer_winrt.cc
+++ b/device/bluetooth/bluetooth_gatt_discoverer_winrt.cc
@@ -18,6 +18,8 @@
 
 namespace {
 
+using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice;
+using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice3;
 using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
     GattCharacteristic;
 using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
@@ -25,6 +27,8 @@
 using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
     GattCommunicationStatus;
 using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
+    GattCommunicationStatus_AccessDenied;
+using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
     GattCommunicationStatus_Success;
 using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::GattDescriptor;
 using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
@@ -43,15 +47,14 @@
     IGattDeviceService3;
 using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::
     IGattDeviceServicesResult;
-using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice3;
-using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice;
-using ABI::Windows::Foundation::Collections::IVectorView;
 using ABI::Windows::Foundation::IAsyncOperation;
 using ABI::Windows::Foundation::IReference;
+using ABI::Windows::Foundation::Collections::IVectorView;
 using Microsoft::WRL::ComPtr;
 
 template <typename IGattResult>
-bool CheckCommunicationStatus(IGattResult* gatt_result) {
+bool CheckCommunicationStatus(IGattResult* gatt_result,
+                              bool allow_access_denied = false) {
   if (!gatt_result) {
     VLOG(2) << "Getting GATT Results failed.";
     return false;
@@ -66,14 +69,20 @@
   }
 
   if (status != GattCommunicationStatus_Success) {
-    VLOG(2) << "Unexpected GattCommunicationStatus: " << status;
+    if (status == GattCommunicationStatus_AccessDenied) {
+      VLOG(2) << "GATT access denied error";
+    } else {
+      VLOG(2) << "Unexpected GattCommunicationStatus: " << status;
+    }
     VLOG(2) << "GATT Error Code: "
             << static_cast<int>(
                    BluetoothRemoteGattServiceWinrt::GetGattErrorCode(
                        gatt_result));
   }
 
-  return status == GattCommunicationStatus_Success;
+  return status == GattCommunicationStatus_Success ||
+         (allow_access_denied &&
+          status == GattCommunicationStatus_AccessDenied);
 }
 
 template <typename T, typename I>
@@ -233,7 +242,9 @@
 void BluetoothGattDiscovererWinrt::OnGetCharacteristics(
     uint16_t service_attribute_handle,
     ComPtr<IGattCharacteristicsResult> characteristics_result) {
-  if (!CheckCommunicationStatus(characteristics_result.Get())) {
+  // A few GATT services like HID over GATT (short UUID 0x1812) are protected
+  // by the OS, leading to an access denied error.
+  if (!CheckCommunicationStatus(characteristics_result.Get(), true)) {
     std::move(callback_).Run(false);
     return;
   }
diff --git a/device/usb/mojo/device_manager_impl.cc b/device/usb/mojo/device_manager_impl.cc
index 9018ea2..0ddbe4b 100644
--- a/device/usb/mojo/device_manager_impl.cc
+++ b/device/usb/mojo/device_manager_impl.cc
@@ -18,6 +18,8 @@
 #include "device/usb/mojo/type_converters.h"
 #include "device/usb/public/cpp/usb_utils.h"
 #include "device/usb/public/mojom/device.mojom.h"
+#include "device/usb/public/mojom/device_enumeration_options.mojom.h"
+#include "device/usb/public/mojom/device_manager_client.mojom.h"
 #include "device/usb/usb_device.h"
 #include "device/usb/usb_service.h"
 
diff --git a/device/usb/mojo/device_manager_impl_unittest.cc b/device/usb/mojo/device_manager_impl_unittest.cc
index 5dafdc39..707b610 100644
--- a/device/usb/mojo/device_manager_impl_unittest.cc
+++ b/device/usb/mojo/device_manager_impl_unittest.cc
@@ -22,6 +22,8 @@
 #include "device/usb/mock_usb_device_handle.h"
 #include "device/usb/mock_usb_service.h"
 #include "device/usb/mojo/device_impl.h"
+#include "device/usb/public/mojom/device_enumeration_options.mojom.h"
+#include "device/usb/public/mojom/device_manager_client.mojom.h"
 #include "mojo/public/cpp/bindings/associated_binding.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/device/usb/public/cpp/fake_usb_device_manager.cc b/device/usb/public/cpp/fake_usb_device_manager.cc
index ee9bc1a..b88b504 100644
--- a/device/usb/public/cpp/fake_usb_device_manager.cc
+++ b/device/usb/public/cpp/fake_usb_device_manager.cc
@@ -12,6 +12,8 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "device/usb/public/cpp/fake_usb_device.h"
 #include "device/usb/public/cpp/usb_utils.h"
+#include "device/usb/public/mojom/device_enumeration_options.mojom.h"
+#include "device/usb/public/mojom/device_manager_client.mojom.h"
 
 namespace device {
 
diff --git a/device/usb/public/cpp/usb_utils.cc b/device/usb/public/cpp/usb_utils.cc
index 5b1f1839..4877c53 100644
--- a/device/usb/public/cpp/usb_utils.cc
+++ b/device/usb/public/cpp/usb_utils.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "device/usb/public/mojom/device_enumeration_options.mojom.h"
 #include "device/usb/usb_device.h"
 
 namespace device {
diff --git a/device/usb/public/cpp/usb_utils_unittest.cc b/device/usb/public/cpp/usb_utils_unittest.cc
index 426a097..9fc5ada 100644
--- a/device/usb/public/cpp/usb_utils_unittest.cc
+++ b/device/usb/public/cpp/usb_utils_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "device/usb/mock_usb_device.h"
 #include "device/usb/public/cpp/usb_utils.h"
+#include "device/usb/public/mojom/device_enumeration_options.mojom.h"
 #include "device/usb/usb_descriptors.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/docs/speed/microbenchmark_regressions.md b/docs/speed/microbenchmark_regressions.md
index 26ba377..f2f7f89e 100644
--- a/docs/speed/microbenchmark_regressions.md
+++ b/docs/speed/microbenchmark_regressions.md
@@ -25,6 +25,9 @@
 to dig into this is to [examine the compiled functions](../disassemble_code.md)
 before and after the no-op change, to see if inlining has changed.
 
+If you have a function that you believe should always be inlined in production
+builds, please contact gbiv@chromium.org for advice.
+
 ### Toolchain rolls
 
 Our toolchain team regularly rolls in new versions of clang, the compiler for
diff --git a/extensions/browser/api/device_permissions_prompt.cc b/extensions/browser/api/device_permissions_prompt.cc
index ec82c1a2..ff1da54 100644
--- a/extensions/browser/api/device_permissions_prompt.cc
+++ b/extensions/browser/api/device_permissions_prompt.cc
@@ -17,6 +17,10 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/common/service_manager_connection.h"
 #include "device/usb/public/cpp/usb_utils.h"
+#include "device/usb/public/mojom/device_enumeration_options.mojom.h"
+#include "device/usb/usb_device.h"
+#include "device/usb/usb_ids.h"
+#include "device/usb/usb_service.h"
 #include "extensions/browser/api/device_permissions_manager.h"
 #include "extensions/browser/api/usb/usb_device_manager.h"
 #include "extensions/common/extension.h"
diff --git a/google_apis/gcm/engine/connection_factory_impl.cc b/google_apis/gcm/engine/connection_factory_impl.cc
index ac5e02a..8dc63f8 100644
--- a/google_apis/gcm/engine/connection_factory_impl.cc
+++ b/google_apis/gcm/engine/connection_factory_impl.cc
@@ -25,6 +25,7 @@
 #include "net/socket/client_socket_pool_manager.h"
 #include "net/ssl/ssl_config_service.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/mojom/tcp_socket.mojom.h"
 
 namespace gcm {
 
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_model.h b/ios/chrome/browser/ui/fullscreen/fullscreen_model.h
index 4cd9cb8b..2861938 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_model.h
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_model.h
@@ -147,6 +147,10 @@
   void SetResizesScrollView(bool resizes_scroll_view);
   bool ResizesScrollView() const;
 
+  // Setter for the safe area insets for the current WebState's view.
+  void SetWebViewSafeAreaInsets(UIEdgeInsets safe_area_insets);
+  UIEdgeInsets GetWebViewSafeAreaInsets() const;
+
  private:
   // Returns how a scroll to the current |y_content_offset_| from |from_offset|
   // should be handled.
@@ -220,6 +224,8 @@
   bool ignoring_current_scroll_ = false;
   // Whether the scroll view is resized for fullscreen events.
   bool resizes_scroll_view_ = false;
+  // The WebState view's safe area insets.
+  UIEdgeInsets safe_area_insets_ = UIEdgeInsetsZero;
   // The number of FullscreenModelObserver callbacks currently being executed.
   size_t observer_callback_count_ = 0;
 
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm
index daa1aadb..b639cb6 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm
@@ -228,6 +228,17 @@
   return resizes_scroll_view_;
 }
 
+void FullscreenModel::SetWebViewSafeAreaInsets(UIEdgeInsets safe_area_insets) {
+  if (UIEdgeInsetsEqualToEdgeInsets(safe_area_insets_, safe_area_insets))
+    return;
+  safe_area_insets_ = safe_area_insets;
+  UpdateDisabledCounterForContentHeight();
+}
+
+UIEdgeInsets FullscreenModel::GetWebViewSafeAreaInsets() const {
+  return safe_area_insets_;
+}
+
 FullscreenModel::ScrollAction FullscreenModel::ActionForScrollFromOffset(
     CGFloat from_offset) const {
   // Update the base offset but don't recalculate progress if:
@@ -279,6 +290,14 @@
     // resized to account for the viewport insets after the page has been
     // rendered, so account for the maximum toolbar insets in the threshold.
     disabling_threshold += expanded_toolbar_height_ + bottom_toolbar_height_;
+  } else {
+    // After reloads, pages whose viewports fit the screen are sometimes resized
+    // to account for the safe area insets.  Adding these to the threshold helps
+    // prevent fullscreen from beeing re-enabled in this case.
+    // TODO(crbug.com/924807): This logic can potentially disable fullscreen for
+    // short pages in which this bug does not occur.  It should be removed once
+    // the page can be reloaded without resizing.
+    disabling_threshold += safe_area_insets_.top + safe_area_insets_.bottom;
   }
 
   // Don't disable fullscreen if both heights have not been received.
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_resizer.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_resizer.mm
index 41a8611..e083962 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_resizer.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_web_view_resizer.mm
@@ -93,6 +93,7 @@
     return;
 
   [self updateForInsets:self.model->GetToolbarInsetsAtProgress(progress)];
+  self.model->SetWebViewSafeAreaInsets(self.webState->GetView().safeAreaInsets);
 }
 
 // Updates the WebState view, resizing it such as |insets| is the insets between
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 6c607f4..7cfab24 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -386,10 +386,6 @@
 const base::Feature kMediaDrmPersistentLicense{
     "MediaDrmPersistentLicense", base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Enables the Android MediaRouter implementation using CAF (Cast v3).
-const base::Feature kCafMediaRouterImpl{"CafMediaRouterImpl",
-                                        base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Enables the Android Image Reader path for Video decoding(for AVDA and MCVD)
 const base::Feature kAImageReaderVideoOutput{"AImageReaderVideoOutput",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 4db370e..bd8b8cd 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -147,7 +147,6 @@
 MEDIA_EXPORT extern const base::Feature kVideoFullscreenOrientationLock;
 MEDIA_EXPORT extern const base::Feature kVideoRotateToFullscreen;
 MEDIA_EXPORT extern const base::Feature kMediaDrmPersistentLicense;
-MEDIA_EXPORT extern const base::Feature kCafMediaRouterImpl;
 MEDIA_EXPORT extern const base::Feature kAImageReaderVideoOutput;
 #endif  // defined(OS_ANDROID)
 
diff --git a/media/capture/BUILD.gn b/media/capture/BUILD.gn
index fd588806..2ff140b 100644
--- a/media/capture/BUILD.gn
+++ b/media/capture/BUILD.gn
@@ -260,6 +260,10 @@
       "video/chromeos/display_rotation_observer.h",
       "video/chromeos/pixel_format_utils.cc",
       "video/chromeos/pixel_format_utils.h",
+      "video/chromeos/request_builder.cc",
+      "video/chromeos/request_builder.h",
+      "video/chromeos/request_manager.cc",
+      "video/chromeos/request_manager.h",
       "video/chromeos/stream_buffer_manager.cc",
       "video/chromeos/stream_buffer_manager.h",
       "video/chromeos/video_capture_device_chromeos_halv3.cc",
@@ -391,7 +395,7 @@
       "video/chromeos/mock_camera_module.h",
       "video/chromeos/mock_video_capture_client.cc",
       "video/chromeos/mock_video_capture_client.h",
-      "video/chromeos/stream_buffer_manager_unittest.cc",
+      "video/chromeos/request_manager_unittest.cc",
     ]
     deps += [
       "//build/config/linux/libdrm",
diff --git a/media/capture/video/chromeos/camera_3a_controller.h b/media/capture/video/chromeos/camera_3a_controller.h
index 8d3226a..681638aa 100644
--- a/media/capture/video/chromeos/camera_3a_controller.h
+++ b/media/capture/video/chromeos/camera_3a_controller.h
@@ -10,7 +10,7 @@
 #include "base/cancelable_callback.h"
 #include "media/base/media_export.h"
 #include "media/capture/video/chromeos/mojo/camera3.mojom.h"
-#include "media/capture/video/chromeos/stream_buffer_manager.h"
+#include "media/capture/video/chromeos/request_manager.h"
 
 namespace media {
 
diff --git a/media/capture/video/chromeos/camera_3a_controller_unittest.cc b/media/capture/video/chromeos/camera_3a_controller_unittest.cc
index 518ef74..8a39e65 100644
--- a/media/capture/video/chromeos/camera_3a_controller_unittest.cc
+++ b/media/capture/video/chromeos/camera_3a_controller_unittest.cc
@@ -8,7 +8,7 @@
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
 #include "media/capture/video/chromeos/camera_metadata_utils.h"
-#include "media/capture/video/chromeos/stream_buffer_manager.h"
+#include "media/capture/video/chromeos/request_builder.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/media/capture/video/chromeos/camera_device_context.h b/media/capture/video/chromeos/camera_device_context.h
index 83e34da..a4fb232 100644
--- a/media/capture/video/chromeos/camera_device_context.h
+++ b/media/capture/video/chromeos/camera_device_context.h
@@ -122,7 +122,7 @@
   int GetCameraFrameOrientation();
 
  private:
-  friend class StreamBufferManagerTest;
+  friend class RequestManagerTest;
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/media/capture/video/chromeos/camera_device_delegate.cc b/media/capture/video/chromeos/camera_device_delegate.cc
index 9ef046e..ceac453 100644
--- a/media/capture/video/chromeos/camera_device_delegate.cc
+++ b/media/capture/video/chromeos/camera_device_delegate.cc
@@ -22,16 +22,20 @@
 #include "media/capture/video/chromeos/camera_device_context.h"
 #include "media/capture/video/chromeos/camera_hal_delegate.h"
 #include "media/capture/video/chromeos/camera_metadata_utils.h"
-#include "media/capture/video/chromeos/stream_buffer_manager.h"
+#include "media/capture/video/chromeos/request_manager.h"
 
 namespace media {
 
 namespace {
 
-void GetMaxBlobStreamResolution(
+// The result of max_width and max_height could be zero if the stream
+// is not in the pre-defined configuration.
+void GetMaxStreamResolution(
     const cros::mojom::CameraMetadataPtr& static_metadata,
-    int32_t* max_blob_width,
-    int32_t* max_blob_height) {
+    cros::mojom::Camera3StreamType stream_type,
+    cros::mojom::HalPixelFormat stream_format,
+    int32_t* max_width,
+    int32_t* max_height) {
   const cros::mojom::CameraMetadataEntryPtr* stream_configurations =
       GetMetadataEntry(static_metadata,
                        cros::mojom::CameraMetadataTag::
@@ -46,8 +50,8 @@
   const size_t kStreamConfigurationSize = 4;
   int32_t* iter =
       reinterpret_cast<int32_t*>((*stream_configurations)->data.data());
-  *max_blob_width = 0;
-  *max_blob_height = 0;
+  *max_width = 0;
+  *max_height = 0;
   for (size_t i = 0; i < (*stream_configurations)->count;
        i += kStreamConfigurationSize) {
     auto format =
@@ -58,17 +62,17 @@
         static_cast<cros::mojom::Camera3StreamType>(iter[kStreamTypeOffset]);
     iter += kStreamConfigurationSize;
 
-    if (type != cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT ||
-        format != cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB) {
+    if (type != stream_type || format != stream_format) {
       continue;
     }
-    if (width > *max_blob_width && height > *max_blob_height) {
-      *max_blob_width = width;
-      *max_blob_height = height;
+
+    // TODO(wtlee): Once we have resolution settings mechanism, we could set
+    // stream resolution based on user's settings.
+    if (width > *max_width && height > *max_height) {
+      *max_width = width;
+      *max_height = height;
     }
   }
-  DCHECK_GT(*max_blob_width, 0);
-  DCHECK_GT(*max_blob_height, 0);
 }
 
 // VideoCaptureDevice::TakePhotoCallback is given by the application and is used
@@ -85,6 +89,17 @@
 
 }  // namespace
 
+StreamType StreamIdToStreamType(uint64_t stream_id) {
+  switch (stream_id) {
+    case 0:
+      return StreamType::kPreview;
+    case 1:
+      return StreamType::kStillCapture;
+    default:
+      return StreamType::kUnknown;
+  }
+}  // namespace media
+
 std::string StreamTypeToString(StreamType stream_type) {
   switch (stream_type) {
     case StreamType::kPreview:
@@ -112,21 +127,6 @@
       base::WeakPtr<CameraDeviceDelegate> camera_device_delegate)
       : camera_device_delegate_(std::move(camera_device_delegate)) {}
 
-  void RegisterBuffer(uint64_t buffer_id,
-                      cros::mojom::Camera3DeviceOps::BufferType type,
-                      uint32_t drm_format,
-                      cros::mojom::HalPixelFormat hal_pixel_format,
-                      uint32_t width,
-                      uint32_t height,
-                      std::vector<StreamCaptureInterface::Plane> planes,
-                      base::OnceCallback<void(int32_t)> callback) final {
-    if (camera_device_delegate_) {
-      camera_device_delegate_->RegisterBuffer(
-          buffer_id, type, drm_format, hal_pixel_format, width, height,
-          std::move(planes), std::move(callback));
-    }
-  }
-
   void ProcessCaptureRequest(cros::mojom::Camera3CaptureRequestPtr request,
                              base::OnceCallback<void(int32_t)> callback) final {
     if (camera_device_delegate_) {
@@ -179,10 +179,10 @@
   if (!device_context_ ||
       device_context_->GetState() == CameraDeviceContext::State::kStopped ||
       (device_context_->GetState() == CameraDeviceContext::State::kError &&
-       !stream_buffer_manager_)) {
+       !request_manager_)) {
     // In case of Mojo connection error the device may be stopped before
     // StopAndDeAllocate is called; in case of device open failure, the state
-    // is set to kError and |stream_buffer_manager_| is uninitialized.
+    // is set to kError and |request_manager_| is uninitialized.
     std::move(device_close_callback).Run();
     return;
   }
@@ -197,7 +197,7 @@
     // The device delegate is in the process of opening the camera device.
     return;
   }
-  stream_buffer_manager_->StopPreview(base::NullCallback());
+  request_manager_->StopPreview(base::NullCallback());
   device_ops_->Close(
       base::BindOnce(&CameraDeviceDelegate::OnClosed, GetWeakPtr()));
 }
@@ -233,8 +233,10 @@
   }
 
   int32_t max_blob_width = 0, max_blob_height = 0;
-  GetMaxBlobStreamResolution(static_metadata_, &max_blob_width,
-                             &max_blob_height);
+  GetMaxStreamResolution(static_metadata_,
+                         cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT,
+                         cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB,
+                         &max_blob_width, &max_blob_height);
   photo_state->width->current = max_blob_width;
   photo_state->width->min = max_blob_width;
   photo_state->width->max = max_blob_width;
@@ -263,8 +265,8 @@
     return;
   }
 
-  if (stream_buffer_manager_->GetStreamNumber() < kMaxConfiguredStreams) {
-    stream_buffer_manager_->StopPreview(
+  if (request_manager_->GetNumberOfStreams() < kMaxConfiguredStreams) {
+    request_manager_->StopPreview(
         base::BindOnce(&CameraDeviceDelegate::OnFlushed, GetWeakPtr()));
     set_photo_option_callback_ = std::move(callback);
   } else {
@@ -290,7 +292,7 @@
       base::BindOnce(&CameraDeviceDelegate::ConstructDefaultRequestSettings,
                      GetWeakPtr(), StreamType::kStillCapture);
 
-  if (stream_buffer_manager_->GetStreamNumber() >= kMaxConfiguredStreams) {
+  if (request_manager_->GetNumberOfStreams() >= kMaxConfiguredStreams) {
     camera_3a_controller_->Stabilize3AForStillCapture(
         std::move(construct_request_cb));
     return;
@@ -317,8 +319,8 @@
     OnClosed(0);
   } else {
     // The Mojo channel terminated unexpectedly.
-    if (stream_buffer_manager_) {
-      stream_buffer_manager_->StopPreview(base::NullCallback());
+    if (request_manager_) {
+      request_manager_->StopPreview(base::NullCallback());
     }
     device_context_->SetState(CameraDeviceContext::State::kStopped);
     device_context_->SetErrorState(
@@ -363,7 +365,7 @@
 
   device_ops_.reset();
   camera_3a_controller_.reset();
-  stream_buffer_manager_.reset();
+  request_manager_.reset();
 }
 
 void CameraDeviceDelegate::OnGotCameraInfo(
@@ -445,13 +447,13 @@
   cros::mojom::Camera3CallbackOpsPtr callback_ops_ptr;
   cros::mojom::Camera3CallbackOpsRequest callback_ops_request =
       mojo::MakeRequest(&callback_ops_ptr);
-  stream_buffer_manager_ = std::make_unique<StreamBufferManager>(
+  request_manager_ = std::make_unique<RequestManager>(
       std::move(callback_ops_request),
       std::make_unique<StreamCaptureInterfaceImpl>(GetWeakPtr()),
       device_context_, std::make_unique<CameraBufferFactory>(),
       base::BindRepeating(&RotateAndBlobify), ipc_task_runner_);
   camera_3a_controller_ = std::make_unique<Camera3AController>(
-      static_metadata_, stream_buffer_manager_.get(), ipc_task_runner_);
+      static_metadata_, request_manager_.get(), ipc_task_runner_);
   device_ops_->Initialize(
       std::move(callback_ops_ptr),
       base::BindOnce(&CameraDeviceDelegate::OnInitialized, GetWeakPtr()));
@@ -509,8 +511,10 @@
   // should be configured dynamically per the photo options.
   if (require_photo) {
     int32_t max_blob_width = 0, max_blob_height = 0;
-    GetMaxBlobStreamResolution(static_metadata_, &max_blob_width,
-                               &max_blob_height);
+    GetMaxStreamResolution(
+        static_metadata_, cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT,
+        cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB, &max_blob_width,
+        &max_blob_height);
 
     cros::mojom::Camera3StreamPtr still_capture_stream =
         cros::mojom::Camera3Stream::New();
@@ -565,7 +569,7 @@
     return;
   }
 
-  stream_buffer_manager_->SetUpStreamsAndBuffers(
+  request_manager_->SetUpStreamsAndBuffers(
       chrome_capture_params_.requested_format, static_metadata_,
       std::move(updated_config->streams));
 
@@ -615,7 +619,7 @@
   }
   device_context_->SetState(CameraDeviceContext::State::kCapturing);
   camera_3a_controller_->SetAutoFocusModeForStillCapture();
-  stream_buffer_manager_->StartPreview(std::move(settings));
+  request_manager_->StartPreview(std::move(settings));
 
   if (!take_photo_callbacks_.empty()) {
     TakePhotoImpl();
@@ -631,7 +635,7 @@
   DCHECK(ipc_task_runner_->BelongsToCurrentThread());
 
   while (!take_photo_callbacks_.empty()) {
-    stream_buffer_manager_->TakePhoto(
+    request_manager_->TakePhoto(
         settings.Clone(),
         base::BindOnce(
             &TakePhotoCallbackBundle, std::move(take_photo_callbacks_.front()),
@@ -641,36 +645,6 @@
   }
 }
 
-void CameraDeviceDelegate::RegisterBuffer(
-    uint64_t buffer_id,
-    cros::mojom::Camera3DeviceOps::BufferType type,
-    uint32_t drm_format,
-    cros::mojom::HalPixelFormat hal_pixel_format,
-    uint32_t width,
-    uint32_t height,
-    std::vector<StreamCaptureInterface::Plane> planes,
-    base::OnceCallback<void(int32_t)> callback) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-
-  if (device_context_->GetState() != CameraDeviceContext::State::kCapturing) {
-    DCHECK_EQ(device_context_->GetState(),
-              CameraDeviceContext::State::kStopping);
-    return;
-  }
-  size_t num_planes = planes.size();
-  std::vector<mojo::ScopedHandle> fds(num_planes);
-  std::vector<uint32_t> strides(num_planes);
-  std::vector<uint32_t> offsets(num_planes);
-  for (size_t i = 0; i < num_planes; ++i) {
-    fds[i] = std::move(planes[i].fd);
-    strides[i] = planes[i].stride;
-    offsets[i] = planes[i].offset;
-  }
-  device_ops_->RegisterBuffer(
-      buffer_id, type, std::move(fds), drm_format, hal_pixel_format, width,
-      height, std::move(strides), std::move(offsets), std::move(callback));
-}
-
 void CameraDeviceDelegate::ProcessCaptureRequest(
     cros::mojom::Camera3CaptureRequestPtr request,
     base::OnceCallback<void(int32_t)> callback) {
diff --git a/media/capture/video/chromeos/camera_device_delegate.h b/media/capture/video/chromeos/camera_device_delegate.h
index 49a0ae0..a94abbc 100644
--- a/media/capture/video/chromeos/camera_device_delegate.h
+++ b/media/capture/video/chromeos/camera_device_delegate.h
@@ -20,7 +20,7 @@
 class Camera3AController;
 class CameraDeviceContext;
 class CameraHalDelegate;
-class StreamBufferManager;
+class RequestManager;
 
 enum class StreamType : uint64_t {
   kPreview = 0,
@@ -28,6 +28,8 @@
   kUnknown,
 };
 
+StreamType StreamIdToStreamType(uint64_t stream_id);
+
 std::string StreamTypeToString(StreamType stream_type);
 
 std::ostream& operator<<(std::ostream& os, StreamType stream_type);
@@ -46,16 +48,6 @@
 
   virtual ~StreamCaptureInterface() {}
 
-  // Registers a buffer to the camera HAL.
-  virtual void RegisterBuffer(uint64_t buffer_id,
-                              cros::mojom::Camera3DeviceOps::BufferType type,
-                              uint32_t drm_format,
-                              cros::mojom::HalPixelFormat hal_pixel_format,
-                              uint32_t width,
-                              uint32_t height,
-                              std::vector<Plane> planes,
-                              base::OnceCallback<void(int32_t)> callback) = 0;
-
   // Sends a capture request to the camera HAL.
   virtual void ProcessCaptureRequest(
       cros::mojom::Camera3CaptureRequestPtr request,
@@ -153,14 +145,6 @@
 
   // StreamCaptureInterface implementations.  These methods are called by
   // |stream_buffer_manager_| on |ipc_task_runner_|.
-  void RegisterBuffer(uint64_t buffer_id,
-                      cros::mojom::Camera3DeviceOps::BufferType type,
-                      uint32_t drm_format,
-                      cros::mojom::HalPixelFormat hal_pixel_format,
-                      uint32_t width,
-                      uint32_t height,
-                      std::vector<StreamCaptureInterface::Plane> planes,
-                      base::OnceCallback<void(int32_t)> callback);
   void ProcessCaptureRequest(cros::mojom::Camera3CaptureRequestPtr request,
                              base::OnceCallback<void(int32_t)> callback);
   void Flush(base::OnceCallback<void(int32_t)> callback);
@@ -180,7 +164,7 @@
 
   std::queue<VideoCaptureDevice::TakePhotoCallback> take_photo_callbacks_;
 
-  std::unique_ptr<StreamBufferManager> stream_buffer_manager_;
+  std::unique_ptr<RequestManager> request_manager_;
 
   std::unique_ptr<Camera3AController> camera_3a_controller_;
 
diff --git a/media/capture/video/chromeos/camera_device_delegate_unittest.cc b/media/capture/video/chromeos/camera_device_delegate_unittest.cc
index 2d565c2..41e6c60 100644
--- a/media/capture/video/chromeos/camera_device_delegate_unittest.cc
+++ b/media/capture/video/chromeos/camera_device_delegate_unittest.cc
@@ -88,21 +88,7 @@
                       uint32_t height,
                       const std::vector<uint32_t>& strides,
                       const std::vector<uint32_t>& offsets,
-                      RegisterBufferCallback callback) override {
-    DoRegisterBuffer(buffer_id, type, fds, drm_format, hal_pixel_format, width,
-                     height, strides, offsets, callback);
-  }
-  MOCK_METHOD10(DoRegisterBuffer,
-                void(uint64_t buffer_id,
-                     cros::mojom::Camera3DeviceOps::BufferType type,
-                     std::vector<mojo::ScopedHandle>& fds,
-                     uint32_t drm_format,
-                     cros::mojom::HalPixelFormat hal_pixel_format,
-                     uint32_t width,
-                     uint32_t height,
-                     const std::vector<uint32_t>& strides,
-                     const std::vector<uint32_t>& offsets,
-                     RegisterBufferCallback& callback));
+                      RegisterBufferCallback callback) override {}
 
   void Close(CloseCallback callback) override { DoClose(callback); }
   MOCK_METHOD1(DoClose, void(CloseCallback& callback));
@@ -265,19 +251,6 @@
     std::move(callback).Run(std::move(fake_settings));
   }
 
-  void RegisterBuffer(uint64_t buffer_id,
-                      cros::mojom::Camera3DeviceOps::BufferType type,
-                      std::vector<mojo::ScopedHandle>& fds,
-                      uint32_t drm_format,
-                      cros::mojom::HalPixelFormat hal_pixel_format,
-                      uint32_t width,
-                      uint32_t height,
-                      const std::vector<uint32_t>& strides,
-                      const std::vector<uint32_t>& offsets,
-                      base::OnceCallback<void(int32_t)>& callback) {
-    std::move(callback).Run(0);
-  }
-
   void ProcessCaptureRequest(cros::mojom::Camera3CaptureRequestPtr& request,
                              base::OnceCallback<void(int32_t)>& callback) {
     std::move(callback).Run(0);
@@ -378,12 +351,6 @@
   }
 
   void SetUpExpectationForCaptureLoop() {
-    EXPECT_CALL(mock_camera_device_,
-                DoRegisterBuffer(_, _, _, _, _, _, _, _, _, _))
-        .Times(AtLeast(1))
-        .WillOnce(Invoke(this, &CameraDeviceDelegateTest::RegisterBuffer))
-        .WillRepeatedly(
-            Invoke(this, &CameraDeviceDelegateTest::RegisterBuffer));
     EXPECT_CALL(mock_camera_device_, DoProcessCaptureRequest(_, _))
         .Times(AtLeast(1))
         .WillOnce(
diff --git a/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc b/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc
index 151d58ee..c85897d 100644
--- a/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc
+++ b/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc
@@ -21,6 +21,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/trace_event/trace_event.h"
+#include "media/capture/video/chromeos/mojo/camera_common.mojom.h"
 #include "mojo/public/cpp/platform/named_platform_channel.h"
 #include "mojo/public/cpp/platform/platform_channel.h"
 #include "mojo/public/cpp/platform/socket_utils_posix.h"
diff --git a/media/capture/video/chromeos/camera_hal_dispatcher_impl_unittest.cc b/media/capture/video/chromeos/camera_hal_dispatcher_impl_unittest.cc
index e8eb1a5f..f6feeec8 100644
--- a/media/capture/video/chromeos/camera_hal_dispatcher_impl_unittest.cc
+++ b/media/capture/video/chromeos/camera_hal_dispatcher_impl_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/test/scoped_task_environment.h"
+#include "media/capture/video/chromeos/mojo/camera_common.mojom.h"
 #include "media/capture/video/chromeos/mojo/cros_camera_service.mojom.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "testing/gmock/include/gmock/gmock.h"
diff --git a/media/capture/video/chromeos/mojo/camera3.mojom b/media/capture/video/chromeos/mojo/camera3.mojom
index 2cba342..1ce2e11 100644
--- a/media/capture/video/chromeos/mojo/camera3.mojom
+++ b/media/capture/video/chromeos/mojo/camera3.mojom
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// Next min version: 2
+// Next min version: 3
 
 module cros.mojom;
 
@@ -73,12 +73,25 @@
   CAMERA3_BUFFER_STATUS_ERROR = 1,
 };
 
+// Structure that contains needed information about a camera buffer.
+struct CameraBufferHandle {
+  uint64 buffer_id;
+  array<handle> fds;
+  uint32 drm_format;
+  HalPixelFormat hal_pixel_format;
+  uint32 width;
+  uint32 height;
+  array<uint32> strides;
+  array<uint32> offsets;
+};
+
 struct Camera3StreamBuffer {
   uint64 stream_id;
   uint64 buffer_id;
   Camera3BufferStatus status;
   handle? acquire_fence;
   handle? release_fence;
+  [MinVersion=2] CameraBufferHandle? buffer_handle;
 };
 
 enum Camera3MsgType {
@@ -158,9 +171,8 @@
 };
 
 // Camera3DeviceOps is mostly a translation of the camera3_device_ops_t API from
-// Android camera HAL v3, with the additional RegisterBuffer() function to pass
-// buffer handles across processes.  This is the interface to interact with a
-// camera device in the camera HAL.
+// Android camera HAL v3.  This is the interface to interact with a camera
+// device in the camera HAL.
 //
 // The work flow of the Camera3DeviceOps is:
 //
@@ -178,21 +190,15 @@
 //   4. Start the capture loop. The capture loop is composed of a series of
 //      capture requests and results.
 //
-//      For each capture request:
-//        a. Call RegisterBuffer() for each buffer associated with the request
-//           to register the buffers which will later be filled by the camera
-//           HAL with capture result.  For example, the client may register one
-//           small buffer for the low-resolution preview stream and one large
-//           buffer for the high-resolution still capture stream.
-//        b. Call ProcessCaptureRequest() to request capturing one frame.  A
-//           request may contain multiple streams and the camera HAL would fill
-//           the buffers of each streams per requirements specified in
-//           ConfigureStreams() and RegisterBuffer().  For example, the camera
-//           HAL may fill a frame to a still capture buffer with the native
-//           capture resolution, and down-scale the same frame to a lower
-//           resolution for the preview buffer.
-//      The client may continue with RegisterBuffer() -> ProcessCaptureRequest()
-//      up to the pipe-line depth configured in ConfigureStreams().
+//      The capture loop shall call ProcessCaptureRequest() to request capturing
+//      each frame.  A request may contain multiple streams and the camera HAL
+//      would fill the buffers of each streams per requirements specified in
+//      ConfigureStreams().  For example, the camera HAL may fill a frame to a
+//      still capture buffer with the native capture resolution, and down-scale
+//      the same frame to a lower resolution for the preview buffer.
+//
+//      The client may continue calling ProcessCaptureRequest() up to the
+//      pipe-line depth configured in ConfigureStreams().
 //
 //      When the camera HAL is done with a capture request, the capture result
 //      is sent back to the client through the callbacks in Camera3CallbackOps.
@@ -247,6 +253,8 @@
   // camera HAL to idle state.
   Flush@5() => (int32 result);
 
+  // [Deprecated in version 2]
+  //
   // The type of buffers the CrOS camera service currently supports.
   // GRALLOC is for the platform-specific gralloc buffer allocated by Android.
   // SHM is for the shared memory buffer allocated by Chrome.
@@ -256,6 +264,8 @@
     // Add DMABUF when needed.
   };
 
+  // [Deprecated in version 2]
+  //
   // RegisterBuffer() is called to register a buffer with the camera HAL.  The
   // registered buffer can then be specified in ProcessCaptureRequest() for the
   // camera HAL to fill captured frame.  RegisterBuffer() is not part of the
diff --git a/media/capture/video/chromeos/request_builder.cc b/media/capture/video/chromeos/request_builder.cc
new file mode 100644
index 0000000..b55a93a
--- /dev/null
+++ b/media/capture/video/chromeos/request_builder.cc
@@ -0,0 +1,96 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/capture/video/chromeos/request_builder.h"
+
+#include <utility>
+
+#include "media/capture/video/chromeos/camera_device_context.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+
+namespace media {
+
+RequestBuilder::RequestBuilder(CameraDeviceContext* device_context,
+                               RequestBufferCallback request_buffer_callback)
+    : device_context_(device_context),
+      request_buffer_callback_(std::move(request_buffer_callback)) {}
+
+RequestBuilder::~RequestBuilder() = default;
+
+cros::mojom::Camera3CaptureRequestPtr RequestBuilder::BuildRequest(
+    std::set<StreamType> stream_types,
+    cros::mojom::CameraMetadataPtr settings) {
+  auto capture_request = cros::mojom::Camera3CaptureRequest::New();
+  for (StreamType stream_type : stream_types) {
+    base::Optional<BufferInfo> buffer_info =
+        request_buffer_callback_.Run(stream_type);
+    if (!buffer_info) {
+      return capture_request;
+    }
+    const uint64_t buffer_id = buffer_info->id;
+    auto buffer_handle = CreateCameraBufferHandle(stream_type, *buffer_info);
+    auto stream_buffer =
+        CreateStreamBuffer(stream_type, buffer_id, std::move(buffer_handle));
+    capture_request->output_buffers.push_back(std::move(stream_buffer));
+  }
+
+  capture_request->settings = std::move(settings);
+  return capture_request;
+}
+
+cros::mojom::CameraBufferHandlePtr RequestBuilder::CreateCameraBufferHandle(
+    StreamType stream_type,
+    BufferInfo buffer_info) {
+  auto buffer_handle = cros::mojom::CameraBufferHandle::New();
+
+  buffer_handle->buffer_id = buffer_info.id;
+  buffer_handle->drm_format = buffer_info.drm_format;
+  buffer_handle->hal_pixel_format = buffer_info.hal_pixel_format;
+  buffer_handle->width = buffer_info.gpu_memory_buffer->GetSize().width();
+  buffer_handle->height = buffer_info.gpu_memory_buffer->GetSize().height();
+
+  gfx::NativePixmapHandle native_pixmap_handle =
+      buffer_info.gpu_memory_buffer->CloneHandle().native_pixmap_handle;
+
+  size_t num_planes = native_pixmap_handle.planes.size();
+  DCHECK_EQ(num_planes, native_pixmap_handle.fds.size());
+  // Take ownership of fds.
+  std::vector<base::ScopedFD> fds(num_planes);
+  for (size_t i = 0; i < num_planes; ++i)
+    fds[i] = base::ScopedFD(native_pixmap_handle.fds[i].fd);
+
+  std::vector<StreamCaptureInterface::Plane> planes(num_planes);
+  for (size_t i = 0; i < num_planes; ++i) {
+    mojo::ScopedHandle mojo_fd =
+        mojo::WrapPlatformHandle(mojo::PlatformHandle(std::move(fds[i])));
+    if (!mojo_fd.is_valid()) {
+      device_context_->SetErrorState(
+          media::VideoCaptureError::
+              kCrosHalV3BufferManagerFailedToWrapGpuMemoryHandle,
+          FROM_HERE, "Failed to wrap gpu memory handle");
+      return nullptr;
+    }
+    buffer_handle->fds.push_back(std::move(mojo_fd));
+    buffer_handle->strides.push_back(native_pixmap_handle.planes[i].stride);
+    buffer_handle->offsets.push_back(native_pixmap_handle.planes[i].offset);
+  }
+
+  return buffer_handle;
+}
+
+cros::mojom::Camera3StreamBufferPtr RequestBuilder::CreateStreamBuffer(
+    StreamType stream_type,
+    uint64_t buffer_id,
+    cros::mojom::CameraBufferHandlePtr buffer_handle) {
+  cros::mojom::Camera3StreamBufferPtr buffer =
+      cros::mojom::Camera3StreamBuffer::New();
+  buffer->stream_id = static_cast<uint64_t>(stream_type);
+  buffer->buffer_id = buffer_id;
+  buffer->status = cros::mojom::Camera3BufferStatus::CAMERA3_BUFFER_STATUS_OK;
+  buffer->buffer_handle = std::move(buffer_handle);
+  return buffer;
+}
+
+}  // namespace media
diff --git a/media/capture/video/chromeos/request_builder.h b/media/capture/video/chromeos/request_builder.h
new file mode 100644
index 0000000..6dbf901
--- /dev/null
+++ b/media/capture/video/chromeos/request_builder.h
@@ -0,0 +1,66 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_CAPTURE_VIDEO_CHROMEOS_REQUEST_BUILDER_H_
+#define MEDIA_CAPTURE_VIDEO_CHROMEOS_REQUEST_BUILDER_H_
+
+#include <memory>
+#include <set>
+#include <vector>
+
+#include "base/optional.h"
+#include "media/capture/video/chromeos/camera_device_delegate.h"
+#include "media/capture/video/chromeos/mojo/camera3.mojom.h"
+#include "media/capture/video_capture_types.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+namespace media {
+
+class CameraDeviceContext;
+
+// BufferInfo is used to store information about the buffer that is needed when
+// building buffers.
+struct BufferInfo {
+  uint64_t id;
+  const gfx::GpuMemoryBuffer* gpu_memory_buffer;
+  uint32_t drm_format;
+  cros::mojom::HalPixelFormat hal_pixel_format;
+};
+
+// RequestBuilder is used to build capture request that will be sent to camera
+// HAL process.
+class CAPTURE_EXPORT RequestBuilder {
+ public:
+  using RequestBufferCallback =
+      base::RepeatingCallback<base::Optional<BufferInfo>(StreamType)>;
+
+  RequestBuilder(CameraDeviceContext* device_context,
+                 // Callback to request buffer from StreamBufferManager. Having
+                 // this callback, we do not need to include StreamBufferManager
+                 // when requesting buffer.
+                 RequestBufferCallback request_buffer_callback);
+  ~RequestBuilder();
+
+  // Builds a capture request by given streams and settings.
+  cros::mojom::Camera3CaptureRequestPtr BuildRequest(
+      std::set<StreamType> stream_types,
+      cros::mojom::CameraMetadataPtr settings);
+
+ private:
+  cros::mojom::CameraBufferHandlePtr CreateCameraBufferHandle(
+      StreamType stream_type,
+      BufferInfo buffer_info);
+
+  cros::mojom::Camera3StreamBufferPtr CreateStreamBuffer(
+      StreamType stream_type,
+      uint64_t buffer_id,
+      cros::mojom::CameraBufferHandlePtr buffer_handle);
+
+  CameraDeviceContext* device_context_;
+
+  RequestBufferCallback request_buffer_callback_;
+};
+}  // namespace media
+
+#endif  // MEDIA_CAPTURE_VIDEO_CHROMEOS_REQUEST_BUILDER_H_
diff --git a/media/capture/video/chromeos/request_manager.cc b/media/capture/video/chromeos/request_manager.cc
new file mode 100644
index 0000000..3a165f7
--- /dev/null
+++ b/media/capture/video/chromeos/request_manager.cc
@@ -0,0 +1,655 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/capture/video/chromeos/request_manager.h"
+
+#include <sync/sync.h>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/posix/safe_strerror.h"
+#include "base/trace_event/trace_event.h"
+#include "media/capture/video/chromeos/camera_buffer_factory.h"
+#include "media/capture/video/chromeos/camera_device_context.h"
+#include "media/capture/video/chromeos/camera_metadata_utils.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+
+namespace media {
+
+namespace {
+
+constexpr uint32_t kUndefinedFrameNumber = 0xFFFFFFFF;
+}  // namespace
+
+RequestManager::RequestManager(
+    cros::mojom::Camera3CallbackOpsRequest callback_ops_request,
+    std::unique_ptr<StreamCaptureInterface> capture_interface,
+    CameraDeviceContext* device_context,
+    std::unique_ptr<CameraBufferFactory> camera_buffer_factory,
+    BlobifyCallback blobify_callback,
+    scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner)
+    : callback_ops_(this, std::move(callback_ops_request)),
+      capture_interface_(std::move(capture_interface)),
+      device_context_(device_context),
+      stream_buffer_manager_(
+          new StreamBufferManager(device_context_,
+                                  std::move(camera_buffer_factory))),
+      blobify_callback_(std::move(blobify_callback)),
+      ipc_task_runner_(std::move(ipc_task_runner)),
+      capturing_(false),
+      frame_number_(0),
+      partial_result_count_(1),
+      first_frame_shutter_time_(base::TimeTicks()),
+      weak_ptr_factory_(this) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+  DCHECK(callback_ops_.is_bound());
+  DCHECK(device_context_);
+  // We use base::Unretained() for the StreamBufferManager here since we
+  // guarantee |request_buffer_callback| is only used by RequestBuilder. In
+  // addition, since C++ destroys member variables in reverse order of
+  // construction, we can ensure that RequestBuilder will be destroyed prior
+  // to StreamBufferManager since RequestBuilder constructs after
+  // StreamBufferManager.
+  auto request_buffer_callback =
+      base::BindRepeating(&StreamBufferManager::RequestBuffer,
+                          base::Unretained(stream_buffer_manager_.get()));
+  request_builder_ = std::make_unique<RequestBuilder>(
+      device_context_, std::move(request_buffer_callback));
+}
+
+RequestManager::~RequestManager() = default;
+
+void RequestManager::SetUpStreamsAndBuffers(
+    VideoCaptureFormat capture_format,
+    const cros::mojom::CameraMetadataPtr& static_metadata,
+    std::vector<cros::mojom::Camera3StreamPtr> streams) {
+  // The partial result count metadata is optional; defaults to 1 in case it
+  // is not set in the static metadata.
+  const cros::mojom::CameraMetadataEntryPtr* partial_count = GetMetadataEntry(
+      static_metadata,
+      cros::mojom::CameraMetadataTag::ANDROID_REQUEST_PARTIAL_RESULT_COUNT);
+  if (partial_count) {
+    partial_result_count_ =
+        *reinterpret_cast<int32_t*>((*partial_count)->data.data());
+  }
+
+  // Set the last received frame number for each stream types to be undefined.
+  for (const auto& stream : streams) {
+    StreamType stream_type = StreamIdToStreamType(stream->id);
+    last_received_frame_number_map_[stream_type] = kUndefinedFrameNumber;
+  }
+
+  stream_buffer_manager_->SetUpStreamsAndBuffers(
+      capture_format, std::move(static_metadata), std::move(streams));
+}
+
+cros::mojom::Camera3StreamPtr RequestManager::GetStreamConfiguration(
+    StreamType stream_type) {
+  return stream_buffer_manager_->GetStreamConfiguration(stream_type);
+}
+
+void RequestManager::StartPreview(
+    cros::mojom::CameraMetadataPtr preview_settings) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+  DCHECK(repeating_request_settings_.is_null());
+
+  capturing_ = true;
+  repeating_request_settings_ = std::move(preview_settings);
+
+  PrepareCaptureRequest();
+}
+
+void RequestManager::StopPreview(base::OnceCallback<void(int32_t)> callback) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+
+  capturing_ = false;
+  repeating_request_settings_ = nullptr;
+  if (callback) {
+    capture_interface_->Flush(std::move(callback));
+  }
+}
+
+void RequestManager::TakePhoto(cros::mojom::CameraMetadataPtr settings,
+                               VideoCaptureDevice::TakePhotoCallback callback) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+
+  std::vector<uint8_t> frame_orientation(sizeof(int32_t));
+  *reinterpret_cast<int32_t*>(frame_orientation.data()) =
+      base::checked_cast<int32_t>(device_context_->GetCameraFrameOrientation());
+  cros::mojom::CameraMetadataEntryPtr e =
+      cros::mojom::CameraMetadataEntry::New();
+  e->tag = cros::mojom::CameraMetadataTag::ANDROID_JPEG_ORIENTATION;
+  e->type = cros::mojom::EntryType::TYPE_INT32;
+  e->count = 1;
+  e->data = std::move(frame_orientation);
+  AddOrUpdateMetadataEntry(&settings, std::move(e));
+
+  oneshot_request_settings_.push(std::move(settings));
+  take_photo_callback_queue_.push(std::move(callback));
+}
+
+void RequestManager::PrepareCaptureRequest() {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+
+  if (!capturing_) {
+    return;
+  }
+  std::set<StreamType> stream_types;
+  cros::mojom::CameraMetadataPtr settings;
+
+  // Reqular request should always have repeating request if the preview is
+  // on.
+  stream_types.insert(StreamType::kPreview);
+  if (!stream_buffer_manager_->HasFreeBuffers(stream_types)) {
+    return;
+  }
+  bool has_still_capture_streams = false;
+  if (!oneshot_request_settings_.empty() &&
+      stream_buffer_manager_->HasFreeBuffers({StreamType::kStillCapture})) {
+    stream_types.insert(StreamType::kStillCapture);
+    settings = std::move(oneshot_request_settings_.front());
+    oneshot_request_settings_.pop();
+    has_still_capture_streams = true;
+  } else {
+    settings = repeating_request_settings_.Clone();
+  }
+
+  auto capture_request = request_builder_->BuildRequest(std::move(stream_types),
+                                                        std::move(settings));
+  if (has_still_capture_streams) {
+    SendCaptureRequest(std::move(capture_request),
+                       std::move(take_photo_callback_queue_.front()));
+    take_photo_callback_queue_.pop();
+  } else {
+    SendCaptureRequest(std::move(capture_request), base::DoNothing());
+  }
+}
+
+void RequestManager::SendCaptureRequest(
+    cros::mojom::Camera3CaptureRequestPtr capture_request,
+    VideoCaptureDevice::TakePhotoCallback take_photo_callback) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+  if (!capturing_) {
+    return;
+  }
+
+  CaptureResult& pending_result = pending_results_[frame_number_];
+  pending_result.still_capture_callback = std::move(take_photo_callback);
+  pending_result.unsubmitted_buffer_count =
+      capture_request->output_buffers.size();
+
+  UpdateCaptureSettings(&capture_request->settings);
+  capture_request->frame_number = frame_number_;
+  capture_interface_->ProcessCaptureRequest(
+      std::move(capture_request),
+      base::BindOnce(&RequestManager::OnProcessedCaptureRequest,
+                     weak_ptr_factory_.GetWeakPtr()));
+  frame_number_++;
+}
+
+void RequestManager::OnProcessedCaptureRequest(int32_t result) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+
+  if (!capturing_) {
+    return;
+  }
+  if (result != 0) {
+    device_context_->SetErrorState(
+        media::VideoCaptureError::
+            kCrosHalV3BufferManagerProcessCaptureRequestFailed,
+        FROM_HERE,
+        std::string("Process capture request failed: ") +
+            base::safe_strerror(-result));
+    return;
+  }
+
+  PrepareCaptureRequest();
+}
+
+void RequestManager::ProcessCaptureResult(
+    cros::mojom::Camera3CaptureResultPtr result) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+
+  if (!capturing_) {
+    return;
+  }
+  uint32_t frame_number = result->frame_number;
+  // A new partial result may be created in either ProcessCaptureResult or
+  // Notify.
+  CaptureResult& pending_result = pending_results_[frame_number];
+
+  // |result->partial_result| is set to 0 if the capture result contains only
+  // the result buffer handles and no result metadata.
+  if (result->partial_result != 0) {
+    uint32_t result_id = result->partial_result;
+    if (result_id > partial_result_count_) {
+      device_context_->SetErrorState(
+          media::VideoCaptureError::
+              kCrosHalV3BufferManagerInvalidPendingResultId,
+          FROM_HERE,
+          std::string("Invalid pending_result id: ") +
+              std::to_string(result_id));
+      return;
+    }
+    if (pending_result.partial_metadata_received.count(result_id)) {
+      device_context_->SetErrorState(
+          media::VideoCaptureError::
+              kCrosHalV3BufferManagerReceivedDuplicatedPartialMetadata,
+          FROM_HERE,
+          std::string("Received duplicated partial metadata: ") +
+              std::to_string(result_id));
+      return;
+    }
+    DVLOG(2) << "Received partial result " << result_id << " for frame "
+             << frame_number;
+    pending_result.partial_metadata_received.insert(result_id);
+    MergeMetadata(&pending_result.metadata, result->result);
+  }
+
+  if (result->output_buffers) {
+    if (result->output_buffers->size() > kMaxConfiguredStreams) {
+      device_context_->SetErrorState(
+          media::VideoCaptureError::
+              kCrosHalV3BufferManagerIncorrectNumberOfOutputBuffersReceived,
+          FROM_HERE,
+          std::string("Incorrect number of output buffers received: ") +
+              std::to_string(result->output_buffers->size()));
+      return;
+    }
+
+    for (auto& stream_buffer : result->output_buffers.value()) {
+      DVLOG(2) << "Received capture result for frame " << frame_number
+               << " stream_id: " << stream_buffer->stream_id;
+      StreamType stream_type = StreamIdToStreamType(stream_buffer->stream_id);
+      if (stream_type == StreamType::kUnknown) {
+        device_context_->SetErrorState(
+            media::VideoCaptureError::
+                kCrosHalV3BufferManagerInvalidTypeOfOutputBuffersReceived,
+            FROM_HERE,
+            std::string("Invalid type of output buffers received: ") +
+                std::to_string(stream_buffer->stream_id));
+        return;
+      }
+
+      // The camera HAL v3 API specifies that only one capture result can carry
+      // the result buffer for any given frame number.
+      if (last_received_frame_number_map_[stream_type] ==
+          kUndefinedFrameNumber) {
+        last_received_frame_number_map_[stream_type] = frame_number;
+      } else {
+        if (last_received_frame_number_map_[stream_type] == frame_number) {
+          device_context_->SetErrorState(
+              media::VideoCaptureError::
+                  kCrosHalV3BufferManagerReceivedMultipleResultBuffersForFrame,
+              FROM_HERE,
+              std::string("Received multiple result buffers for frame ") +
+                  std::to_string(frame_number) + std::string(" for stream ") +
+                  std::to_string(stream_buffer->stream_id));
+          return;
+        } else if (last_received_frame_number_map_[stream_type] >
+                   frame_number) {
+          device_context_->SetErrorState(
+              media::VideoCaptureError::
+                  kCrosHalV3BufferManagerReceivedFrameIsOutOfOrder,
+              FROM_HERE,
+              std::string("Received frame is out-of-order; expect frame number "
+                          "greater than ") +
+                  std::to_string(last_received_frame_number_map_[stream_type]) +
+                  std::string(" but got ") + std::to_string(frame_number));
+        } else {
+          last_received_frame_number_map_[stream_type] = frame_number;
+        }
+      }
+
+      if (stream_buffer->status ==
+          cros::mojom::Camera3BufferStatus::CAMERA3_BUFFER_STATUS_ERROR) {
+        // If the buffer is marked as error, its content is discarded for this
+        // frame.  Send the buffer to the free list directly through
+        // SubmitCaptureResult.
+        SubmitCaptureResult(frame_number, stream_type,
+                            std::move(stream_buffer));
+      } else {
+        pending_result.buffers[stream_type] = std::move(stream_buffer);
+      }
+    }
+  }
+
+  TRACE_EVENT1("camera", "Capture Result", "frame_number", frame_number);
+  TrySubmitPendingBuffers(frame_number);
+}
+
+void RequestManager::TrySubmitPendingBuffers(uint32_t frame_number) {
+  if (!pending_results_.count(frame_number)) {
+    return;
+  }
+
+  CaptureResult& pending_result = pending_results_[frame_number];
+
+  // If the metadata is not ready, or the shutter time is not set, just
+  // returned.
+  bool is_ready_to_submit =
+      pending_result.partial_metadata_received.size() > 0 &&
+      *pending_result.partial_metadata_received.rbegin() ==
+          partial_result_count_ &&
+      !pending_result.reference_time.is_null();
+  if (!is_ready_to_submit) {
+    return;
+  }
+
+  if (!pending_result.buffers.empty()) {
+    // Put pending buffers into local map since |pending_result| might be
+    // deleted in SubmitCaptureResult(). We should not reference pending_result
+    // after SubmitCaptureResult() is triggered.
+    std::map<StreamType, cros::mojom::Camera3StreamBufferPtr> buffers =
+        std::move(pending_result.buffers);
+    for (auto& it : buffers) {
+      SubmitCaptureResult(frame_number, it.first, std::move(it.second));
+    }
+  }
+}
+
+void RequestManager::Notify(cros::mojom::Camera3NotifyMsgPtr message) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+
+  if (!capturing_) {
+    return;
+  }
+  if (message->type == cros::mojom::Camera3MsgType::CAMERA3_MSG_ERROR) {
+    auto error = std::move(message->message->get_error());
+    uint32_t frame_number = error->frame_number;
+    uint64_t error_stream_id = error->error_stream_id;
+    StreamType stream_type = StreamIdToStreamType(error_stream_id);
+    if (stream_type == StreamType::kUnknown) {
+      device_context_->SetErrorState(
+          media::VideoCaptureError::
+              kCrosHalV3BufferManagerUnknownStreamInCamera3NotifyMsg,
+          FROM_HERE,
+          std::string("Unknown stream in Camera3NotifyMsg: ") +
+              std::to_string(error_stream_id));
+      return;
+    }
+    cros::mojom::Camera3ErrorMsgCode error_code = error->error_code;
+    HandleNotifyError(frame_number, stream_type, error_code);
+  } else if (message->type ==
+             cros::mojom::Camera3MsgType::CAMERA3_MSG_SHUTTER) {
+    auto shutter = std::move(message->message->get_shutter());
+    uint32_t frame_number = shutter->frame_number;
+    uint64_t shutter_time = shutter->timestamp;
+    DVLOG(2) << "Received shutter time for frame " << frame_number;
+    if (!shutter_time) {
+      device_context_->SetErrorState(
+          media::VideoCaptureError::
+              kCrosHalV3BufferManagerReceivedInvalidShutterTime,
+          FROM_HERE,
+          std::string("Received invalid shutter time: ") +
+              std::to_string(shutter_time));
+      return;
+    }
+    CaptureResult& pending_result = pending_results_[frame_number];
+    // Shutter timestamp is in ns.
+    base::TimeTicks reference_time =
+        base::TimeTicks() +
+        base::TimeDelta::FromMicroseconds(shutter_time / 1000);
+    pending_result.reference_time = reference_time;
+    if (first_frame_shutter_time_.is_null()) {
+      // Record the shutter time of the first frame for calculating the
+      // timestamp.
+      first_frame_shutter_time_ = reference_time;
+    }
+    pending_result.timestamp = reference_time - first_frame_shutter_time_;
+    TrySubmitPendingBuffers(frame_number);
+  }
+}
+
+void RequestManager::HandleNotifyError(
+    uint32_t frame_number,
+    StreamType stream_type,
+    cros::mojom::Camera3ErrorMsgCode error_code) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+
+  std::string warning_msg;
+
+  switch (error_code) {
+    case cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_DEVICE:
+      // Fatal error and no more frames will be produced by the device.
+      device_context_->SetErrorState(
+          media::VideoCaptureError::kCrosHalV3BufferManagerFatalDeviceError,
+          FROM_HERE, "Fatal device error");
+      return;
+
+    case cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_REQUEST:
+      // An error has occurred in processing the request; the request
+      // specified by |frame_number| has been dropped by the camera device.
+      // Subsequent requests are unaffected.
+      //
+      // The HAL will call ProcessCaptureResult with the buffers' state set to
+      // STATUS_ERROR.  The content of the buffers will be dropped and the
+      // buffers will be reused in SubmitCaptureResult.
+      warning_msg =
+          std::string("An error occurred while processing request for frame ") +
+          std::to_string(frame_number);
+      break;
+
+    case cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_RESULT:
+      // An error has occurred in producing the output metadata buffer for a
+      // result; the output metadata will not be available for the frame
+      // specified by |frame_number|.  Subsequent requests are unaffected.
+      warning_msg = std::string(
+                        "An error occurred while producing result "
+                        "metadata for frame ") +
+                    std::to_string(frame_number);
+      break;
+
+    case cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_BUFFER:
+      // An error has occurred in placing the output buffer into a stream for
+      // a request. |frame_number| specifies the request for which the buffer
+      // was dropped, and |stream_type| specifies the stream that dropped
+      // the buffer.
+      //
+      // The HAL will call ProcessCaptureResult with the buffer's state set to
+      // STATUS_ERROR.  The content of the buffer will be dropped and the
+      // buffer will be reused in SubmitCaptureResult.
+      warning_msg =
+          std::string(
+              "An error occurred while filling output buffer of stream ") +
+          StreamTypeToString(stream_type) + std::string(" in frame ") +
+          std::to_string(frame_number);
+      break;
+
+    default:
+      // To eliminate the warning for not handling CAMERA3_MSG_NUM_ERRORS
+      break;
+  }
+
+  LOG(WARNING) << warning_msg << stream_type;
+  device_context_->LogToClient(warning_msg);
+
+  // If the buffer is already returned by the HAL, submit it and we're done.
+  if (pending_results_.count(frame_number)) {
+    auto it = pending_results_[frame_number].buffers.find(stream_type);
+    if (it != pending_results_[frame_number].buffers.end()) {
+      auto stream_buffer = std::move(it->second);
+      pending_results_[frame_number].buffers.erase(stream_type);
+      SubmitCaptureResult(frame_number, stream_type, std::move(stream_buffer));
+    }
+  }
+}
+
+void RequestManager::SubmitCaptureResult(
+    uint32_t frame_number,
+    StreamType stream_type,
+    cros::mojom::Camera3StreamBufferPtr stream_buffer) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+  DCHECK(pending_results_.count(frame_number));
+
+  CaptureResult& pending_result = pending_results_[frame_number];
+  DVLOG(2) << "Submit capture result of frame " << frame_number
+           << " for stream " << static_cast<int>(stream_type);
+  for (auto* observer : result_metadata_observers_) {
+    observer->OnResultMetadataAvailable(pending_result.metadata);
+  }
+  uint64_t buffer_id = stream_buffer->buffer_id;
+
+  // Wait on release fence before delivering the result buffer to client.
+  if (stream_buffer->release_fence.is_valid()) {
+    const int kSyncWaitTimeoutMs = 1000;
+    mojo::PlatformHandle fence =
+        mojo::UnwrapPlatformHandle(std::move(stream_buffer->release_fence));
+    if (!fence.is_valid()) {
+      device_context_->SetErrorState(
+          media::VideoCaptureError::
+              kCrosHalV3BufferManagerFailedToUnwrapReleaseFenceFd,
+          FROM_HERE, "Failed to unwrap release fence fd");
+      return;
+    }
+    if (!sync_wait(fence.GetFD().get(), kSyncWaitTimeoutMs)) {
+      device_context_->SetErrorState(
+          media::VideoCaptureError::
+              kCrosHalV3BufferManagerSyncWaitOnReleaseFenceTimedOut,
+          FROM_HERE, "Sync wait on release fence timed out");
+      return;
+    }
+  }
+
+  // Deliver the captured data to client.
+  if (stream_buffer->status ==
+      cros::mojom::Camera3BufferStatus::CAMERA3_BUFFER_STATUS_OK) {
+    gfx::GpuMemoryBuffer* buffer =
+        stream_buffer_manager_->GetBufferById(stream_type, buffer_id);
+    if (stream_type == StreamType::kPreview) {
+      device_context_->SubmitCapturedData(
+          buffer, stream_buffer_manager_->GetStreamCaptureFormat(stream_type),
+          pending_result.reference_time, pending_result.timestamp);
+    } else if (stream_type == StreamType::kStillCapture) {
+      DCHECK(pending_result.still_capture_callback);
+      const Camera3JpegBlob* header = reinterpret_cast<Camera3JpegBlob*>(
+          reinterpret_cast<uintptr_t>(buffer->memory(0)) +
+          buffer->GetSize().width() - sizeof(Camera3JpegBlob));
+      if (header->jpeg_blob_id != kCamera3JpegBlobId) {
+        device_context_->SetErrorState(
+            media::VideoCaptureError::kCrosHalV3BufferManagerInvalidJpegBlob,
+            FROM_HERE, "Invalid JPEG blob");
+        return;
+      }
+      // Still capture result from HALv3 already has orientation info in EXIF,
+      // so just provide 0 as screen rotation in |blobify_callback_| parameters.
+      mojom::BlobPtr blob = blobify_callback_.Run(
+          reinterpret_cast<uint8_t*>(buffer->memory(0)), header->jpeg_size,
+          stream_buffer_manager_->GetStreamCaptureFormat(stream_type), 0);
+      if (blob) {
+        std::move(pending_result.still_capture_callback).Run(std::move(blob));
+      } else {
+        // TODO(wtlee): If it is fatal, we should set error state here.
+        LOG(ERROR) << "Failed to blobify the captured JPEG image";
+      }
+    }
+  }
+  stream_buffer_manager_->ReleaseBuffer(stream_type, buffer_id);
+  pending_result.unsubmitted_buffer_count--;
+
+  if (pending_result.unsubmitted_buffer_count == 0) {
+    pending_results_.erase(frame_number);
+  }
+  // Every time a buffer is released, try to prepare another capture request
+  // again.
+  PrepareCaptureRequest();
+}
+
+size_t RequestManager::GetNumberOfStreams() {
+  return stream_buffer_manager_->GetNumberOfStreams();
+}
+
+void RequestManager::AddResultMetadataObserver(
+    ResultMetadataObserver* observer) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+  DCHECK(!result_metadata_observers_.count(observer));
+
+  result_metadata_observers_.insert(observer);
+}
+
+void RequestManager::RemoveResultMetadataObserver(
+    ResultMetadataObserver* observer) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+  DCHECK(result_metadata_observers_.count(observer));
+
+  result_metadata_observers_.erase(observer);
+}
+
+void RequestManager::SetCaptureMetadata(cros::mojom::CameraMetadataTag tag,
+                                        cros::mojom::EntryType type,
+                                        size_t count,
+                                        std::vector<uint8_t> value) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+
+  cros::mojom::CameraMetadataEntryPtr setting =
+      cros::mojom::CameraMetadataEntry::New();
+
+  setting->tag = tag;
+  setting->type = type;
+  setting->count = count;
+  setting->data = std::move(value);
+
+  capture_settings_override_.push_back(std::move(setting));
+}
+
+void RequestManager::SetRepeatingCaptureMetadata(
+    cros::mojom::CameraMetadataTag tag,
+    cros::mojom::EntryType type,
+    size_t count,
+    std::vector<uint8_t> value) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+  cros::mojom::CameraMetadataEntryPtr setting =
+      cros::mojom::CameraMetadataEntry::New();
+
+  setting->tag = tag;
+  setting->type = type;
+  setting->count = count;
+  setting->data = std::move(value);
+
+  capture_settings_repeating_override_[tag] = std::move(setting);
+}
+
+void RequestManager::UnsetRepeatingCaptureMetadata(
+    cros::mojom::CameraMetadataTag tag) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+  auto it = capture_settings_repeating_override_.find(tag);
+  if (it == capture_settings_repeating_override_.end()) {
+    LOG(ERROR) << "Unset a non-existent metadata: " << tag;
+    return;
+  }
+  capture_settings_repeating_override_.erase(it);
+}
+
+void RequestManager::UpdateCaptureSettings(
+    cros::mojom::CameraMetadataPtr* capture_settings) {
+  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
+
+  if (capture_settings_override_.empty() &&
+      capture_settings_repeating_override_.empty()) {
+    return;
+  }
+
+  for (const auto& setting : capture_settings_repeating_override_) {
+    AddOrUpdateMetadataEntry(capture_settings, setting.second.Clone());
+  }
+
+  for (auto& s : capture_settings_override_) {
+    AddOrUpdateMetadataEntry(capture_settings, std::move(s));
+  }
+  capture_settings_override_.clear();
+  SortCameraMetadata(capture_settings);
+}
+
+RequestManager::CaptureResult::CaptureResult()
+    : metadata(cros::mojom::CameraMetadata::New()),
+      unsubmitted_buffer_count(0) {}
+
+RequestManager::CaptureResult::~CaptureResult() = default;
+
+}  // namespace media
diff --git a/media/capture/video/chromeos/request_manager.h b/media/capture/video/chromeos/request_manager.h
new file mode 100644
index 0000000..fb9110b4
--- /dev/null
+++ b/media/capture/video/chromeos/request_manager.h
@@ -0,0 +1,302 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_CAPTURE_VIDEO_CHROMEOS_REQUEST_MANAGER_H_
+#define MEDIA_CAPTURE_VIDEO_CHROMEOS_REQUEST_MANAGER_H_
+
+#include <cstring>
+#include <map>
+#include <memory>
+#include <queue>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "media/capture/mojom/image_capture.mojom.h"
+#include "media/capture/video/chromeos/camera_device_delegate.h"
+#include "media/capture/video/chromeos/mojo/camera3.mojom.h"
+#include "media/capture/video/chromeos/request_builder.h"
+#include "media/capture/video/chromeos/stream_buffer_manager.h"
+#include "media/capture/video_capture_types.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+namespace media {
+
+class CameraBufferFactory;
+class CameraDeviceContext;
+
+// The JPEG transport header as defined by Android camera HAL v3 API.  The JPEG
+// transport header is at the end of the blob buffer filled by the HAL.
+constexpr uint16_t kCamera3JpegBlobId = 0x00FF;
+struct Camera3JpegBlob {
+  uint16_t jpeg_blob_id;
+  uint32_t jpeg_size;
+};
+
+static const int kMaxConfiguredStreams = 2;
+
+// Interface that provides API to let Camera3AController to update the metadata
+// that will be sent with capture request.
+class CAPTURE_EXPORT CaptureMetadataDispatcher {
+ public:
+  class ResultMetadataObserver {
+   public:
+    virtual ~ResultMetadataObserver() {}
+    virtual void OnResultMetadataAvailable(
+        const cros::mojom::CameraMetadataPtr&) = 0;
+  };
+
+  virtual ~CaptureMetadataDispatcher() {}
+  virtual void AddResultMetadataObserver(ResultMetadataObserver* observer) = 0;
+  virtual void RemoveResultMetadataObserver(
+      ResultMetadataObserver* observer) = 0;
+  virtual void SetCaptureMetadata(cros::mojom::CameraMetadataTag tag,
+                                  cros::mojom::EntryType type,
+                                  size_t count,
+                                  std::vector<uint8_t> value) = 0;
+  virtual void SetRepeatingCaptureMetadata(cros::mojom::CameraMetadataTag tag,
+                                           cros::mojom::EntryType type,
+                                           size_t count,
+                                           std::vector<uint8_t> value) = 0;
+  virtual void UnsetRepeatingCaptureMetadata(
+      cros::mojom::CameraMetadataTag tag) = 0;
+};
+
+// RequestManager is responsible for managing the flow for sending capture
+// requests and receiving capture results. Having RequestBuilder to build
+// request and StreamBufferManager to handles stream buffers, it focuses on
+// controlling the capture flow between Chrome and camera HAL process.
+class CAPTURE_EXPORT RequestManager final
+    : public cros::mojom::Camera3CallbackOps,
+      public CaptureMetadataDispatcher {
+ public:
+  using BlobifyCallback = base::RepeatingCallback<mojom::BlobPtr(
+      const uint8_t* buffer,
+      const uint32_t bytesused,
+      const VideoCaptureFormat& capture_format,
+      int screen_rotation)>;
+
+  // CaptureResult is used to hold the pending capture results for each frame.
+  struct CaptureResult {
+    CaptureResult();
+    ~CaptureResult();
+    // |reference_time| and |timestamp| are derived from the shutter time of
+    // this frame.  They are be passed to |client_->OnIncomingCapturedData|
+    // along with the |buffers| when the captured frame is submitted.
+    base::TimeTicks reference_time;
+    base::TimeDelta timestamp;
+    // The result metadata.  Contains various information about the captured
+    // frame.
+    cros::mojom::CameraMetadataPtr metadata;
+    // The buffer handles that hold the captured data of this frame.
+    std::map<StreamType, cros::mojom::Camera3StreamBufferPtr> buffers;
+    // The set of the partial metadata received.  For each capture result, the
+    // total number of partial metadata should equal to
+    // |partial_result_count_|.
+    std::set<uint32_t> partial_metadata_received;
+    // Incremented for every stream buffer requested for the given frame.
+    // StreamBufferManager destructs the CaptureResult when
+    // |unsubmitted_buffer_count| drops to zero.
+    size_t unsubmitted_buffer_count;
+    // The callback used to return the captured still capture JPEG buffer.  Set
+    // if and only if the capture request was sent with a still capture buffer.
+    VideoCaptureDevice::TakePhotoCallback still_capture_callback;
+  };
+
+  RequestManager(cros::mojom::Camera3CallbackOpsRequest callback_ops_request,
+                 std::unique_ptr<StreamCaptureInterface> capture_interface,
+                 CameraDeviceContext* device_context,
+                 std::unique_ptr<CameraBufferFactory> camera_buffer_factory,
+                 BlobifyCallback blobify_callback,
+                 scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner);
+  ~RequestManager() override;
+
+  // Sets up the stream context and allocate buffers according to the
+  // configuration specified in |streams|.
+  void SetUpStreamsAndBuffers(
+      VideoCaptureFormat capture_format,
+      const cros::mojom::CameraMetadataPtr& static_metadata,
+      std::vector<cros::mojom::Camera3StreamPtr> streams);
+
+  cros::mojom::Camera3StreamPtr GetStreamConfiguration(StreamType stream_type);
+
+  // StartPreview is the entry point to starting the video capture.  The way
+  // the video capture loop works is:
+  //
+  //  (1) Preparing capture request by mixing repeating request with still
+  //      capture request if it exists. And build the capture request by
+  //      RequestBuilder.
+  //  (2) Once the capture request is built, it triggers SendCaptureRequest() to
+  //      send the capture request and it will go back to (1) to generate next
+  //      request.
+  //  (3) The camera HAL returns the shutter time of a capture request through
+  //      Notify, and the filled buffer through ProcessCaptureResult.
+  //  (4) Once all the result metadata are collected, TrySubmitPendingBuffers()
+  //      is passed and trigger SubmitCaptureResult() to deliver the filled
+  //      buffer to Chrome. After the buffer is consumed by Chrome it is
+  //      enqueued back to the free buffer queue.  Goto (1) to start another
+  //      capture loop.
+  //
+  // When TakePhoto() is called, an additional BLOB buffer is queued in step (2)
+  // to let the HAL fill the still capture JPEG image.  When the JPEG image is
+  // returned in (4), it's passed to upper layer through the TakePhotoCallback.
+  void StartPreview(cros::mojom::CameraMetadataPtr preview_settings);
+
+  // Stops the capture loop.  After StopPreview is called |callback_ops_| is
+  // unbound, so no new capture request or result will be processed. It will
+  // also try to trigger Flush() and pass the |callback| to it.
+  void StopPreview(base::OnceCallback<void(int32_t)> callback);
+
+  void TakePhoto(cros::mojom::CameraMetadataPtr settings,
+                 VideoCaptureDevice::TakePhotoCallback callback);
+
+  size_t GetNumberOfStreams();
+
+  // CaptureMetadataDispatcher implementations.
+  void AddResultMetadataObserver(ResultMetadataObserver* observer) override;
+  void RemoveResultMetadataObserver(ResultMetadataObserver* observer) override;
+
+  // Queues a capture setting that will be send along with the earliest next
+  // capture request.
+  void SetCaptureMetadata(cros::mojom::CameraMetadataTag tag,
+                          cros::mojom::EntryType type,
+                          size_t count,
+                          std::vector<uint8_t> value) override;
+
+  void SetRepeatingCaptureMetadata(cros::mojom::CameraMetadataTag tag,
+                                   cros::mojom::EntryType type,
+                                   size_t count,
+                                   std::vector<uint8_t> value) override;
+
+  void UnsetRepeatingCaptureMetadata(
+      cros::mojom::CameraMetadataTag tag) override;
+
+ private:
+  friend class RequestManagerTest;
+
+  // Prepares a capture request by mixing repeating request with still capture
+  // request if it exists.
+  void PrepareCaptureRequest();
+
+  // Decorates the frame number and settings for the capture request and send it
+  // to HAL.
+  void SendCaptureRequest(
+      cros::mojom::Camera3CaptureRequestPtr capture_request,
+      VideoCaptureDevice::TakePhotoCallback take_photo_callback);
+
+  // Callback for ProcessCaptureRequest().
+  void OnProcessedCaptureRequest(int32_t result);
+
+  // If there are some metadata set by SetCaptureMetadata() or
+  // SetRepeatingCaptureMetadata(), update them onto |capture_settings|.
+  void UpdateCaptureSettings(cros::mojom::CameraMetadataPtr* capture_settings);
+
+  // ProcessCaptureResult receives the result metadata as well as the filled
+  // buffer from camera HAL.  The result metadata may be divided and delivered
+  // in several stages.  Before all the result metadata is received the
+  // partial results are kept in |pending_results_|.
+  void ProcessCaptureResult(
+      cros::mojom::Camera3CaptureResultPtr result) override;
+
+  // Notify receives the shutter time of capture requests and various errors
+  // from camera HAL.  The shutter time is used as the timestamp in the video
+  // frame delivered to Chrome.
+  void Notify(cros::mojom::Camera3NotifyMsgPtr message) override;
+
+  void HandleNotifyError(uint32_t frame_number,
+                         StreamType stream_type,
+                         cros::mojom::Camera3ErrorMsgCode error_code);
+
+  // Submits the captured buffer of frame |frame_number_| for the given
+  // |stream_type| to Chrome if all the required metadata and the captured
+  // buffer are received.  After the buffer is submitted the function then
+  // enqueues the buffer to free buffer queue for the next capture request.
+  void SubmitCaptureResult(uint32_t frame_number,
+                           StreamType stream_type,
+                           cros::mojom::Camera3StreamBufferPtr stream_buffer);
+
+  // Checks if the pending buffers are ready to submit. Trigger
+  // SubmitCaptureResult() if the buffers are ready to submit.
+  void TrySubmitPendingBuffers(uint32_t frame_number);
+
+  mojo::Binding<cros::mojom::Camera3CallbackOps> callback_ops_;
+
+  std::unique_ptr<StreamCaptureInterface> capture_interface_;
+
+  CameraDeviceContext* device_context_;
+
+  // StreamBufferManager should be declared before RequestBuilder since
+  // RequestBuilder holds an instance of StreamBufferManager and should be
+  // destroyed first.
+  std::unique_ptr<StreamBufferManager> stream_buffer_manager_;
+
+  std::unique_ptr<RequestBuilder> request_builder_;
+
+  BlobifyCallback blobify_callback_;
+
+  // Where all the Mojo IPC calls takes place.
+  const scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_;
+
+  // A flag indicating whether the capture loops is running.
+  bool capturing_;
+
+  // The frame number.  Increased by one for each capture request sent; reset
+  // to zero in AllocateAndStart.
+  uint32_t frame_number_;
+
+  // The number of partial stages.  |partial_result_count_| is learned by
+  // querying |static_metadata_|.  In case the result count is absent in
+  // |static_metadata_|, it defaults to one which means all the result
+  // metadata and captured buffer of a frame are returned together in one
+  // shot.
+  uint32_t partial_result_count_;
+
+  // The shutter time of the first frame.  We derive the |timestamp| of a
+  // frame using the difference between the frame's shutter time and
+  // |first_frame_shutter_time_|.
+  base::TimeTicks first_frame_shutter_time_;
+
+  // The repeating request settings.  The settings come from the default preview
+  // request settings reported by the HAL.  |repeating_request_settings_| is the
+  // default settings for each capture request.
+  cros::mojom::CameraMetadataPtr repeating_request_settings_;
+
+  // A queue of oneshot request settings.  These are the request settings for
+  // each still capture requests.  |oneshot_request_settings_| overrides
+  // |repeating_request_settings_| if present.
+  std::queue<cros::mojom::CameraMetadataPtr> oneshot_request_settings_;
+
+  // StreamBufferManager does not own the ResultMetadataObservers.  The
+  // observers are responsible for removing itself before self-destruction.
+  std::unordered_set<ResultMetadataObserver*> result_metadata_observers_;
+
+  // The list of settings to set/override once in the capture request.
+  std::vector<cros::mojom::CameraMetadataEntryPtr> capture_settings_override_;
+
+  // The settings to set/override repeatedly in the capture request.  In
+  // conflict with |capture_settings_override_|, this one has lower priority.
+  std::map<cros::mojom::CameraMetadataTag, cros::mojom::CameraMetadataEntryPtr>
+      capture_settings_repeating_override_;
+
+  // Stores the pending capture results of the current in-flight frames.
+  std::map<uint32_t, CaptureResult> pending_results_;
+
+  // Callback for TakePhoto(). When preparing capture request, the callback will
+  // be popped and moved to CaptureResult.
+  std::queue<VideoCaptureDevice::TakePhotoCallback> take_photo_callback_queue_;
+
+  // Map for retrieving the last received frame number. It is used to check for
+  // duplicate or out of order of frames.
+  std::map<StreamType, uint32_t> last_received_frame_number_map_;
+
+  base::WeakPtrFactory<RequestManager> weak_ptr_factory_;
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(RequestManager);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_CAPTURE_VIDEO_CHROMEOS_REQUEST_MANAGER_H_
diff --git a/media/capture/video/chromeos/stream_buffer_manager_unittest.cc b/media/capture/video/chromeos/request_manager_unittest.cc
similarity index 71%
rename from media/capture/video/chromeos/stream_buffer_manager_unittest.cc
rename to media/capture/video/chromeos/request_manager_unittest.cc
index 823f210..f79620d 100644
--- a/media/capture/video/chromeos/stream_buffer_manager_unittest.cc
+++ b/media/capture/video/chromeos/request_manager_unittest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "media/capture/video/chromeos/request_manager.h"
 #include "media/capture/video/chromeos/stream_buffer_manager.h"
 
 #include <map>
@@ -36,27 +37,6 @@
 
 class MockStreamCaptureInterface : public StreamCaptureInterface {
  public:
-  void RegisterBuffer(uint64_t buffer_id,
-                      cros::mojom::Camera3DeviceOps::BufferType type,
-                      uint32_t drm_format,
-                      cros::mojom::HalPixelFormat hal_pixel_format,
-                      uint32_t width,
-                      uint32_t height,
-                      std::vector<StreamCaptureInterface::Plane> planes,
-                      base::OnceCallback<void(int32_t)> callback) {
-    DoRegisterBuffer(buffer_id, type, drm_format, hal_pixel_format, width,
-                     height, planes, callback);
-  }
-  MOCK_METHOD8(DoRegisterBuffer,
-               void(uint64_t buffer_id,
-                    cros::mojom::Camera3DeviceOps::BufferType type,
-                    uint32_t drm_format,
-                    cros::mojom::HalPixelFormat hal_pixel_format,
-                    uint32_t width,
-                    uint32_t height,
-                    const std::vector<StreamCaptureInterface::Plane>& planes,
-                    base::OnceCallback<void(int32_t)>& callback));
-
   void ProcessCaptureRequest(cros::mojom::Camera3CaptureRequestPtr request,
                              base::OnceCallback<void(int32_t)> callback) {
     DoProcessCaptureRequest(request, callback);
@@ -101,7 +81,7 @@
 
 }  // namespace
 
-class StreamBufferManagerTest : public ::testing::Test {
+class RequestManagerTest : public ::testing::Test {
  public:
   void SetUp() override {
     quit_ = false;
@@ -110,7 +90,7 @@
     device_context_ = std::make_unique<CameraDeviceContext>(
         std::make_unique<unittest_internal::MockVideoCaptureClient>());
 
-    stream_buffer_manager_ = std::make_unique<StreamBufferManager>(
+    request_manager_ = std::make_unique<RequestManager>(
         std::move(callback_ops_request),
         std::make_unique<MockStreamCaptureInterface>(), device_context_.get(),
         std::make_unique<FakeCameraBufferFactory>(),
@@ -121,7 +101,7 @@
         base::ThreadTaskRunnerHandle::Get());
   }
 
-  void TearDown() override { stream_buffer_manager_.reset(); }
+  void TearDown() override { request_manager_.reset(); }
 
   void DoLoop() {
     run_loop_.reset(new base::RunLoop());
@@ -168,20 +148,6 @@
     return static_metadata;
   }
 
-  void RegisterBuffer(uint64_t buffer_id,
-                      cros::mojom::Camera3DeviceOps::BufferType type,
-                      uint32_t drm_format,
-                      cros::mojom::HalPixelFormat hal_pixel_format,
-                      uint32_t width,
-                      uint32_t height,
-                      const std::vector<StreamCaptureInterface::Plane>& planes,
-                      base::OnceCallback<void(int32_t)>& callback) {
-    if (quit_) {
-      return;
-    }
-    std::move(callback).Run(0);
-  }
-
   void ProcessCaptureRequest(cros::mojom::Camera3CaptureRequestPtr& request,
                              base::OnceCallback<void(int32_t)>& callback) {
     if (quit_) {
@@ -189,16 +155,17 @@
     }
     std::move(callback).Run(0);
     mock_callback_ops_->Notify(PrepareShutterNotifyMessage(
-        request->frame_number, base::TimeTicks::Now().ToInternalValue()));
+        request->frame_number,
+        (base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds()));
     mock_callback_ops_->ProcessCaptureResult(PrepareCapturedResult(
         request->frame_number, cros::mojom::CameraMetadata::New(), 1,
         std::move(request->output_buffers)));
   }
 
   MockStreamCaptureInterface* GetMockCaptureInterface() {
-    EXPECT_NE(nullptr, stream_buffer_manager_.get());
+    EXPECT_NE(nullptr, request_manager_.get());
     return reinterpret_cast<MockStreamCaptureInterface*>(
-        stream_buffer_manager_->capture_interface_.get());
+        request_manager_->capture_interface_.get());
   }
 
   unittest_internal::MockVideoCaptureClient* GetMockVideoCaptureClient() {
@@ -207,9 +174,9 @@
         device_context_->client_.get());
   }
 
-  std::map<uint32_t, StreamBufferManager::CaptureResult>& GetPendingResults() {
-    EXPECT_NE(nullptr, stream_buffer_manager_.get());
-    return stream_buffer_manager_->pending_results_;
+  std::map<uint32_t, RequestManager::CaptureResult>& GetPendingResults() {
+    EXPECT_NE(nullptr, request_manager_.get());
+    return request_manager_->pending_results_;
   }
 
   std::vector<cros::mojom::Camera3StreamPtr> PrepareCaptureStream(
@@ -293,7 +260,7 @@
   }
 
  protected:
-  std::unique_ptr<StreamBufferManager> stream_buffer_manager_;
+  std::unique_ptr<RequestManager> request_manager_;
   cros::mojom::Camera3CallbackOpsPtr mock_callback_ops_;
   std::unique_ptr<CameraDeviceContext> device_context_;
   cros::mojom::Camera3StreamPtr stream;
@@ -305,35 +272,28 @@
 };
 
 // A basic sanity test to capture one frame with the capture loop.
-TEST_F(StreamBufferManagerTest, SimpleCaptureTest) {
+TEST_F(RequestManagerTest, SimpleCaptureTest) {
   GetMockVideoCaptureClient()->SetFrameCb(base::BindOnce(
-      &StreamBufferManagerTest::QuitCaptureLoop, base::Unretained(this)));
-  EXPECT_CALL(
-      *GetMockCaptureInterface(),
-      DoRegisterBuffer(
-          StreamBufferManager::GetBufferIpcId(StreamType::kPreview, 0),
-          cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _, _, _, _, _, _))
-      .Times(AtLeast(1))
-      .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer));
+      &RequestManagerTest::QuitCaptureLoop, base::Unretained(this)));
   EXPECT_CALL(*GetMockCaptureInterface(), DoProcessCaptureRequest(_, _))
-      .Times(1)
-      .WillOnce(Invoke(this, &StreamBufferManagerTest::ProcessCaptureRequest));
+      .Times(AtLeast(1))
+      .WillOnce(Invoke(this, &RequestManagerTest::ProcessCaptureRequest));
 
-  stream_buffer_manager_->SetUpStreamsAndBuffers(
+  request_manager_->SetUpStreamsAndBuffers(
       kDefaultCaptureFormat,
       GetFakeStaticMetadata(/* partial_result_count */ 1),
       PrepareCaptureStream(/* max_buffers */ 1));
-  stream_buffer_manager_->StartPreview(cros::mojom::CameraMetadata::New());
+  request_manager_->StartPreview(cros::mojom::CameraMetadata::New());
 
   // Wait until a captured frame is received by MockVideoCaptureClient.
   DoLoop();
 }
 
-// Test that the StreamBufferManager submits a captured result only after all
+// Test that the RequestManager submits a captured result only after all
 // partial metadata are received.
-TEST_F(StreamBufferManagerTest, PartialResultTest) {
+TEST_F(RequestManagerTest, PartialResultTest) {
   GetMockVideoCaptureClient()->SetFrameCb(base::BindOnce(
-      [](StreamBufferManagerTest* test) {
+      [](RequestManagerTest* test) {
         EXPECT_EQ(1u, test->GetPendingResults().size());
         // Make sure all the three partial metadata are received before the
         // captured result is submitted.
@@ -342,20 +302,14 @@
         test->QuitCaptureLoop();
       },
       base::Unretained(this)));
-  EXPECT_CALL(
-      *GetMockCaptureInterface(),
-      DoRegisterBuffer(
-          StreamBufferManager::GetBufferIpcId(StreamType::kPreview, 0),
-          cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _, _, _, _, _, _))
-      .Times(AtLeast(1))
-      .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer));
   EXPECT_CALL(*GetMockCaptureInterface(), DoProcessCaptureRequest(_, _))
-      .Times(1)
+      .Times(AtLeast(1))
       .WillOnce(Invoke([this](cros::mojom::Camera3CaptureRequestPtr& request,
                               base::OnceCallback<void(int32_t)>& callback) {
         std::move(callback).Run(0);
         mock_callback_ops_->Notify(PrepareShutterNotifyMessage(
-            request->frame_number, base::TimeTicks::Now().ToInternalValue()));
+            request->frame_number,
+            (base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds()));
         mock_callback_ops_->ProcessCaptureResult(PrepareCapturedResult(
             request->frame_number, cros::mojom::CameraMetadata::New(), 1,
             std::move(request->output_buffers)));
@@ -367,11 +321,11 @@
             std::vector<cros::mojom::Camera3StreamBufferPtr>()));
       }));
 
-  stream_buffer_manager_->SetUpStreamsAndBuffers(
+  request_manager_->SetUpStreamsAndBuffers(
       kDefaultCaptureFormat,
       GetFakeStaticMetadata(/* partial_result_count */ 3),
       PrepareCaptureStream(/* max_buffers */ 1));
-  stream_buffer_manager_->StartPreview(cros::mojom::CameraMetadata::New());
+  request_manager_->StartPreview(cros::mojom::CameraMetadata::New());
 
   // Wait until a captured frame is received by MockVideoCaptureClient.
   DoLoop();
@@ -379,24 +333,16 @@
 
 // Test that the capture loop is stopped and no frame is submitted when a device
 // error happens.
-TEST_F(StreamBufferManagerTest, DeviceErrorTest) {
+TEST_F(RequestManagerTest, DeviceErrorTest) {
   GetMockVideoCaptureClient()->SetFrameCb(base::BindOnce(
-      [](StreamBufferManagerTest* test) {
+      [](RequestManagerTest* test) {
         ADD_FAILURE() << "No frame should be submitted";
         test->QuitCaptureLoop();
       },
       base::Unretained(this)));
   EXPECT_CALL(*GetMockVideoCaptureClient(), OnError(_, _, _))
       .Times(1)
-      .WillOnce(
-          InvokeWithoutArgs(this, &StreamBufferManagerTest::QuitCaptureLoop));
-  EXPECT_CALL(
-      *GetMockCaptureInterface(),
-      DoRegisterBuffer(
-          StreamBufferManager::GetBufferIpcId(StreamType::kPreview, 0),
-          cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _, _, _, _, _, _))
-      .Times(1)
-      .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer));
+      .WillOnce(InvokeWithoutArgs(this, &RequestManagerTest::QuitCaptureLoop));
   EXPECT_CALL(*GetMockCaptureInterface(), DoProcessCaptureRequest(_, _))
       .Times(1)
       .WillOnce(Invoke([this](cros::mojom::Camera3CaptureRequestPtr& request,
@@ -407,11 +353,11 @@
             cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_DEVICE));
       }));
 
-  stream_buffer_manager_->SetUpStreamsAndBuffers(
+  request_manager_->SetUpStreamsAndBuffers(
       kDefaultCaptureFormat,
       GetFakeStaticMetadata(/* partial_result_count */ 1),
       PrepareCaptureStream(/* max_buffers */ 1));
-  stream_buffer_manager_->StartPreview(cros::mojom::CameraMetadata::New());
+  request_manager_->StartPreview(cros::mojom::CameraMetadata::New());
 
   // Wait until the MockVideoCaptureClient is deleted.
   DoLoop();
@@ -419,9 +365,9 @@
 
 // Test that upon request error the erroneous frame is dropped, and the capture
 // loop continues.
-TEST_F(StreamBufferManagerTest, RequestErrorTest) {
+TEST_F(RequestManagerTest, RequestErrorTest) {
   GetMockVideoCaptureClient()->SetFrameCb(base::BindOnce(
-      [](StreamBufferManagerTest* test) {
+      [](RequestManagerTest* test) {
         // Frame 0 should be dropped, and the frame callback should be called
         // with frame 1.
         EXPECT_EQ(test->GetPendingResults().end(),
@@ -431,16 +377,8 @@
         test->QuitCaptureLoop();
       },
       base::Unretained(this)));
-  EXPECT_CALL(
-      *GetMockCaptureInterface(),
-      DoRegisterBuffer(
-          StreamBufferManager::GetBufferIpcId(StreamType::kPreview, 0),
-          cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _, _, _, _, _, _))
-      .Times(AtLeast(2))
-      .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer))
-      .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer));
   EXPECT_CALL(*GetMockCaptureInterface(), DoProcessCaptureRequest(_, _))
-      .Times(2)
+      .Times(AtLeast(2))
       .WillOnce(Invoke([this](cros::mojom::Camera3CaptureRequestPtr& request,
                               base::OnceCallback<void(int32_t)>& callback) {
         std::move(callback).Run(0);
@@ -453,13 +391,13 @@
             request->frame_number, cros::mojom::CameraMetadata::New(), 1,
             std::move(request->output_buffers)));
       }))
-      .WillOnce(Invoke(this, &StreamBufferManagerTest::ProcessCaptureRequest));
+      .WillOnce(Invoke(this, &RequestManagerTest::ProcessCaptureRequest));
 
-  stream_buffer_manager_->SetUpStreamsAndBuffers(
+  request_manager_->SetUpStreamsAndBuffers(
       kDefaultCaptureFormat,
       GetFakeStaticMetadata(/* partial_result_count */ 1),
       PrepareCaptureStream(/* max_buffers */ 1));
-  stream_buffer_manager_->StartPreview(cros::mojom::CameraMetadata::New());
+  request_manager_->StartPreview(cros::mojom::CameraMetadata::New());
 
   // Wait until the MockVideoCaptureClient is deleted.
   DoLoop();
@@ -467,46 +405,40 @@
 
 // Test that upon result error the captured buffer is submitted despite of the
 // missing result metadata, and the capture loop continues.
-TEST_F(StreamBufferManagerTest, ResultErrorTest) {
+TEST_F(RequestManagerTest, ResultErrorTest) {
   GetMockVideoCaptureClient()->SetFrameCb(base::BindOnce(
-      [](StreamBufferManagerTest* test) {
+      [](RequestManagerTest* test) {
         // Frame 0 should be submitted.
         EXPECT_NE(test->GetPendingResults().end(),
                   test->GetPendingResults().find(0));
         test->QuitCaptureLoop();
       },
       base::Unretained(this)));
-  EXPECT_CALL(
-      *GetMockCaptureInterface(),
-      DoRegisterBuffer(
-          StreamBufferManager::GetBufferIpcId(StreamType::kPreview, 0),
-          cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _, _, _, _, _, _))
-      .Times(AtLeast(1))
-      .WillRepeatedly(Invoke(this, &StreamBufferManagerTest::RegisterBuffer));
   EXPECT_CALL(*GetMockCaptureInterface(), DoProcessCaptureRequest(_, _))
       .Times(AtLeast(1))
       .WillOnce(Invoke([this](cros::mojom::Camera3CaptureRequestPtr& request,
                               base::OnceCallback<void(int32_t)>& callback) {
         std::move(callback).Run(0);
         mock_callback_ops_->Notify(PrepareShutterNotifyMessage(
-            request->frame_number, base::TimeTicks::Now().ToInternalValue()));
+            request->frame_number,
+            (base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds()));
         mock_callback_ops_->ProcessCaptureResult(PrepareCapturedResult(
             request->frame_number, cros::mojom::CameraMetadata::New(), 1,
             std::move(request->output_buffers)));
         // Send a result error notify without sending the second partial result.
-        // StreamBufferManager should submit the buffer when it receives the
+        // RequestManager should submit the buffer when it receives the
         // result error.
         mock_callback_ops_->Notify(PrepareErrorNotifyMessage(
             request->frame_number,
             cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_RESULT));
       }))
-      .WillOnce(Invoke(this, &StreamBufferManagerTest::ProcessCaptureRequest));
+      .WillOnce(Invoke(this, &RequestManagerTest::ProcessCaptureRequest));
 
-  stream_buffer_manager_->SetUpStreamsAndBuffers(
+  request_manager_->SetUpStreamsAndBuffers(
       kDefaultCaptureFormat,
       GetFakeStaticMetadata(/* partial_result_count */ 2),
       PrepareCaptureStream(/* max_buffers */ 1));
-  stream_buffer_manager_->StartPreview(cros::mojom::CameraMetadata::New());
+  request_manager_->StartPreview(cros::mojom::CameraMetadata::New());
 
   // Wait until the MockVideoCaptureClient is deleted.
   DoLoop();
@@ -514,9 +446,9 @@
 
 // Test that upon buffer error the erroneous buffer is dropped, and the capture
 // loop continues.
-TEST_F(StreamBufferManagerTest, BufferErrorTest) {
+TEST_F(RequestManagerTest, BufferErrorTest) {
   GetMockVideoCaptureClient()->SetFrameCb(base::BindOnce(
-      [](StreamBufferManagerTest* test) {
+      [](RequestManagerTest* test) {
         // Frame 0 should be dropped, and the frame callback should be called
         // with frame 1.
         EXPECT_EQ(test->GetPendingResults().end(),
@@ -526,21 +458,14 @@
         test->QuitCaptureLoop();
       },
       base::Unretained(this)));
-  EXPECT_CALL(
-      *GetMockCaptureInterface(),
-      DoRegisterBuffer(
-          StreamBufferManager::GetBufferIpcId(StreamType::kPreview, 0),
-          cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _, _, _, _, _, _))
-      .Times(AtLeast(2))
-      .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer))
-      .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer));
   EXPECT_CALL(*GetMockCaptureInterface(), DoProcessCaptureRequest(_, _))
-      .Times(2)
+      .Times(AtLeast(2))
       .WillOnce(Invoke([this](cros::mojom::Camera3CaptureRequestPtr& request,
                               base::OnceCallback<void(int32_t)>& callback) {
         std::move(callback).Run(0);
         mock_callback_ops_->Notify(PrepareShutterNotifyMessage(
-            request->frame_number, base::TimeTicks::Now().ToInternalValue()));
+            request->frame_number,
+            (base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds()));
         mock_callback_ops_->Notify(PrepareErrorNotifyMessage(
             request->frame_number,
             cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_BUFFER));
@@ -550,47 +475,32 @@
             request->frame_number, cros::mojom::CameraMetadata::New(), 1,
             std::move(request->output_buffers)));
       }))
-      .WillOnce(Invoke(this, &StreamBufferManagerTest::ProcessCaptureRequest));
+      .WillOnce(Invoke(this, &RequestManagerTest::ProcessCaptureRequest));
 
-  stream_buffer_manager_->SetUpStreamsAndBuffers(
+  request_manager_->SetUpStreamsAndBuffers(
       kDefaultCaptureFormat,
       GetFakeStaticMetadata(/* partial_result_count */ 1),
       PrepareCaptureStream(/* max_buffers */ 1));
-  stream_buffer_manager_->StartPreview(cros::mojom::CameraMetadata::New());
+  request_manager_->StartPreview(cros::mojom::CameraMetadata::New());
 
   // Wait until the MockVideoCaptureClient is deleted.
   DoLoop();
 }
 
 // Test that preview and still capture buffers can be correctly submitted.
-TEST_F(StreamBufferManagerTest, TakePhotoTest) {
-  EXPECT_CALL(
-      *GetMockCaptureInterface(),
-      DoRegisterBuffer(
-          StreamBufferManager::GetBufferIpcId(StreamType::kPreview, 0),
-          cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _, _, _, _, _, _))
-      .Times(AtLeast(1))
-      .WillRepeatedly(Invoke(this, &StreamBufferManagerTest::RegisterBuffer));
-  EXPECT_CALL(
-      *GetMockCaptureInterface(),
-      DoRegisterBuffer(
-          StreamBufferManager::GetBufferIpcId(StreamType::kStillCapture, 0),
-          cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, _, _, _, _, _, _))
-      .Times(1)
-      .WillOnce(Invoke(this, &StreamBufferManagerTest::RegisterBuffer));
+TEST_F(RequestManagerTest, TakePhotoTest) {
   EXPECT_CALL(*GetMockCaptureInterface(), DoProcessCaptureRequest(_, _))
       .Times(AtLeast(1))
-      .WillRepeatedly(
-          Invoke(this, &StreamBufferManagerTest::ProcessCaptureRequest));
+      .WillRepeatedly(Invoke(this, &RequestManagerTest::ProcessCaptureRequest));
 
-  stream_buffer_manager_->SetUpStreamsAndBuffers(
+  request_manager_->SetUpStreamsAndBuffers(
       kDefaultCaptureFormat,
       GetFakeStaticMetadata(/* partial_result_count */ 1),
       PrepareCaptureStream(/* max_buffers */ 1));
-  stream_buffer_manager_->StartPreview(cros::mojom::CameraMetadata::New());
-  stream_buffer_manager_->TakePhoto(
+  request_manager_->StartPreview(cros::mojom::CameraMetadata::New());
+  request_manager_->TakePhoto(
       GetFakeStaticMetadata(/* partial_result_count */ 1),
-      base::BindOnce([](StreamBufferManagerTest* test,
+      base::BindOnce([](RequestManagerTest* test,
                         mojom::BlobPtr blob) { test->QuitCaptureLoop(); },
                      base::Unretained(this)));
 
diff --git a/media/capture/video/chromeos/stream_buffer_manager.cc b/media/capture/video/chromeos/stream_buffer_manager.cc
index 29d0809..4420ec9 100644
--- a/media/capture/video/chromeos/stream_buffer_manager.cc
+++ b/media/capture/video/chromeos/stream_buffer_manager.cc
@@ -4,8 +4,8 @@
 
 #include "media/capture/video/chromeos/stream_buffer_manager.h"
 
-#include <sync/sync.h>
 #include <memory>
+#include <string>
 
 #include "base/bind.h"
 #include "base/posix/safe_strerror.h"
@@ -13,59 +13,20 @@
 #include "media/capture/video/chromeos/camera_buffer_factory.h"
 #include "media/capture/video/chromeos/camera_device_context.h"
 #include "media/capture/video/chromeos/camera_metadata_utils.h"
+#include "media/capture/video/chromeos/request_builder.h"
 #include "mojo/public/cpp/platform/platform_handle.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 
 namespace media {
 
-namespace {
-
-size_t GetBufferIndex(uint64_t buffer_id) {
-  return buffer_id & 0xFFFFFFFF;
-}
-
-StreamType StreamIdToStreamType(uint64_t stream_id) {
-  switch (stream_id) {
-    case 0:
-      return StreamType::kPreview;
-    case 1:
-      return StreamType::kStillCapture;
-    default:
-      return StreamType::kUnknown;
-  }
-}
-
-}  // namespace
-
 StreamBufferManager::StreamBufferManager(
-    cros::mojom::Camera3CallbackOpsRequest callback_ops_request,
-    std::unique_ptr<StreamCaptureInterface> capture_interface,
     CameraDeviceContext* device_context,
-    std::unique_ptr<CameraBufferFactory> camera_buffer_factory,
-    base::RepeatingCallback<
-        mojom::BlobPtr(const uint8_t* buffer,
-                       const uint32_t bytesused,
-                       const VideoCaptureFormat& capture_format,
-                       int screen_rotation)> blobify_callback,
-    scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner)
-    : callback_ops_(this, std::move(callback_ops_request)),
-      capture_interface_(std::move(capture_interface)),
-      device_context_(device_context),
+    std::unique_ptr<CameraBufferFactory> camera_buffer_factory)
+    : device_context_(device_context),
       camera_buffer_factory_(std::move(camera_buffer_factory)),
-      blobify_callback_(std::move(blobify_callback)),
-      ipc_task_runner_(std::move(ipc_task_runner)),
-      capturing_(false),
-      frame_number_(0),
-      partial_result_count_(1),
-      first_frame_shutter_time_(base::TimeTicks()),
-      weak_ptr_factory_(this) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(callback_ops_.is_bound());
-  DCHECK(device_context_);
-}
+      weak_ptr_factory_(this) {}
 
 StreamBufferManager::~StreamBufferManager() {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
   for (const auto& iter : stream_context_) {
     if (iter.second) {
       for (const auto& buf : iter.second->buffers) {
@@ -77,24 +38,39 @@
   }
 }
 
+gfx::GpuMemoryBuffer* StreamBufferManager::GetBufferById(StreamType stream_type,
+                                                         uint64_t buffer_id) {
+  size_t buffer_index = GetBufferIndex(buffer_id);
+  if (buffer_index >= stream_context_[stream_type]->buffers.size()) {
+    LOG(ERROR) << "Invalid buffer index: " << buffer_index
+               << " for stream: " << stream_type;
+    return nullptr;
+  }
+  return stream_context_[stream_type]->buffers[buffer_index].get();
+}
+
+VideoCaptureFormat StreamBufferManager::GetStreamCaptureFormat(
+    StreamType stream_type) {
+  return stream_context_[stream_type]->capture_format;
+}
+
+bool StreamBufferManager::HasFreeBuffers(
+    const std::set<StreamType>& stream_types) {
+  for (auto stream_type : stream_types) {
+    if (stream_context_[stream_type]->free_buffers.empty()) {
+      return false;
+    }
+  }
+  return true;
+}
+
 void StreamBufferManager::SetUpStreamsAndBuffers(
     VideoCaptureFormat capture_format,
     const cros::mojom::CameraMetadataPtr& static_metadata,
     std::vector<cros::mojom::Camera3StreamPtr> streams) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-
-  // The partial result count metadata is optional; defaults to 1 in case it
-  // is not set in the static metadata.
-  const cros::mojom::CameraMetadataEntryPtr* partial_count = GetMetadataEntry(
-      static_metadata,
-      cros::mojom::CameraMetadataTag::ANDROID_REQUEST_PARTIAL_RESULT_COUNT);
-  if (partial_count) {
-    partial_result_count_ =
-        *reinterpret_cast<int32_t*>((*partial_count)->data.data());
-  }
-
   for (auto& stream : streams) {
     DVLOG(2) << "Stream " << stream->id
+             << " stream_type: " << stream->stream_type
              << " configured: usage=" << stream->usage
              << " max_buffers=" << stream->max_buffers;
 
@@ -113,14 +89,7 @@
 
     // A better way to tell the stream type here would be to check on the usage
     // flags of the stream.
-    StreamType stream_type;
-    if (stream->format ==
-        cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_YCbCr_420_888) {
-      stream_type = StreamType::kPreview;
-    } else {  // stream->format ==
-              // cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB
-      stream_type = StreamType::kStillCapture;
-    }
+    StreamType stream_type = StreamIdToStreamType(stream->id);
     stream_context_[stream_type] = std::make_unique<StreamContext>();
     stream_context_[stream_type]->capture_format = capture_format;
     stream_context_[stream_type]->stream = std::move(stream);
@@ -135,16 +104,25 @@
     size_t num_buffers = stream_context_[stream_type]->stream->max_buffers;
     stream_context_[stream_type]->buffers.resize(num_buffers);
     int32_t buffer_width, buffer_height;
-    if (stream_type == StreamType::kPreview) {
-      buffer_width = stream_context_[stream_type]->stream->width;
-      buffer_height = stream_context_[stream_type]->stream->height;
-    } else {  // StreamType::kStillCapture
-      const cros::mojom::CameraMetadataEntryPtr* jpeg_max_size =
-          GetMetadataEntry(
-              static_metadata,
-              cros::mojom::CameraMetadataTag::ANDROID_JPEG_MAX_SIZE);
-      buffer_width = *reinterpret_cast<int32_t*>((*jpeg_max_size)->data.data());
-      buffer_height = 1;
+    switch (stream_type) {
+      case StreamType::kPreview: {
+        buffer_width = stream_context_[stream_type]->stream->width;
+        buffer_height = stream_context_[stream_type]->stream->height;
+        break;
+      }
+      case StreamType::kStillCapture: {
+        const cros::mojom::CameraMetadataEntryPtr* jpeg_max_size =
+            GetMetadataEntry(
+                static_metadata,
+                cros::mojom::CameraMetadataTag::ANDROID_JPEG_MAX_SIZE);
+        buffer_width =
+            *reinterpret_cast<int32_t*>((*jpeg_max_size)->data.data());
+        buffer_height = 1;
+        break;
+      }
+      default: {
+        NOTREACHED();
+      }
     }
     for (size_t j = 0; j < num_buffers; ++j) {
       auto buffer = camera_buffer_factory_->CreateGpuMemoryBuffer(
@@ -173,30 +151,6 @@
   }
 }
 
-void StreamBufferManager::StartPreview(
-    cros::mojom::CameraMetadataPtr preview_settings) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(stream_context_[StreamType::kPreview]);
-  DCHECK(repeating_request_settings_.is_null());
-
-  capturing_ = true;
-  repeating_request_settings_ = std::move(preview_settings);
-  // We cannot use a loop to register all the free buffers in one shot here
-  // because the camera HAL v3 API specifies that the client cannot call
-  // ProcessCaptureRequest before the previous one returns.
-  RegisterBuffer(StreamType::kPreview);
-}
-
-void StreamBufferManager::StopPreview(
-    base::OnceCallback<void(int32_t)> callback) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  capturing_ = false;
-  repeating_request_settings_.reset();
-  if (callback) {
-    capture_interface_->Flush(std::move(callback));
-  }
-}
-
 cros::mojom::Camera3StreamPtr StreamBufferManager::GetStreamConfiguration(
     StreamType stream_type) {
   if (!stream_context_.count(stream_type)) {
@@ -205,92 +159,39 @@
   return stream_context_[stream_type]->stream.Clone();
 }
 
-void StreamBufferManager::TakePhoto(
-    cros::mojom::CameraMetadataPtr settings,
-    VideoCaptureDevice::TakePhotoCallback callback) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(stream_context_[StreamType::kStillCapture]);
-
-  still_capture_callbacks_yet_to_be_processed_.push(std::move(callback));
-
-  std::vector<uint8_t> frame_orientation(sizeof(int32_t));
-  *reinterpret_cast<int32_t*>(frame_orientation.data()) =
-      base::checked_cast<int32_t>(device_context_->GetCameraFrameOrientation());
-  cros::mojom::CameraMetadataEntryPtr e =
-      cros::mojom::CameraMetadataEntry::New();
-  e->tag = cros::mojom::CameraMetadataTag::ANDROID_JPEG_ORIENTATION;
-  e->type = cros::mojom::EntryType::TYPE_INT32;
-  e->count = 1;
-  e->data = std::move(frame_orientation);
-  AddOrUpdateMetadataEntry(&settings, std::move(e));
-
-  oneshot_request_settings_.push(std::move(settings));
-  RegisterBuffer(StreamType::kStillCapture);
-}
-
-size_t StreamBufferManager::GetStreamNumber() {
-  return stream_context_.size();
-}
-
-void StreamBufferManager::AddResultMetadataObserver(
-    ResultMetadataObserver* observer) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(!result_metadata_observers_.count(observer));
-
-  result_metadata_observers_.insert(observer);
-}
-
-void StreamBufferManager::RemoveResultMetadataObserver(
-    ResultMetadataObserver* observer) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(result_metadata_observers_.count(observer));
-
-  result_metadata_observers_.erase(observer);
-}
-
-void StreamBufferManager::SetCaptureMetadata(cros::mojom::CameraMetadataTag tag,
-                                             cros::mojom::EntryType type,
-                                             size_t count,
-                                             std::vector<uint8_t> value) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-
-  cros::mojom::CameraMetadataEntryPtr setting =
-      cros::mojom::CameraMetadataEntry::New();
-
-  setting->tag = tag;
-  setting->type = type;
-  setting->count = count;
-  setting->data = std::move(value);
-
-  capture_settings_override_.push_back(std::move(setting));
-}
-
-void StreamBufferManager::SetRepeatingCaptureMetadata(
-    cros::mojom::CameraMetadataTag tag,
-    cros::mojom::EntryType type,
-    size_t count,
-    std::vector<uint8_t> value) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  cros::mojom::CameraMetadataEntryPtr setting =
-      cros::mojom::CameraMetadataEntry::New();
-
-  setting->tag = tag;
-  setting->type = type;
-  setting->count = count;
-  setting->data = std::move(value);
-
-  capture_settings_repeating_override_[tag] = std::move(setting);
-}
-
-void StreamBufferManager::UnsetRepeatingCaptureMetadata(
-    cros::mojom::CameraMetadataTag tag) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  auto it = capture_settings_repeating_override_.find(tag);
-  if (it == capture_settings_repeating_override_.end()) {
-    LOG(ERROR) << "Unset a non-existent metadata: " << tag;
-    return;
+base::Optional<BufferInfo> StreamBufferManager::RequestBuffer(
+    StreamType stream_type) {
+  VideoPixelFormat buffer_format =
+      stream_context_[stream_type]->capture_format.pixel_format;
+  uint32_t drm_format = PixFormatVideoToDrm(buffer_format);
+  if (!drm_format) {
+    device_context_->SetErrorState(
+        media::VideoCaptureError::
+            kCrosHalV3BufferManagerUnsupportedVideoPixelFormat,
+        FROM_HERE,
+        std::string("Unsupported video pixel format") +
+            VideoPixelFormatToString(buffer_format));
+    return {};
   }
-  capture_settings_repeating_override_.erase(it);
+
+  BufferInfo buffer_info;
+  buffer_info.id = stream_context_[stream_type]->free_buffers.front();
+  stream_context_[stream_type]->free_buffers.pop();
+  buffer_info.gpu_memory_buffer = stream_context_[stream_type]
+                                      ->buffers[GetBufferIndex(buffer_info.id)]
+                                      .get();
+  buffer_info.hal_pixel_format = stream_context_[stream_type]->stream->format;
+  buffer_info.drm_format = drm_format;
+  return buffer_info;
+}
+
+void StreamBufferManager::ReleaseBuffer(StreamType stream_type,
+                                        uint64_t buffer_id) {
+  stream_context_[stream_type]->free_buffers.push(buffer_id);
+}
+
+size_t StreamBufferManager::GetNumberOfStreams() {
+  return stream_context_.size();
 }
 
 // static
@@ -302,556 +203,13 @@
   return id;
 }
 
-void StreamBufferManager::ApplyCaptureSettings(
-    cros::mojom::CameraMetadataPtr* capture_settings) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-
-  if (capture_settings_override_.empty() &&
-      capture_settings_repeating_override_.empty()) {
-    return;
-  }
-
-  for (const auto& setting : capture_settings_repeating_override_) {
-    AddOrUpdateMetadataEntry(capture_settings, setting.second.Clone());
-  }
-
-  for (auto& s : capture_settings_override_) {
-    AddOrUpdateMetadataEntry(capture_settings, std::move(s));
-  }
-  capture_settings_override_.clear();
-  SortCameraMetadata(capture_settings);
-}
-
-void StreamBufferManager::RegisterBuffer(StreamType stream_type) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(stream_context_[stream_type]);
-
-  if (!capturing_) {
-    return;
-  }
-
-  if (stream_context_[stream_type]->free_buffers.empty()) {
-    return;
-  }
-
-  uint64_t buffer_id = stream_context_[stream_type]->free_buffers.front();
-  stream_context_[stream_type]->free_buffers.pop();
-  const gfx::GpuMemoryBuffer* buffer =
-      stream_context_[stream_type]->buffers[GetBufferIndex(buffer_id)].get();
-
-  VideoPixelFormat buffer_format =
-      stream_context_[stream_type]->capture_format.pixel_format;
-  uint32_t drm_format = PixFormatVideoToDrm(buffer_format);
-  if (!drm_format) {
-    device_context_->SetErrorState(
-        media::VideoCaptureError::
-            kCrosHalV3BufferManagerUnsupportedVideoPixelFormat,
-        FROM_HERE,
-        std::string("Unsupported video pixel format") +
-            VideoPixelFormatToString(buffer_format));
-    return;
-  }
-  cros::mojom::HalPixelFormat hal_pixel_format =
-      stream_context_[stream_type]->stream->format;
-
-  gfx::NativePixmapHandle buffer_handle =
-      buffer->CloneHandle().native_pixmap_handle;
-
-  size_t num_planes = buffer_handle.planes.size();
-  DCHECK_EQ(num_planes, buffer_handle.fds.size());
-  // Take ownership of fds.
-  std::vector<base::ScopedFD> fds(num_planes);
-  for (size_t i = 0; i < num_planes; ++i)
-    fds[i] = base::ScopedFD(buffer_handle.fds[i].fd);
-
-  std::vector<StreamCaptureInterface::Plane> planes(num_planes);
-  for (size_t i = 0; i < num_planes; ++i) {
-    planes[i].fd =
-        mojo::WrapPlatformHandle(mojo::PlatformHandle(std::move(fds[i])));
-    if (!planes[i].fd.is_valid()) {
-      device_context_->SetErrorState(
-          media::VideoCaptureError::
-              kCrosHalV3BufferManagerFailedToWrapGpuMemoryHandle,
-          FROM_HERE, "Failed to wrap gpu memory handle");
-      return;
-    }
-    planes[i].stride = buffer_handle.planes[i].stride;
-    planes[i].offset = buffer_handle.planes[i].offset;
-  }
-  if (stream_type == StreamType::kStillCapture) {
-    still_capture_callbacks_currently_processing_.push(
-        std::move(still_capture_callbacks_yet_to_be_processed_.front()));
-    still_capture_callbacks_yet_to_be_processed_.pop();
-  }
-  // We reuse BufferType::GRALLOC here since on ARC++ we are using DMA-buf-based
-  // gralloc buffers.
-  capture_interface_->RegisterBuffer(
-      buffer_id, cros::mojom::Camera3DeviceOps::BufferType::GRALLOC, drm_format,
-      hal_pixel_format, buffer->GetSize().width(), buffer->GetSize().height(),
-      std::move(planes),
-      base::BindOnce(&StreamBufferManager::OnRegisteredBuffer,
-                     weak_ptr_factory_.GetWeakPtr(), stream_type, buffer_id));
-  DVLOG(2) << "Registered buffer " << buffer_id;
-}
-
-void StreamBufferManager::OnRegisteredBuffer(StreamType stream_type,
-                                             uint64_t buffer_id,
-                                             int32_t result) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(stream_context_[stream_type]);
-
-  if (!capturing_) {
-    return;
-  }
-  if (result) {
-    device_context_->SetErrorState(
-        media::VideoCaptureError::kCrosHalV3BufferManagerFailedToRegisterBuffer,
-        FROM_HERE,
-        std::string("Failed to register buffer: ") +
-            base::safe_strerror(-result));
-    return;
-  }
-  stream_context_[stream_type]->registered_buffers.push(buffer_id);
-  ProcessCaptureRequest();
-}
-
-void StreamBufferManager::ProcessCaptureRequest() {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(stream_context_[StreamType::kPreview]);
-
-  cros::mojom::Camera3CaptureRequestPtr request =
-      cros::mojom::Camera3CaptureRequest::New();
-  request->frame_number = frame_number_;
-
-  CaptureResult& pending_result = pending_results_[frame_number_];
-
-  if (!stream_context_[StreamType::kPreview]->registered_buffers.empty()) {
-    cros::mojom::Camera3StreamBufferPtr buffer =
-        cros::mojom::Camera3StreamBuffer::New();
-    buffer->stream_id = static_cast<uint64_t>(StreamType::kPreview);
-    buffer->buffer_id =
-        stream_context_[StreamType::kPreview]->registered_buffers.front();
-    stream_context_[StreamType::kPreview]->registered_buffers.pop();
-    buffer->status = cros::mojom::Camera3BufferStatus::CAMERA3_BUFFER_STATUS_OK;
-
-    DVLOG(2) << "Requested capture for stream " << StreamType::kPreview
-             << " in frame " << frame_number_;
-    request->settings = repeating_request_settings_.Clone();
-    request->output_buffers.push_back(std::move(buffer));
-  }
-
-  if (stream_context_.count(StreamType::kStillCapture) &&
-      !stream_context_[StreamType::kStillCapture]->registered_buffers.empty()) {
-    DCHECK(!still_capture_callbacks_currently_processing_.empty());
-    cros::mojom::Camera3StreamBufferPtr buffer =
-        cros::mojom::Camera3StreamBuffer::New();
-    buffer->stream_id = static_cast<uint64_t>(StreamType::kStillCapture);
-    buffer->buffer_id =
-        stream_context_[StreamType::kStillCapture]->registered_buffers.front();
-    stream_context_[StreamType::kStillCapture]->registered_buffers.pop();
-    buffer->status = cros::mojom::Camera3BufferStatus::CAMERA3_BUFFER_STATUS_OK;
-
-    DVLOG(2) << "Requested capture for stream " << StreamType::kStillCapture
-             << " in frame " << frame_number_;
-    // Use the still capture settings and override the preview ones.
-    request->settings = std::move(oneshot_request_settings_.front());
-    oneshot_request_settings_.pop();
-    pending_result.still_capture_callback =
-        std::move(still_capture_callbacks_currently_processing_.front());
-    still_capture_callbacks_currently_processing_.pop();
-    request->output_buffers.push_back(std::move(buffer));
-  }
-
-  pending_result.unsubmitted_buffer_count = request->output_buffers.size();
-
-  ApplyCaptureSettings(&request->settings);
-  capture_interface_->ProcessCaptureRequest(
-      std::move(request),
-      base::BindOnce(&StreamBufferManager::OnProcessedCaptureRequest,
-                     weak_ptr_factory_.GetWeakPtr()));
-  frame_number_++;
-}
-
-void StreamBufferManager::OnProcessedCaptureRequest(int32_t result) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-
-  if (!capturing_) {
-    return;
-  }
-  if (result) {
-    device_context_->SetErrorState(
-        media::VideoCaptureError::
-            kCrosHalV3BufferManagerProcessCaptureRequestFailed,
-        FROM_HERE,
-        std::string("Process capture request failed: ") +
-            base::safe_strerror(-result));
-    return;
-  }
-  // Keeps the preview stream going.
-  RegisterBuffer(StreamType::kPreview);
-}
-
-void StreamBufferManager::ProcessCaptureResult(
-    cros::mojom::Camera3CaptureResultPtr result) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-
-  if (!capturing_) {
-    return;
-  }
-  uint32_t frame_number = result->frame_number;
-  // A new partial result may be created in either ProcessCaptureResult or
-  // Notify.
-  CaptureResult& pending_result = pending_results_[frame_number];
-
-  // |result->pending_result| is set to 0 if the capture result contains only
-  // the result buffer handles and no result metadata.
-  if (result->partial_result) {
-    uint32_t result_id = result->partial_result;
-    if (result_id > partial_result_count_) {
-      device_context_->SetErrorState(
-          media::VideoCaptureError::
-              kCrosHalV3BufferManagerInvalidPendingResultId,
-          FROM_HERE,
-          std::string("Invalid pending_result id: ") +
-              std::to_string(result_id));
-      return;
-    }
-    if (pending_result.partial_metadata_received.count(result_id)) {
-      device_context_->SetErrorState(
-          media::VideoCaptureError::
-              kCrosHalV3BufferManagerReceivedDuplicatedPartialMetadata,
-          FROM_HERE,
-          std::string("Received duplicated partial metadata: ") +
-              std::to_string(result_id));
-      return;
-    }
-    DVLOG(2) << "Received partial result " << result_id << " for frame "
-             << frame_number;
-    pending_result.partial_metadata_received.insert(result_id);
-    MergeMetadata(&pending_result.metadata, result->result);
-  }
-
-  if (result->output_buffers) {
-    if (result->output_buffers->size() > kMaxConfiguredStreams) {
-      device_context_->SetErrorState(
-          media::VideoCaptureError::
-              kCrosHalV3BufferManagerIncorrectNumberOfOutputBuffersReceived,
-          FROM_HERE,
-          std::string("Incorrect number of output buffers received: ") +
-              std::to_string(result->output_buffers->size()));
-      return;
-    }
-    for (auto& stream_buffer : result->output_buffers.value()) {
-      DVLOG(2) << "Received capture result for frame " << frame_number
-               << " stream_id: " << stream_buffer->stream_id;
-      StreamType stream_type = StreamIdToStreamType(stream_buffer->stream_id);
-      if (stream_type == StreamType::kUnknown) {
-        device_context_->SetErrorState(
-            media::VideoCaptureError::
-                kCrosHalV3BufferManagerInvalidTypeOfOutputBuffersReceived,
-            FROM_HERE,
-            std::string("Invalid type of output buffers received: ") +
-                std::to_string(stream_buffer->stream_id));
-        return;
-      }
-
-      // The camera HAL v3 API specifies that only one capture result can carry
-      // the result buffer for any given frame number.
-      if (stream_context_[stream_type]->capture_results_with_buffer.count(
-              frame_number)) {
-        device_context_->SetErrorState(
-            media::VideoCaptureError::
-                kCrosHalV3BufferManagerReceivedMultipleResultBuffersForFrame,
-            FROM_HERE,
-            std::string("Received multiple result buffers for frame ") +
-                std::to_string(frame_number) + std::string(" for stream ") +
-                std::to_string(stream_buffer->stream_id));
-        return;
-      }
-
-      pending_result.buffers[stream_type] = std::move(stream_buffer);
-      stream_context_[stream_type]->capture_results_with_buffer[frame_number] =
-          &pending_result;
-      if (pending_result.buffers[stream_type]->status ==
-          cros::mojom::Camera3BufferStatus::CAMERA3_BUFFER_STATUS_ERROR) {
-        // If the buffer is marked as error, its content is discarded for this
-        // frame.  Send the buffer to the free list directly through
-        // SubmitCaptureResult.
-        SubmitCaptureResult(frame_number, stream_type);
-      }
-    }
-  }
-
-  for (const auto& iter : stream_context_) {
-    TRACE_EVENT1("camera", "Capture Result", "frame_number", frame_number);
-    SubmitCaptureResultIfComplete(frame_number, iter.first);
-  }
-}
-
-void StreamBufferManager::Notify(cros::mojom::Camera3NotifyMsgPtr message) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-
-  if (!capturing_) {
-    return;
-  }
-  if (message->type == cros::mojom::Camera3MsgType::CAMERA3_MSG_ERROR) {
-    uint32_t frame_number = message->message->get_error()->frame_number;
-    uint64_t error_stream_id = message->message->get_error()->error_stream_id;
-    StreamType stream_type = StreamIdToStreamType(error_stream_id);
-    if (stream_type == StreamType::kUnknown) {
-      device_context_->SetErrorState(
-          media::VideoCaptureError::
-              kCrosHalV3BufferManagerUnknownStreamInCamera3NotifyMsg,
-          FROM_HERE,
-          std::string("Unknown stream in Camera3NotifyMsg: ") +
-              std::to_string(error_stream_id));
-      return;
-    }
-    cros::mojom::Camera3ErrorMsgCode error_code =
-        message->message->get_error()->error_code;
-    HandleNotifyError(frame_number, stream_type, error_code);
-  } else {  // cros::mojom::Camera3MsgType::CAMERA3_MSG_SHUTTER
-    uint32_t frame_number = message->message->get_shutter()->frame_number;
-    uint64_t shutter_time = message->message->get_shutter()->timestamp;
-    DVLOG(2) << "Received shutter time for frame " << frame_number;
-    if (!shutter_time) {
-      device_context_->SetErrorState(
-          media::VideoCaptureError::
-              kCrosHalV3BufferManagerReceivedInvalidShutterTime,
-          FROM_HERE,
-          std::string("Received invalid shutter time: ") +
-              std::to_string(shutter_time));
-      return;
-    }
-    CaptureResult& pending_result = pending_results_[frame_number];
-    // Shutter timestamp is in ns.
-    base::TimeTicks reference_time =
-        base::TimeTicks::FromInternalValue(shutter_time / 1000);
-    pending_result.reference_time = reference_time;
-    if (first_frame_shutter_time_.is_null()) {
-      // Record the shutter time of the first frame for calculating the
-      // timestamp.
-      first_frame_shutter_time_ = reference_time;
-    }
-    pending_result.timestamp = reference_time - first_frame_shutter_time_;
-    for (const auto& iter : stream_context_) {
-      SubmitCaptureResultIfComplete(frame_number, iter.first);
-    }
-  }
-}
-
-void StreamBufferManager::HandleNotifyError(
-    uint32_t frame_number,
-    StreamType stream_type,
-    cros::mojom::Camera3ErrorMsgCode error_code) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-
-  std::string warning_msg;
-
-  switch (error_code) {
-    case cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_DEVICE:
-      // Fatal error and no more frames will be produced by the device.
-      device_context_->SetErrorState(
-          media::VideoCaptureError::kCrosHalV3BufferManagerFatalDeviceError,
-          FROM_HERE, "Fatal device error");
-      return;
-
-    case cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_REQUEST:
-      // An error has occurred in processing the request; the request
-      // specified by |frame_number| has been dropped by the camera device.
-      // Subsequent requests are unaffected.
-      //
-      // The HAL will call ProcessCaptureResult with the buffers' state set to
-      // STATUS_ERROR.  The content of the buffers will be dropped and the
-      // buffers will be reused in SubmitCaptureResult.
-      warning_msg =
-          std::string("An error occurred while processing request for frame ") +
-          std::to_string(frame_number);
-      break;
-
-    case cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_RESULT:
-      // An error has occurred in producing the output metadata buffer for a
-      // result; the output metadata will not be available for the frame
-      // specified by |frame_number|.  Subsequent requests are unaffected.
-      warning_msg = std::string(
-                        "An error occurred while producing result "
-                        "metadata for frame ") +
-                    std::to_string(frame_number);
-      break;
-
-    case cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_BUFFER:
-      // An error has occurred in placing the output buffer into a stream for
-      // a request. |frame_number| specifies the request for which the buffer
-      // was dropped, and |stream_type| specifies the stream that dropped
-      // the buffer.
-      //
-      // The HAL will call ProcessCaptureResult with the buffer's state set to
-      // STATUS_ERROR.  The content of the buffer will be dropped and the
-      // buffer will be reused in SubmitCaptureResult.
-      warning_msg =
-          std::string(
-              "An error occurred while filling output buffer of stream ") +
-          StreamTypeToString(stream_type) + std::string(" in frame ") +
-          std::to_string(frame_number);
-      break;
-
-    default:
-      // To eliminate the warning for not handling CAMERA3_MSG_NUM_ERRORS
-      break;
-  }
-
-  LOG(WARNING) << warning_msg << stream_type;
-  device_context_->LogToClient(warning_msg);
-  // If the buffer is already returned by the HAL, submit it and we're done.
-  if (pending_results_.count(frame_number) &&
-      pending_results_[frame_number].buffers.count(stream_type)) {
-    SubmitCaptureResult(frame_number, stream_type);
-  }
-}
-
-void StreamBufferManager::SubmitCaptureResultIfComplete(
-    uint32_t frame_number,
-    StreamType stream_type) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-
-  if (!pending_results_.count(frame_number)) {
-    // The capture result may be discarded in case of error.
-    return;
-  }
-
-  CaptureResult& pending_result = pending_results_[frame_number];
-  if (!stream_context_[stream_type]->capture_results_with_buffer.count(
-          frame_number) ||
-      *pending_result.partial_metadata_received.rbegin() <
-          partial_result_count_ ||
-      pending_result.reference_time == base::TimeTicks()) {
-    // We can only submit the result buffer of |frame_number| for |stream_type|
-    // when:
-    //   1. The result buffer for |stream_type| is received, and
-    //   2. Received partial result id equals to partial result count, and
-    //   3. The shutter time is received.
-    return;
-  }
-  SubmitCaptureResult(frame_number, stream_type);
-}
-
-void StreamBufferManager::SubmitCaptureResult(uint32_t frame_number,
-                                              StreamType stream_type) {
-  DCHECK(ipc_task_runner_->BelongsToCurrentThread());
-  DCHECK(pending_results_.count(frame_number));
-  DCHECK(stream_context_[stream_type]->capture_results_with_buffer.count(
-      frame_number));
-
-  CaptureResult& pending_result =
-      *stream_context_[stream_type]->capture_results_with_buffer[frame_number];
-  if (stream_context_[stream_type]
-          ->capture_results_with_buffer.begin()
-          ->first != frame_number) {
-    device_context_->SetErrorState(
-        media::VideoCaptureError::
-            kCrosHalV3BufferManagerReceivedFrameIsOutOfOrder,
-        FROM_HERE,
-        std::string("Received frame is out-of-order; expect ") +
-            std::to_string(pending_results_.begin()->first) +
-            std::string(" but got ") + std::to_string(frame_number));
-    return;
-  }
-
-  DVLOG(2) << "Submit capture result of frame " << frame_number
-           << " for stream " << static_cast<int>(stream_type);
-  for (auto* iter : result_metadata_observers_) {
-    iter->OnResultMetadataAvailable(pending_result.metadata);
-  }
-
-  DCHECK(pending_result.buffers[stream_type]);
-  const cros::mojom::Camera3StreamBufferPtr& stream_buffer =
-      pending_result.buffers[stream_type];
-  uint64_t buffer_id = stream_buffer->buffer_id;
-
-  // Wait on release fence before delivering the result buffer to client.
-  if (stream_buffer->release_fence.is_valid()) {
-    const int kSyncWaitTimeoutMs = 1000;
-    mojo::PlatformHandle fence =
-        mojo::UnwrapPlatformHandle(std::move(stream_buffer->release_fence));
-    if (!fence.is_valid()) {
-      device_context_->SetErrorState(
-          media::VideoCaptureError::
-              kCrosHalV3BufferManagerFailedToUnwrapReleaseFenceFd,
-          FROM_HERE, "Failed to unwrap release fence fd");
-      return;
-    }
-    if (!sync_wait(fence.GetFD().get(), kSyncWaitTimeoutMs)) {
-      device_context_->SetErrorState(
-          media::VideoCaptureError::
-              kCrosHalV3BufferManagerSyncWaitOnReleaseFenceTimedOut,
-          FROM_HERE, "Sync wait on release fence timed out");
-      return;
-    }
-  }
-
-  // Deliver the captured data to client.
-  if (stream_buffer->status !=
-      cros::mojom::Camera3BufferStatus::CAMERA3_BUFFER_STATUS_ERROR) {
-    size_t buffer_index = GetBufferIndex(buffer_id);
-    gfx::GpuMemoryBuffer* buffer =
-        stream_context_[stream_type]->buffers[buffer_index].get();
-    if (stream_type == StreamType::kPreview) {
-      device_context_->SubmitCapturedData(
-          buffer, stream_context_[StreamType::kPreview]->capture_format,
-          pending_result.reference_time, pending_result.timestamp);
-      ipc_task_runner_->PostTask(
-          FROM_HERE,
-          base::BindOnce(&StreamBufferManager::RegisterBuffer,
-                         weak_ptr_factory_.GetWeakPtr(), StreamType::kPreview));
-    } else {  // StreamType::kStillCapture
-      DCHECK(pending_result.still_capture_callback);
-      const Camera3JpegBlob* header = reinterpret_cast<Camera3JpegBlob*>(
-          reinterpret_cast<uintptr_t>(buffer->memory(0)) +
-          buffer->GetSize().width() - sizeof(Camera3JpegBlob));
-      if (header->jpeg_blob_id != kCamera3JpegBlobId) {
-        device_context_->SetErrorState(
-            media::VideoCaptureError::kCrosHalV3BufferManagerInvalidJpegBlob,
-            FROM_HERE, "Invalid JPEG blob");
-        return;
-      }
-      // Still capture result from HALv3 already has orientation info in EXIF,
-      // so just provide 0 as screen rotation in |blobify_callback_| parameters.
-      mojom::BlobPtr blob = blobify_callback_.Run(
-          reinterpret_cast<uint8_t*>(buffer->memory(0)), header->jpeg_size,
-          stream_context_[stream_type]->capture_format, 0);
-      if (blob) {
-        std::move(pending_result.still_capture_callback).Run(std::move(blob));
-      } else {
-        LOG(ERROR) << "Failed to blobify the captured JPEG image";
-      }
-    }
-  }
-
-  stream_context_[stream_type]->free_buffers.push(buffer_id);
-  stream_context_[stream_type]->capture_results_with_buffer.erase(frame_number);
-  pending_result.unsubmitted_buffer_count--;
-  if (!pending_result.unsubmitted_buffer_count) {
-    pending_results_.erase(frame_number);
-  }
-
-  if (stream_type == StreamType::kPreview) {
-    // Always keep the preview stream running.
-    RegisterBuffer(StreamType::kPreview);
-  } else {  // stream_type == StreamType::kStillCapture
-    if (!still_capture_callbacks_yet_to_be_processed_.empty()) {
-      RegisterBuffer(StreamType::kStillCapture);
-    }
-  }
+// static
+size_t StreamBufferManager::GetBufferIndex(uint64_t buffer_id) {
+  return buffer_id & 0xFFFFFFFF;
 }
 
 StreamBufferManager::StreamContext::StreamContext() = default;
 
 StreamBufferManager::StreamContext::~StreamContext() = default;
 
-StreamBufferManager::CaptureResult::CaptureResult()
-    : metadata(cros::mojom::CameraMetadata::New()),
-      unsubmitted_buffer_count(0) {}
-
-StreamBufferManager::CaptureResult::~CaptureResult() = default;
-
 }  // namespace media
diff --git a/media/capture/video/chromeos/stream_buffer_manager.h b/media/capture/video/chromeos/stream_buffer_manager.h
index d11f86e..a71db10 100644
--- a/media/capture/video/chromeos/stream_buffer_manager.h
+++ b/media/capture/video/chromeos/stream_buffer_manager.h
@@ -5,13 +5,17 @@
 #ifndef MEDIA_CAPTURE_VIDEO_CHROMEOS_STREAM_BUFFER_MANAGER_H_
 #define MEDIA_CAPTURE_VIDEO_CHROMEOS_STREAM_BUFFER_MANAGER_H_
 
+#include <cstring>
+#include <initializer_list>
 #include <memory>
 #include <queue>
+#include <set>
 #include <unordered_map>
 #include <vector>
 
 #include "base/containers/queue.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "base/single_thread_task_runner.h"
 #include "media/capture/video/chromeos/camera_device_delegate.h"
 #include "media/capture/video/chromeos/mojo/camera3.mojom.h"
@@ -22,70 +26,33 @@
 
 class GpuMemoryBuffer;
 
-}  // namespace base
+}  // namespace gfx
 
 namespace media {
 
 class CameraBufferFactory;
 class CameraDeviceContext;
 
-// One stream for preview, one stream for still capture.
-constexpr size_t kMaxConfiguredStreams = 2;
-
-// The JPEG transport header as defined by Android camera HAL v3 API.  The JPEG
-// transport header is at the end of the blob buffer filled by the HAL.
-constexpr uint16_t kCamera3JpegBlobId = 0x00FF;
-struct Camera3JpegBlob {
-  uint16_t jpeg_blob_id;
-  uint32_t jpeg_size;
-};
-
-class CAPTURE_EXPORT CaptureMetadataDispatcher {
- public:
-  class ResultMetadataObserver {
-   public:
-    virtual ~ResultMetadataObserver() {}
-    virtual void OnResultMetadataAvailable(
-        const cros::mojom::CameraMetadataPtr&) = 0;
-  };
-
-  virtual ~CaptureMetadataDispatcher() {}
-  virtual void AddResultMetadataObserver(ResultMetadataObserver* observer) = 0;
-  virtual void RemoveResultMetadataObserver(
-      ResultMetadataObserver* observer) = 0;
-  virtual void SetCaptureMetadata(cros::mojom::CameraMetadataTag tag,
-                                  cros::mojom::EntryType type,
-                                  size_t count,
-                                  std::vector<uint8_t> value) = 0;
-  virtual void SetRepeatingCaptureMetadata(cros::mojom::CameraMetadataTag tag,
-                                           cros::mojom::EntryType type,
-                                           size_t count,
-                                           std::vector<uint8_t> value) = 0;
-  virtual void UnsetRepeatingCaptureMetadata(
-      cros::mojom::CameraMetadataTag tag) = 0;
-};
+struct BufferInfo;
 
 // StreamBufferManager is responsible for managing the buffers of the
 // stream.  StreamBufferManager allocates buffers according to the given
-// stream configuration, and circulates the buffers along with capture
-// requests and results between Chrome and the camera HAL process.
-class CAPTURE_EXPORT StreamBufferManager final
-    : public cros::mojom::Camera3CallbackOps,
-      public CaptureMetadataDispatcher {
+// stream configuration.
+class CAPTURE_EXPORT StreamBufferManager final {
  public:
   StreamBufferManager(
-      cros::mojom::Camera3CallbackOpsRequest callback_ops_request,
-      std::unique_ptr<StreamCaptureInterface> capture_interface,
       CameraDeviceContext* device_context,
-      std::unique_ptr<CameraBufferFactory> camera_buffer_factory,
-      base::RepeatingCallback<
-          mojom::BlobPtr(const uint8_t* buffer,
-                         const uint32_t bytesused,
-                         const VideoCaptureFormat& capture_format,
-                         int screen_rotation)> blobify_callback,
-      scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner);
+      std::unique_ptr<CameraBufferFactory> camera_buffer_factory);
+  ~StreamBufferManager();
 
-  ~StreamBufferManager() override;
+  gfx::GpuMemoryBuffer* GetBufferById(StreamType stream_type,
+                                      uint64_t buffer_id);
+
+  VideoCaptureFormat GetStreamCaptureFormat(StreamType stream_type);
+
+  // Checks if all streams are available. For output stream, it is available if
+  // it has free buffers.
+  bool HasFreeBuffers(const std::set<StreamType>& stream_types);
 
   // Sets up the stream context and allocate buffers according to the
   // configuration specified in |stream|.
@@ -94,154 +61,22 @@
       const cros::mojom::CameraMetadataPtr& static_metadata,
       std::vector<cros::mojom::Camera3StreamPtr> streams);
 
-  // StartPreview is the entry point to starting the video capture.  The way
-  // the video capture loop works is:
-  //
-  //  (1) If there is a free buffer, RegisterBuffer registers the buffer with
-  //      the camera HAL.
-  //  (2) Once the free buffer is registered, ProcessCaptureRequest is called
-  //      to issue a capture request which will eventually fill the registered
-  //      buffer.  Goto (1) to register the remaining free buffers.
-  //  (3) The camera HAL returns the shutter time of a capture request through
-  //      Notify, and the filled buffer through ProcessCaptureResult.
-  //  (4) Once all the result metadata are collected,
-  //      SubmitCaptureResultIfComplete is called to deliver the filled buffer
-  //      to Chrome.  After the buffer is consumed by Chrome it is enqueued back
-  //      to the free buffer queue.  Goto (1) to start another capture loop.
-  //
-  // When TakePhoto() is called, an additional BLOB buffer is queued in step (2)
-  // to let the HAL fill the still capture JPEG image.  When the JPEG image is
-  // returned in (4), it's passed to upper layer through the TakePhotoCallback.
-  void StartPreview(cros::mojom::CameraMetadataPtr preview_settings);
-
-  // Stops the capture loop.  After StopPreview is called |callback_ops_| is
-  // unbound, so no new capture request or result will be processed.
-  void StopPreview(base::OnceCallback<void(int32_t)> callback);
-
   cros::mojom::Camera3StreamPtr GetStreamConfiguration(StreamType stream_type);
 
-  void TakePhoto(cros::mojom::CameraMetadataPtr settings,
-                 VideoCaptureDevice::TakePhotoCallback callback);
+  // Requests buffer for specific stream type.
+  base::Optional<BufferInfo> RequestBuffer(StreamType stream_type);
 
-  size_t GetStreamNumber();
+  // Releases buffer by marking it as free buffer.
+  void ReleaseBuffer(StreamType stream_type, uint64_t buffer_id);
 
-  // CaptureMetadataDispatcher implementations.
-  void AddResultMetadataObserver(ResultMetadataObserver* observer) override;
-  void RemoveResultMetadataObserver(ResultMetadataObserver* observer) override;
+  size_t GetNumberOfStreams();
 
-  // Queues a capture setting that will be send along with the earliest next
-  // capture request.
-  void SetCaptureMetadata(cros::mojom::CameraMetadataTag tag,
-                          cros::mojom::EntryType type,
-                          size_t count,
-                          std::vector<uint8_t> value) override;
-
-  void SetRepeatingCaptureMetadata(cros::mojom::CameraMetadataTag tag,
-                                   cros::mojom::EntryType type,
-                                   size_t count,
-                                   std::vector<uint8_t> value) override;
-
-  void UnsetRepeatingCaptureMetadata(
-      cros::mojom::CameraMetadataTag tag) override;
+ private:
+  friend class RequestManagerTest;
 
   static uint64_t GetBufferIpcId(StreamType stream_type, size_t index);
 
- private:
-  friend class StreamBufferManagerTest;
-
-  // Registers a free buffer, if any, for the give |stream_type| to the camera
-  // HAL.
-  void RegisterBuffer(StreamType stream_type);
-
-  // Calls ProcessCaptureRequest if the buffer specified by |buffer_id| is
-  // successfully registered.
-  void OnRegisteredBuffer(StreamType stream_type,
-                          uint64_t buffer_id,
-                          int32_t result);
-
-  // The capture request contains the buffer handles waiting to be filled.
-  void ProcessCaptureRequest();
-  // Calls RegisterBuffer to attempt to register any remaining free buffers.
-  void OnProcessedCaptureRequest(int32_t result);
-
-  // Camera3CallbackOps implementations.
-
-  // ProcessCaptureResult receives the result metadata as well as the filled
-  // buffer from camera HAL.  The result metadata may be divided and delivered
-  // in several stages.  Before all the result metadata is received the
-  // partial results are kept in |pending_results_|.
-  void ProcessCaptureResult(
-      cros::mojom::Camera3CaptureResultPtr result) override;
-
-  // Notify receives the shutter time of capture requests and various errors
-  // from camera HAL.  The shutter time is used as the timestamp in the video
-  // frame delivered to Chrome.
-  void Notify(cros::mojom::Camera3NotifyMsgPtr message) override;
-  void HandleNotifyError(uint32_t frame_number,
-                         StreamType stream_type,
-                         cros::mojom::Camera3ErrorMsgCode error_code);
-
-  // Submits the captured buffer of frame |frame_number_| for the give
-  // |stream_type| to Chrome if all the required metadata and the captured
-  // buffer are received.  After the buffer is submitted the function then
-  // enqueues the buffer to free buffer queue for the next capture request.
-  void SubmitCaptureResultIfComplete(uint32_t frame_number,
-                                     StreamType stream_type);
-  void SubmitCaptureResult(uint32_t frame_number, StreamType stream_type);
-
-  void ApplyCaptureSettings(cros::mojom::CameraMetadataPtr* capture_settings);
-
-  mojo::Binding<cros::mojom::Camera3CallbackOps> callback_ops_;
-
-  std::unique_ptr<StreamCaptureInterface> capture_interface_;
-
-  CameraDeviceContext* device_context_;
-
-  std::unique_ptr<CameraBufferFactory> camera_buffer_factory_;
-
-  base::RepeatingCallback<mojom::BlobPtr(
-      const uint8_t* buffer,
-      const uint32_t bytesused,
-      const VideoCaptureFormat& capture_format,
-      int screen_rotation)>
-      blobify_callback_;
-
-  // Where all the Mojo IPC calls takes place.
-  const scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_;
-
-  // A flag indicating whether the capture loops is running.
-  bool capturing_;
-
-  // The frame number.  Increased by one for each capture request sent; reset
-  // to zero in AllocateAndStart.
-  uint32_t frame_number_;
-
-  // CaptureResult is used to hold the pending capture results for each frame.
-  struct CaptureResult {
-    CaptureResult();
-    ~CaptureResult();
-    // |reference_time| and |timestamp| are derived from the shutter time of
-    // this frame.  They are be passed to |client_->OnIncomingCapturedData|
-    // along with the |buffers| when the captured frame is submitted.
-    base::TimeTicks reference_time;
-    base::TimeDelta timestamp;
-    // The result metadata.  Contains various information about the captured
-    // frame.
-    cros::mojom::CameraMetadataPtr metadata;
-    // The buffer handles that hold the captured data of this frame.
-    std::unordered_map<StreamType, cros::mojom::Camera3StreamBufferPtr> buffers;
-    // The set of the partial metadata received.  For each capture result, the
-    // total number of partial metadata should equal to
-    // |partial_result_count_|.
-    std::set<uint32_t> partial_metadata_received;
-    // Incremented for every stream buffer requested for the given frame.
-    // StreamBufferManager destructs the CaptureResult when
-    // |unsubmitted_buffer_count| drops to zero.
-    size_t unsubmitted_buffer_count;
-    // The callback used to return the captured still capture JPEG buffer.  Set
-    // if and only if the capture request was sent with a still capture buffer.
-    VideoCaptureDevice::TakePhotoCallback still_capture_callback;
-  };
+  static size_t GetBufferIndex(uint64_t buffer_id);
 
   struct StreamContext {
     StreamContext();
@@ -255,60 +90,15 @@
     // The free buffers of this stream.  The queue stores indices into the
     // |buffers| vector.
     std::queue<uint64_t> free_buffers;
-    // The buffers that are registered to the HAL, which can be used as the
-    // output buffers for capture requests.
-    std::queue<uint64_t> registered_buffers;
-    // The pointers to the pending capture results that have unsubmitted result
-    // buffers.
-    std::map<uint32_t, CaptureResult*> capture_results_with_buffer;
   };
 
   // The context for the set of active streams.
   std::unordered_map<StreamType, std::unique_ptr<StreamContext>>
       stream_context_;
 
-  // The repeating request settings.  The settings come from the default preview
-  // request settings reported by the HAL.  |repeating_request_settings_| is the
-  // default settings for each capture request.
-  cros::mojom::CameraMetadataPtr repeating_request_settings_;
+  CameraDeviceContext* device_context_;
 
-  // A queue of oneshot request settings.  These are the request settings for
-  // each still capture requests.  |oneshot_request_settings_| overrides
-  // |repeating_request_settings_| if present.
-  std::queue<cros::mojom::CameraMetadataPtr> oneshot_request_settings_;
-
-  // The pending callbacks for the TakePhoto requests.
-  std::queue<VideoCaptureDevice::TakePhotoCallback>
-      still_capture_callbacks_yet_to_be_processed_;
-  std::queue<VideoCaptureDevice::TakePhotoCallback>
-      still_capture_callbacks_currently_processing_;
-
-  // The number of partial stages.  |partial_result_count_| is learned by
-  // querying |static_metadata_|.  In case the result count is absent in
-  // |static_metadata_|, it defaults to one which means all the result
-  // metadata and captured buffer of a frame are returned together in one
-  // shot.
-  uint32_t partial_result_count_;
-
-  // The shutter time of the first frame.  We derive the |timestamp| of a
-  // frame using the difference between the frame's shutter time and
-  // |first_frame_shutter_time_|.
-  base::TimeTicks first_frame_shutter_time_;
-
-  // Stores the pending capture results of the current in-flight frames.
-  std::map<uint32_t, CaptureResult> pending_results_;
-
-  // StreamBufferManager does not own the ResultMetadataObservers.  The
-  // observers are responsible for removing itself before self-destruction.
-  std::unordered_set<ResultMetadataObserver*> result_metadata_observers_;
-
-  // The list of settings to set/override once in the capture request.
-  std::vector<cros::mojom::CameraMetadataEntryPtr> capture_settings_override_;
-
-  // The settings to set/override repeatedly in the capture request.  In
-  // conflict with |capture_settings_override_|, this one has lower priority.
-  std::map<cros::mojom::CameraMetadataTag, cros::mojom::CameraMetadataEntryPtr>
-      capture_settings_repeating_override_;
+  std::unique_ptr<CameraBufferFactory> camera_buffer_factory_;
 
   base::WeakPtrFactory<StreamBufferManager> weak_ptr_factory_;
 
diff --git a/media/capture/video/mock_gpu_memory_buffer_manager.cc b/media/capture/video/mock_gpu_memory_buffer_manager.cc
index 9f86633..b07328e 100644
--- a/media/capture/video/mock_gpu_memory_buffer_manager.cc
+++ b/media/capture/video/mock_gpu_memory_buffer_manager.cc
@@ -9,7 +9,7 @@
 #include "build/build_config.h"
 
 #if defined(OS_CHROMEOS)
-#include "media/capture/video/chromeos/stream_buffer_manager.h"
+#include "media/capture/video/chromeos/request_manager.h"
 #endif
 
 using ::testing::Return;
diff --git a/media/mojo/services/mojo_audio_output_stream.cc b/media/mojo/services/mojo_audio_output_stream.cc
index 1fb18bb..27ee8f7 100644
--- a/media/mojo/services/mojo_audio_output_stream.cc
+++ b/media/mojo/services/mojo_audio_output_stream.cc
@@ -11,6 +11,7 @@
 #include "base/callback_helpers.h"
 #include "base/memory/unsafe_shared_memory_region.h"
 #include "base/sync_socket.h"
+#include "media/mojo/interfaces/audio_data_pipe.mojom.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 
 namespace media {
diff --git a/media/mojo/services/mojo_audio_output_stream_provider.cc b/media/mojo/services/mojo_audio_output_stream_provider.cc
index f7893f8..acfd83d 100644
--- a/media/mojo/services/mojo_audio_output_stream_provider.cc
+++ b/media/mojo/services/mojo_audio_output_stream_provider.cc
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "build/build_config.h"
+#include "media/mojo/interfaces/audio_data_pipe.mojom.h"
 #include "mojo/public/cpp/bindings/message.h"
 
 namespace media {
diff --git a/media/mojo/services/mojo_audio_output_stream_unittest.cc b/media/mojo/services/mojo_audio_output_stream_unittest.cc
index 5f701d17..e5d80a12 100644
--- a/media/mojo/services/mojo_audio_output_stream_unittest.cc
+++ b/media/mojo/services/mojo_audio_output_stream_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/run_loop.h"
 #include "base/sync_socket.h"
 #include "media/audio/audio_output_controller.h"
+#include "media/mojo/interfaces/audio_data_pipe.mojom.h"
 #include "mojo/public/cpp/system/message_pipe.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "testing/gmock/include/gmock/gmock.h"
diff --git a/media/remoting/renderer_controller.h b/media/remoting/renderer_controller.h
index 90abdda..6da6f94c 100644
--- a/media/remoting/renderer_controller.h
+++ b/media/remoting/renderer_controller.h
@@ -17,6 +17,7 @@
 #include "media/base/media_observer.h"
 #include "media/media_buildflags.h"
 #include "media/mojo/interfaces/remoting.mojom.h"
+#include "media/mojo/interfaces/remoting_common.mojom.h"
 #include "media/remoting/metrics.h"
 #include "mojo/public/cpp/bindings/binding.h"
 
diff --git a/mojo/public/cpp/bindings/features.cc b/mojo/public/cpp/bindings/features.cc
index 36c00d99..9026ba7 100644
--- a/mojo/public/cpp/bindings/features.cc
+++ b/mojo/public/cpp/bindings/features.cc
@@ -19,10 +19,8 @@
 // regress in performance due to high-priority messages seeing increased
 // latency. Ideally we'd address these cases by giving the affected bindings
 // higher-priority TaskRunners.
-//
-// TODO(https://crbug.com/866708): Enable this by default after M73 branch.
 const base::Feature kTaskPerMessage{"MojoTaskPerMessage",
-                                    base::FEATURE_DISABLED_BY_DEFAULT};
+                                    base::FEATURE_ENABLED_BY_DEFAULT};
 
 }  // namespace features
 }  // namespace mojo
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
index 12e67cb..b60b9260 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
@@ -31,7 +31,7 @@
 {%- endmacro %}
 
 {#--- Begin #}
-const char {{class_name}}::Name_[] = "{{namespace}}.{{class_name}}";
+const char {{class_name}}::Name_[] = "{{module_namespace}}.{{class_name}}";
 
 {#--- Constants #}
 {%-  for constant in interface.constants %}
diff --git a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
index e2470eb..443f5e2 100644
--- a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
@@ -302,7 +302,7 @@
       "interfaces": self.module.interfaces,
       "kinds": self.module.kinds,
       "module": self.module,
-      "namespace": self.module.namespace,
+      "module_namespace": self.module.namespace,
       "namespaces_as_array": NamespaceToArray(self.module.namespace),
       "structs": self.module.structs,
       "support_lazy_serialization": self.support_lazy_serialization,
diff --git a/services/audio/log_factory_adapter.h b/services/audio/log_factory_adapter.h
index c55edef..f248897 100644
--- a/services/audio/log_factory_adapter.h
+++ b/services/audio/log_factory_adapter.h
@@ -10,6 +10,7 @@
 #include "base/containers/queue.h"
 #include "media/audio/audio_logging.h"
 #include "media/audio/fake_audio_log_factory.h"
+#include "media/mojo/interfaces/audio_logging.mojom.h"
 #include "services/audio/public/mojom/log_factory_manager.mojom.h"
 
 namespace media {
diff --git a/services/audio/public/cpp/fake_stream_factory.h b/services/audio/public/cpp/fake_stream_factory.h
index b5ad3415..2a661986d 100644
--- a/services/audio/public/cpp/fake_stream_factory.h
+++ b/services/audio/public/cpp/fake_stream_factory.h
@@ -7,8 +7,11 @@
 
 #include <string>
 
+#include "media/mojo/interfaces/audio_input_stream.mojom.h"
+#include "media/mojo/interfaces/audio_logging.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
+#include "services/audio/public/mojom/audio_processing.mojom.h"
 #include "services/audio/public/mojom/stream_factory.mojom.h"
 
 namespace audio {
diff --git a/services/audio/public/cpp/input_ipc.cc b/services/audio/public/cpp/input_ipc.cc
index 0347d35..e98ebb1 100644
--- a/services/audio/public/cpp/input_ipc.cc
+++ b/services/audio/public/cpp/input_ipc.cc
@@ -8,7 +8,9 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "media/mojo/interfaces/audio_data_pipe.mojom.h"
 #include "mojo/public/cpp/system/platform_handle.h"
+#include "services/audio/public/mojom/audio_processing.mojom.h"
 #include "services/audio/public/mojom/constants.mojom.h"
 
 namespace audio {
diff --git a/services/audio/public/cpp/input_ipc.h b/services/audio/public/cpp/input_ipc.h
index 6971b2d9..e20e59e 100644
--- a/services/audio/public/cpp/input_ipc.h
+++ b/services/audio/public/cpp/input_ipc.h
@@ -15,6 +15,7 @@
 #include "base/time/time.h"
 #include "media/audio/audio_input_ipc.h"
 #include "media/mojo/interfaces/audio_input_stream.mojom.h"
+#include "media/mojo/interfaces/audio_logging.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/audio/public/mojom/stream_factory.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
diff --git a/services/audio/public/cpp/output_device.cc b/services/audio/public/cpp/output_device.cc
index b2e85f0..7241e60 100644
--- a/services/audio/public/cpp/output_device.cc
+++ b/services/audio/public/cpp/output_device.cc
@@ -10,6 +10,8 @@
 #include "base/optional.h"
 #include "base/threading/thread_restrictions.h"
 #include "media/audio/audio_output_device_thread_callback.h"
+#include "media/mojo/interfaces/audio_data_pipe.mojom.h"
+#include "media/mojo/interfaces/audio_logging.mojom.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "services/audio/public/mojom/constants.mojom.h"
 
diff --git a/services/audio/public/cpp/output_device.h b/services/audio/public/cpp/output_device.h
index 277d970e..370d4c6 100644
--- a/services/audio/public/cpp/output_device.h
+++ b/services/audio/public/cpp/output_device.h
@@ -10,6 +10,7 @@
 
 #include "media/base/audio_renderer_sink.h"
 #include "media/mojo/interfaces/audio_output_stream.mojom.h"
+#include "services/audio/public/mojom/audio_processing.mojom.h"
 #include "services/audio/public/mojom/stream_factory.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
 
diff --git a/services/audio/public/cpp/output_device_unittest.cc b/services/audio/public/cpp/output_device_unittest.cc
index af31623..89e762e 100644
--- a/services/audio/public/cpp/output_device_unittest.cc
+++ b/services/audio/public/cpp/output_device_unittest.cc
@@ -12,6 +12,7 @@
 #include "build/build_config.h"
 #include "media/audio/audio_sync_reader.h"
 #include "media/base/audio_renderer_sink.h"
+#include "media/mojo/interfaces/audio_data_pipe.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "services/audio/public/cpp/fake_stream_factory.h"
diff --git a/services/identity/identity_manager_impl_unittest.cc b/services/identity/identity_manager_impl_unittest.cc
index 7968932..28ae35c 100644
--- a/services/identity/identity_manager_impl_unittest.cc
+++ b/services/identity/identity_manager_impl_unittest.cc
@@ -15,6 +15,7 @@
 #include "services/identity/identity_service.h"
 #include "services/identity/public/cpp/account_state.h"
 #include "services/identity/public/cpp/scope_set.h"
+#include "services/identity/public/mojom/account.mojom.h"
 #include "services/identity/public/mojom/constants.mojom.h"
 #include "services/identity/public/mojom/identity_manager.mojom.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
diff --git a/services/media_session/media_controller_unittest.cc b/services/media_session/media_controller_unittest.cc
index 9e2f0d2..c0090fe 100644
--- a/services/media_session/media_controller_unittest.cc
+++ b/services/media_session/media_controller_unittest.cc
@@ -770,7 +770,8 @@
   }
 }
 
-TEST_F(MediaControllerTest, ActiveController_Actions_Observer_Empty) {
+// TODO(https://crbug.com/925868): Fix and re-enable this.
+TEST_F(MediaControllerTest, DISABLED_ActiveController_Actions_Observer_Empty) {
   test::MockMediaSession media_session;
   media_session.EnableAction(mojom::MediaSessionAction::kPlay);
   media_session.SetIsControllable(true);
@@ -790,7 +791,9 @@
   }
 }
 
-TEST_F(MediaControllerTest, ActiveController_Actions_Observer_WithInfo) {
+// TODO(https://crbug.com/925868): Fix and re-enable this.
+TEST_F(MediaControllerTest,
+       DISABLED_ActiveController_Actions_Observer_WithInfo) {
   test::MockMediaSession media_session;
   media_session.SetIsControllable(true);
 
diff --git a/services/media_session/public/cpp/test/test_media_controller.h b/services/media_session/public/cpp/test/test_media_controller.h
index 8801a67d..af8de837 100644
--- a/services/media_session/public/cpp/test/test_media_controller.h
+++ b/services/media_session/public/cpp/test/test_media_controller.h
@@ -10,6 +10,7 @@
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/interface_ptr_set.h"
 #include "services/media_session/public/mojom/media_controller.mojom.h"
+#include "services/media_session/public/mojom/media_session.mojom.h"
 
 namespace media_session {
 namespace test {
diff --git a/services/service_manager/runner/host/service_process_launcher_unittest.cc b/services/service_manager/runner/host/service_process_launcher_unittest.cc
index 2528224..0b91049 100644
--- a/services/service_manager/runner/host/service_process_launcher_unittest.cc
+++ b/services/service_manager/runner/host/service_process_launcher_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "build/build_config.h"
+#include "services/service_manager/public/mojom/service.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace service_manager {
diff --git a/services/video_capture/broadcasting_receiver.cc b/services/video_capture/broadcasting_receiver.cc
index 19accba6..9babeca 100644
--- a/services/video_capture/broadcasting_receiver.cc
+++ b/services/video_capture/broadcasting_receiver.cc
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
+#include "services/video_capture/public/mojom/scoped_access_permission.mojom.h"
 
 namespace video_capture {
 
diff --git a/services/video_capture/broadcasting_receiver.h b/services/video_capture/broadcasting_receiver.h
index 19753d1..47636de 100644
--- a/services/video_capture/broadcasting_receiver.h
+++ b/services/video_capture/broadcasting_receiver.h
@@ -11,6 +11,7 @@
 #include "base/sequence_checker.h"
 #include "media/capture/video/video_frame_receiver.h"
 #include "services/video_capture/public/mojom/receiver.mojom.h"
+#include "services/video_capture/public/mojom/scoped_access_permission.mojom.h"
 
 namespace video_capture {
 
diff --git a/services/video_capture/device_factory_media_to_mojo_adapter.cc b/services/video_capture/device_factory_media_to_mojo_adapter.cc
index 49bcd88..ba80b1c 100644
--- a/services/video_capture/device_factory_media_to_mojo_adapter.cc
+++ b/services/video_capture/device_factory_media_to_mojo_adapter.cc
@@ -14,6 +14,7 @@
 #include "media/capture/video/video_capture_device_info.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "services/video_capture/device_media_to_mojo_adapter.h"
+#include "services/video_capture/public/mojom/producer.mojom.h"
 #include "services/video_capture/public/uma/video_capture_service_event.h"
 
 namespace {
diff --git a/services/video_capture/device_factory_provider_impl.h b/services/video_capture/device_factory_provider_impl.h
index f3556e1..19b641a 100644
--- a/services/video_capture/device_factory_provider_impl.h
+++ b/services/video_capture/device_factory_provider_impl.h
@@ -14,7 +14,9 @@
 #include "services/service_manager/public/cpp/connector.h"
 #include "services/service_manager/public/cpp/service.h"
 #include "services/service_manager/public/cpp/service_context_ref.h"
+#include "services/video_capture/public/mojom/device_factory.mojom.h"
 #include "services/video_capture/public/mojom/device_factory_provider.mojom.h"
+#include "services/video_capture/public/mojom/video_source_provider.mojom.h"
 
 namespace video_capture {
 
diff --git a/services/video_capture/public/cpp/mock_device_factory.h b/services/video_capture/public/cpp/mock_device_factory.h
index 8fc641b..bfb3827 100644
--- a/services/video_capture/public/cpp/mock_device_factory.h
+++ b/services/video_capture/public/cpp/mock_device_factory.h
@@ -6,6 +6,7 @@
 #define SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_DEVICE_FACTORY_H_
 
 #include "services/video_capture/public/mojom/device_factory.mojom.h"
+#include "services/video_capture/public/mojom/producer.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace video_capture {
diff --git a/services/video_capture/public/cpp/mock_producer.h b/services/video_capture/public/cpp/mock_producer.h
index 084168c..cf506da 100644
--- a/services/video_capture/public/cpp/mock_producer.h
+++ b/services/video_capture/public/cpp/mock_producer.h
@@ -5,6 +5,7 @@
 #ifndef SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_PRODUCER_H_
 #define SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_PRODUCER_H_
 
+#include "media/capture/mojom/video_capture_types.mojom.h"
 #include "media/mojo/interfaces/media_types.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/video_capture/public/mojom/producer.mojom.h"
diff --git a/services/video_capture/public/cpp/mock_push_subscription.h b/services/video_capture/public/cpp/mock_push_subscription.h
index 5293a9e..ee7ebc8 100644
--- a/services/video_capture/public/cpp/mock_push_subscription.h
+++ b/services/video_capture/public/cpp/mock_push_subscription.h
@@ -5,6 +5,7 @@
 #ifndef SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_PUSH_SUBSCRIPTION_H_
 #define SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_PUSH_SUBSCRIPTION_H_
 
+#include "media/capture/mojom/image_capture.mojom.h"
 #include "services/video_capture/public/mojom/video_source.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
diff --git a/services/video_capture/public/cpp/mock_receiver.cc b/services/video_capture/public/cpp/mock_receiver.cc
index f49123f..788ee07 100644
--- a/services/video_capture/public/cpp/mock_receiver.cc
+++ b/services/video_capture/public/cpp/mock_receiver.cc
@@ -5,6 +5,7 @@
 #include "services/video_capture/public/cpp/mock_receiver.h"
 
 #include "base/stl_util.h"
+#include "services/video_capture/public/mojom/scoped_access_permission.mojom.h"
 
 namespace video_capture {
 
diff --git a/services/video_capture/public/cpp/mock_receiver.h b/services/video_capture/public/cpp/mock_receiver.h
index 6edfa1d..aa8fb08 100644
--- a/services/video_capture/public/cpp/mock_receiver.h
+++ b/services/video_capture/public/cpp/mock_receiver.h
@@ -7,9 +7,11 @@
 
 #include <vector>
 
+#include "media/capture/mojom/video_capture_types.mojom.h"
 #include "media/mojo/interfaces/media_types.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/video_capture/public/mojom/receiver.mojom.h"
+#include "services/video_capture/public/mojom/scoped_access_permission.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace video_capture {
diff --git a/services/video_capture/public/cpp/mock_video_source.h b/services/video_capture/public/cpp/mock_video_source.h
index 77bb5f9..d8c54248b2 100644
--- a/services/video_capture/public/cpp/mock_video_source.h
+++ b/services/video_capture/public/cpp/mock_video_source.h
@@ -5,6 +5,7 @@
 #ifndef SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_VIDEO_SOURCE_H_
 #define SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_VIDEO_SOURCE_H_
 
+#include "services/video_capture/public/mojom/receiver.mojom.h"
 #include "services/video_capture/public/mojom/video_source.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
diff --git a/services/video_capture/public/cpp/mock_video_source_provider.h b/services/video_capture/public/cpp/mock_video_source_provider.h
index 6dcaed0..9b0282d 100644
--- a/services/video_capture/public/cpp/mock_video_source_provider.h
+++ b/services/video_capture/public/cpp/mock_video_source_provider.h
@@ -5,6 +5,7 @@
 #ifndef SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_VIDEO_SOURCE_PROVIDER_H_
 #define SERVICES_VIDEO_CAPTURE_PUBLIC_CPP_MOCK_VIDEO_SOURCE_PROVIDER_H_
 
+#include "services/video_capture/public/mojom/producer.mojom.h"
 #include "services/video_capture/public/mojom/video_source_provider.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
diff --git a/services/video_capture/public/cpp/receiver_media_to_mojo_adapter.cc b/services/video_capture/public/cpp/receiver_media_to_mojo_adapter.cc
index e5b54860..f1d7dd3b 100644
--- a/services/video_capture/public/cpp/receiver_media_to_mojo_adapter.cc
+++ b/services/video_capture/public/cpp/receiver_media_to_mojo_adapter.cc
@@ -6,6 +6,7 @@
 
 #include "media/capture/video/shared_memory_handle_provider.h"
 #include "mojo/public/cpp/system/platform_handle.h"
+#include "services/video_capture/public/mojom/scoped_access_permission.mojom.h"
 
 namespace {
 
diff --git a/services/video_capture/push_video_stream_subscription_impl.h b/services/video_capture/push_video_stream_subscription_impl.h
index 74c1dac0..92cde96 100644
--- a/services/video_capture/push_video_stream_subscription_impl.h
+++ b/services/video_capture/push_video_stream_subscription_impl.h
@@ -7,6 +7,7 @@
 
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/video_capture/public/mojom/device.mojom.h"
+#include "services/video_capture/public/mojom/receiver.mojom.h"
 #include "services/video_capture/public/mojom/video_source.mojom.h"
 
 namespace video_capture {
diff --git a/services/video_capture/scoped_access_permission_media_to_mojo_adapter.h b/services/video_capture/scoped_access_permission_media_to_mojo_adapter.h
index 4ef3e5d..e0c4e33 100644
--- a/services/video_capture/scoped_access_permission_media_to_mojo_adapter.h
+++ b/services/video_capture/scoped_access_permission_media_to_mojo_adapter.h
@@ -7,6 +7,7 @@
 
 #include "media/capture/video/video_capture_device_client.h"
 #include "services/video_capture/public/mojom/receiver.mojom.h"
+#include "services/video_capture/public/mojom/scoped_access_permission.mojom.h"
 
 namespace video_capture {
 
diff --git a/services/video_capture/shared_memory_virtual_device_mojo_adapter.h b/services/video_capture/shared_memory_virtual_device_mojo_adapter.h
index 0a8d57e..208179d 100644
--- a/services/video_capture/shared_memory_virtual_device_mojo_adapter.h
+++ b/services/video_capture/shared_memory_virtual_device_mojo_adapter.h
@@ -11,6 +11,7 @@
 #include "services/service_manager/public/cpp/service_context_ref.h"
 #include "services/video_capture/public/mojom/device.mojom.h"
 #include "services/video_capture/public/mojom/producer.mojom.h"
+#include "services/video_capture/public/mojom/receiver.mojom.h"
 #include "services/video_capture/public/mojom/virtual_device.mojom.h"
 
 namespace video_capture {
diff --git a/services/video_capture/test/device_factory_provider_connectortest.cc b/services/video_capture/test/device_factory_provider_connectortest.cc
index 638be12..104f89b 100644
--- a/services/video_capture/test/device_factory_provider_connectortest.cc
+++ b/services/video_capture/test/device_factory_provider_connectortest.cc
@@ -12,6 +12,7 @@
 #include "services/service_manager/public/cpp/test/test_connector_factory.h"
 #include "services/video_capture/public/cpp/mock_receiver.h"
 #include "services/video_capture/public/mojom/constants.mojom.h"
+#include "services/video_capture/public/mojom/device.mojom.h"
 #include "services/video_capture/public/mojom/device_factory_provider.mojom.h"
 #include "services/video_capture/service_impl.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/services/video_capture/test/device_factory_provider_test.h b/services/video_capture/test/device_factory_provider_test.h
index fe3262ba..791fa80 100644
--- a/services/video_capture/test/device_factory_provider_test.h
+++ b/services/video_capture/test/device_factory_provider_test.h
@@ -12,7 +12,9 @@
 #include "services/service_manager/public/cpp/service.h"
 #include "services/service_manager/public/cpp/service_binding.h"
 #include "services/service_manager/public/cpp/test/test_service_manager.h"
+#include "services/video_capture/public/mojom/device_factory.mojom.h"
 #include "services/video_capture/public/mojom/device_factory_provider.mojom.h"
+#include "services/video_capture/public/mojom/virtual_device.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace video_capture {
diff --git a/services/video_capture/test/device_factory_provider_unittest.cc b/services/video_capture/test/device_factory_provider_unittest.cc
index 4c7e0d3..a68b27a 100644
--- a/services/video_capture/test/device_factory_provider_unittest.cc
+++ b/services/video_capture/test/device_factory_provider_unittest.cc
@@ -8,7 +8,9 @@
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/video_capture/public/cpp/mock_producer.h"
 #include "services/video_capture/public/mojom/constants.mojom.h"
+#include "services/video_capture/public/mojom/device.mojom.h"
 #include "services/video_capture/public/mojom/device_factory.mojom.h"
+#include "services/video_capture/public/mojom/virtual_device.mojom.h"
 #include "services/video_capture/test/device_factory_provider_test.h"
 #include "services/video_capture/test/mock_devices_changed_observer.h"
 
diff --git a/services/video_capture/test/fake_device_descriptor_unittest.cc b/services/video_capture/test/fake_device_descriptor_unittest.cc
index 215319b..ad5a377 100644
--- a/services/video_capture/test/fake_device_descriptor_unittest.cc
+++ b/services/video_capture/test/fake_device_descriptor_unittest.cc
@@ -6,6 +6,7 @@
 #include "base/bind_helpers.h"
 #include "base/run_loop.h"
 #include "services/video_capture/public/cpp/mock_receiver.h"
+#include "services/video_capture/public/mojom/device.mojom.h"
 #include "services/video_capture/test/fake_device_descriptor_test.h"
 
 using testing::_;
diff --git a/services/video_capture/test/fake_device_test.h b/services/video_capture/test/fake_device_test.h
index eb9dd01d..f9974e1 100644
--- a/services/video_capture/test/fake_device_test.h
+++ b/services/video_capture/test/fake_device_test.h
@@ -6,6 +6,7 @@
 #define SERVICES_VIDEO_CAPTURE_TEST_FAKE_DEVICE_TEST_H_
 
 #include "media/capture/video_capture_types.h"
+#include "services/video_capture/public/mojom/device.mojom.h"
 #include "services/video_capture/test/fake_device_descriptor_test.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
diff --git a/services/video_capture/test/mock_device_shared_access_unittest.cc b/services/video_capture/test/mock_device_shared_access_unittest.cc
index 5ef70c8f..da8e46f 100644
--- a/services/video_capture/test/mock_device_shared_access_unittest.cc
+++ b/services/video_capture/test/mock_device_shared_access_unittest.cc
@@ -16,6 +16,7 @@
 #include "services/video_capture/device_factory_media_to_mojo_adapter.h"
 #include "services/video_capture/public/cpp/mock_receiver.h"
 #include "services/video_capture/public/mojom/device_factory_provider.mojom.h"
+#include "services/video_capture/public/mojom/video_source.mojom.h"
 #include "services/video_capture/video_source_provider_impl.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/services/video_capture/test/mock_device_test.h b/services/video_capture/test/mock_device_test.h
index 4932f03..66055d83 100644
--- a/services/video_capture/test/mock_device_test.h
+++ b/services/video_capture/test/mock_device_test.h
@@ -12,6 +12,7 @@
 #include "services/service_manager/public/cpp/service_keepalive.h"
 #include "services/video_capture/device_factory_media_to_mojo_adapter.h"
 #include "services/video_capture/public/cpp/mock_receiver.h"
+#include "services/video_capture/public/mojom/device.mojom.h"
 #include "services/video_capture/public/mojom/device_factory_provider.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/services/video_capture/texture_virtual_device_mojo_adapter.cc b/services/video_capture/texture_virtual_device_mojo_adapter.cc
index 2a45c4f..dcbb225 100644
--- a/services/video_capture/texture_virtual_device_mojo_adapter.cc
+++ b/services/video_capture/texture_virtual_device_mojo_adapter.cc
@@ -11,6 +11,7 @@
 #include "mojo/public/cpp/bindings/callback_helpers.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "services/video_capture/public/mojom/constants.mojom.h"
+#include "services/video_capture/public/mojom/scoped_access_permission.mojom.h"
 
 namespace video_capture {
 
diff --git a/services/video_capture/texture_virtual_device_mojo_adapter.h b/services/video_capture/texture_virtual_device_mojo_adapter.h
index 16a7de6..2c41dcc 100644
--- a/services/video_capture/texture_virtual_device_mojo_adapter.h
+++ b/services/video_capture/texture_virtual_device_mojo_adapter.h
@@ -11,6 +11,7 @@
 #include "services/service_manager/public/cpp/service_context_ref.h"
 #include "services/video_capture/public/mojom/device.mojom.h"
 #include "services/video_capture/public/mojom/producer.mojom.h"
+#include "services/video_capture/public/mojom/receiver.mojom.h"
 #include "services/video_capture/public/mojom/virtual_device.mojom.h"
 
 namespace video_capture {
diff --git a/services/video_capture/video_source_impl.h b/services/video_capture/video_source_impl.h
index 16e80f2..950c281 100644
--- a/services/video_capture/video_source_impl.h
+++ b/services/video_capture/video_source_impl.h
@@ -12,6 +12,8 @@
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "services/video_capture/broadcasting_receiver.h"
 #include "services/video_capture/device_factory_media_to_mojo_adapter.h"
+#include "services/video_capture/public/mojom/device.mojom.h"
+#include "services/video_capture/public/mojom/video_source.mojom.h"
 #include "services/video_capture/public/mojom/video_source_provider.mojom.h"
 
 namespace video_capture {
diff --git a/services/video_capture/video_source_provider_impl.cc b/services/video_capture/video_source_provider_impl.cc
index e43050ba..3c395a3c 100644
--- a/services/video_capture/video_source_provider_impl.cc
+++ b/services/video_capture/video_source_provider_impl.cc
@@ -5,6 +5,7 @@
 #include "services/video_capture/video_source_provider_impl.h"
 
 #include "base/bind.h"
+#include "services/video_capture/public/mojom/producer.mojom.h"
 #include "services/video_capture/video_source_impl.h"
 #include "services/video_capture/virtual_device_enabled_device_factory.h"
 
diff --git a/services/ws/public/mojom/ime/ime_struct_traits_unittest.cc b/services/ws/public/mojom/ime/ime_struct_traits_unittest.cc
index 25fa17e..656d3d3 100644
--- a/services/ws/public/mojom/ime/ime_struct_traits_unittest.cc
+++ b/services/ws/public/mojom/ime/ime_struct_traits_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "mojo/public/cpp/base/string16_mojom_traits.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
+#include "services/ws/public/mojom/ime/ime.mojom.h"
 #include "services/ws/public/mojom/ime/ime_struct_traits_test.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/ime/composition_text.h"
diff --git a/testing/buildbot/chromium.webrtc.fyi.json b/testing/buildbot/chromium.webrtc.fyi.json
index 4951a90d..28d934b 100644
--- a/testing/buildbot/chromium.webrtc.fyi.json
+++ b/testing/buildbot/chromium.webrtc.fyi.json
@@ -75,18 +75,6 @@
     "gtest_tests": [
       {
         "args": [
-          "--gtest_filter=WebRtcApprtcBrowserTest.*",
-          "--run-manual",
-          "--test-launcher-jobs=1"
-        ],
-        "name": "browser_tests_apprtc",
-        "swarming": {
-          "can_use_on_swarming_builders": false
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc_functional.browser_tests.filter",
           "--run-manual",
           "--test-launcher-jobs=1"
@@ -105,6 +93,23 @@
       },
       {
         "args": [
+          "--gtest_filter=WebRtcApprtcBrowserTest.*",
+          "--run-manual",
+          "--test-launcher-jobs=1"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Ubuntu-14.04"
+            }
+          ]
+        },
+        "test": "browser_tests_apprtc"
+      },
+      {
+        "args": [
           "--gtest_filter=WebRtc*"
         ],
         "swarming": {
@@ -202,18 +207,6 @@
     "gtest_tests": [
       {
         "args": [
-          "--gtest_filter=WebRtcApprtcBrowserTest.*",
-          "--run-manual",
-          "--test-launcher-jobs=1"
-        ],
-        "name": "browser_tests_apprtc",
-        "swarming": {
-          "can_use_on_swarming_builders": false
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc_functional.browser_tests.filter",
           "--run-manual",
           "--test-launcher-jobs=1"
@@ -232,6 +225,23 @@
       },
       {
         "args": [
+          "--gtest_filter=WebRtcApprtcBrowserTest.*",
+          "--run-manual",
+          "--test-launcher-jobs=1"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:0a2e",
+              "os": "Mac-10.12.6"
+            }
+          ]
+        },
+        "test": "browser_tests_apprtc"
+      },
+      {
+        "args": [
           "--gtest_filter=WebRtc*"
         ],
         "swarming": {
@@ -329,18 +339,6 @@
     "gtest_tests": [
       {
         "args": [
-          "--gtest_filter=WebRtcApprtcBrowserTest.*",
-          "--run-manual",
-          "--test-launcher-jobs=1"
-        ],
-        "name": "browser_tests_apprtc",
-        "swarming": {
-          "can_use_on_swarming_builders": false
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc_functional.browser_tests.filter",
           "--run-manual",
           "--test-launcher-jobs=1"
@@ -359,6 +357,23 @@
       },
       {
         "args": [
+          "--gtest_filter=WebRtcApprtcBrowserTest.*",
+          "--run-manual",
+          "--test-launcher-jobs=1"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
+        },
+        "test": "browser_tests_apprtc"
+      },
+      {
+        "args": [
           "--gtest_filter=WebRtc*"
         ],
         "swarming": {
@@ -438,18 +453,6 @@
     "gtest_tests": [
       {
         "args": [
-          "--gtest_filter=WebRtcApprtcBrowserTest.*",
-          "--run-manual",
-          "--test-launcher-jobs=1"
-        ],
-        "name": "browser_tests_apprtc",
-        "swarming": {
-          "can_use_on_swarming_builders": false
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc_functional.browser_tests.filter",
           "--run-manual",
           "--test-launcher-jobs=1"
@@ -468,6 +471,23 @@
       },
       {
         "args": [
+          "--gtest_filter=WebRtcApprtcBrowserTest.*",
+          "--run-manual",
+          "--test-launcher-jobs=1"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-7-SP1"
+            }
+          ]
+        },
+        "test": "browser_tests_apprtc"
+      },
+      {
+        "args": [
           "--gtest_filter=WebRtc*"
         ],
         "swarming": {
@@ -547,18 +567,6 @@
     "gtest_tests": [
       {
         "args": [
-          "--gtest_filter=WebRtcApprtcBrowserTest.*",
-          "--run-manual",
-          "--test-launcher-jobs=1"
-        ],
-        "name": "browser_tests_apprtc",
-        "swarming": {
-          "can_use_on_swarming_builders": false
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc_functional.browser_tests.filter",
           "--run-manual",
           "--test-launcher-jobs=1"
@@ -577,6 +585,23 @@
       },
       {
         "args": [
+          "--gtest_filter=WebRtcApprtcBrowserTest.*",
+          "--run-manual",
+          "--test-launcher-jobs=1"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-8.1-SP0"
+            }
+          ]
+        },
+        "test": "browser_tests_apprtc"
+      },
+      {
+        "args": [
           "--gtest_filter=WebRtc*"
         ],
         "swarming": {
diff --git a/testing/buildbot/chromium.webrtc.json b/testing/buildbot/chromium.webrtc.json
index 27e271c..56d32ce 100644
--- a/testing/buildbot/chromium.webrtc.json
+++ b/testing/buildbot/chromium.webrtc.json
@@ -38,18 +38,6 @@
     "gtest_tests": [
       {
         "args": [
-          "--gtest_filter=WebRtcApprtcBrowserTest.*",
-          "--run-manual",
-          "--test-launcher-jobs=1"
-        ],
-        "name": "browser_tests_apprtc",
-        "swarming": {
-          "can_use_on_swarming_builders": false
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc_functional.browser_tests.filter",
           "--run-manual",
           "--test-launcher-jobs=1"
@@ -62,6 +50,17 @@
       },
       {
         "args": [
+          "--gtest_filter=WebRtcApprtcBrowserTest.*",
+          "--run-manual",
+          "--test-launcher-jobs=1"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "browser_tests_apprtc"
+      },
+      {
+        "args": [
           "--enable-logging",
           "--v=1",
           "--test-launcher-jobs=1",
@@ -142,18 +141,6 @@
     "gtest_tests": [
       {
         "args": [
-          "--gtest_filter=WebRtcApprtcBrowserTest.*",
-          "--run-manual",
-          "--test-launcher-jobs=1"
-        ],
-        "name": "browser_tests_apprtc",
-        "swarming": {
-          "can_use_on_swarming_builders": false
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc_functional.browser_tests.filter",
           "--run-manual",
           "--test-launcher-jobs=1"
@@ -166,6 +153,17 @@
       },
       {
         "args": [
+          "--gtest_filter=WebRtcApprtcBrowserTest.*",
+          "--run-manual",
+          "--test-launcher-jobs=1"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "browser_tests_apprtc"
+      },
+      {
+        "args": [
           "--enable-logging",
           "--v=1",
           "--test-launcher-jobs=1",
@@ -246,18 +244,6 @@
     "gtest_tests": [
       {
         "args": [
-          "--gtest_filter=WebRtcApprtcBrowserTest.*",
-          "--run-manual",
-          "--test-launcher-jobs=1"
-        ],
-        "name": "browser_tests_apprtc",
-        "swarming": {
-          "can_use_on_swarming_builders": false
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc_functional.browser_tests.filter",
           "--run-manual",
           "--test-launcher-jobs=1"
@@ -270,6 +256,17 @@
       },
       {
         "args": [
+          "--gtest_filter=WebRtcApprtcBrowserTest.*",
+          "--run-manual",
+          "--test-launcher-jobs=1"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "browser_tests_apprtc"
+      },
+      {
+        "args": [
           "--enable-logging",
           "--v=1",
           "--test-launcher-jobs=1",
@@ -343,18 +340,6 @@
     "gtest_tests": [
       {
         "args": [
-          "--gtest_filter=WebRtcApprtcBrowserTest.*",
-          "--run-manual",
-          "--test-launcher-jobs=1"
-        ],
-        "name": "browser_tests_apprtc",
-        "swarming": {
-          "can_use_on_swarming_builders": false
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc_functional.browser_tests.filter",
           "--run-manual",
           "--test-launcher-jobs=1"
@@ -367,6 +352,17 @@
       },
       {
         "args": [
+          "--gtest_filter=WebRtcApprtcBrowserTest.*",
+          "--run-manual",
+          "--test-launcher-jobs=1"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "browser_tests_apprtc"
+      },
+      {
+        "args": [
           "--enable-logging",
           "--v=1",
           "--test-launcher-jobs=1",
@@ -440,18 +436,6 @@
     "gtest_tests": [
       {
         "args": [
-          "--gtest_filter=WebRtcApprtcBrowserTest.*",
-          "--run-manual",
-          "--test-launcher-jobs=1"
-        ],
-        "name": "browser_tests_apprtc",
-        "swarming": {
-          "can_use_on_swarming_builders": false
-        },
-        "test": "browser_tests"
-      },
-      {
-        "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/webrtc_functional.browser_tests.filter",
           "--run-manual",
           "--test-launcher-jobs=1"
@@ -464,6 +448,17 @@
       },
       {
         "args": [
+          "--gtest_filter=WebRtcApprtcBrowserTest.*",
+          "--run-manual",
+          "--test-launcher-jobs=1"
+        ],
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        },
+        "test": "browser_tests_apprtc"
+      },
+      {
+        "args": [
           "--enable-logging",
           "--v=1",
           "--test-launcher-jobs=1",
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index f2039308..bfc4da6 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -405,6 +405,11 @@
     "label": "//chrome/test:browser_tests",
     "type": "windowed_test_launcher",
   },
+  "browser_tests_apprtc": {
+    "label": "//chrome/test:browser_tests_apprtc",
+    "type": "windowed_test_launcher",
+    "executable": "browser_tests",
+  },
   "cacheinvalidation_unittests": {
     "label": "//third_party/cacheinvalidation:cacheinvalidation_unittests",
     "type": "console_test_launcher",
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index f43c84e..930e5a0 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -4302,11 +4302,6 @@
       'browser_tests_apprtc': {
         'args': ['--gtest_filter=WebRtcApprtcBrowserTest.*',
                  '--run-manual', '--test-launcher-jobs=1'],
-        'test': 'browser_tests',
-        # TODO(crbug.com/888429): Port this test to swarming.
-        'swarming': {
-          'can_use_on_swarming_builders': False,
-        },
       },
       'content_unittests': {
         'args': [
diff --git a/third_party/blink/common/DEPS b/third_party/blink/common/DEPS
index b64c82e..83711aa 100644
--- a/third_party/blink/common/DEPS
+++ b/third_party/blink/common/DEPS
@@ -13,6 +13,7 @@
     "+mojo",
     "+services/metrics/public/cpp",
     "+services/network/public/cpp",
+    "+services/network/public/mojom/url_loader.mojom.h",
     "+skia/public/interfaces/bitmap_skbitmap_struct_traits.h",
     "+testing/gmock/include/gmock",
     "+testing/gtest/include/gtest",
diff --git a/third_party/blink/common/loader/url_loader_factory_bundle.cc b/third_party/blink/common/loader/url_loader_factory_bundle.cc
index acab90d..45b5841 100644
--- a/third_party/blink/common/loader/url_loader_factory_bundle.cc
+++ b/third_party/blink/common/loader/url_loader_factory_bundle.cc
@@ -8,6 +8,7 @@
 
 #include "base/logging.h"
 #include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
 #include "url/gurl.h"
 
 namespace blink {
diff --git a/third_party/blink/public/mojom/payments/payment_request.mojom b/third_party/blink/public/mojom/payments/payment_request.mojom
index 9135e62..2bb404f 100644
--- a/third_party/blink/public/mojom/payments/payment_request.mojom
+++ b/third_party/blink/public/mojom/payments/payment_request.mojom
@@ -41,6 +41,12 @@
 enum CanMakePaymentQueryResult {
   CAN_MAKE_PAYMENT,
   CANNOT_MAKE_PAYMENT,
+  QUERY_QUOTA_EXCEEDED,
+
+  // Used only on localhost and file:// schemes to warn web developer that the
+  // query quota has been exceeded, but Chrome is returning an answer anyway.
+  WARNING_CAN_MAKE_PAYMENT,
+  WARNING_CANNOT_MAKE_PAYMENT,
 };
 
 enum HasEnrolledInstrumentQueryResult {
@@ -228,8 +234,9 @@
   // Queries whether support for the merchant-specified payment method is
   // available, either because the user has a registered payment handler for
   // that method, or because the browser can do just-in-time registration for a
-  // suitable payment handler.
-  CanMakePayment();
+  // suitable payment handler. If |legacy_mode| is true, provide the legacy
+  // behavior, which also checks that the user has an enrolled instrument.
+  CanMakePayment(bool legacy_mode);
 
   // Queries whether support for the merchant-specified payment method is
   // available and the user has an enrolled instrument for that payment method
diff --git a/third_party/blink/public/mojom/service_worker/service_worker_provider.mojom b/third_party/blink/public/mojom/service_worker/service_worker_provider.mojom
index 3dd4665..dcd55e3 100644
--- a/third_party/blink/public/mojom/service_worker/service_worker_provider.mojom
+++ b/third_party/blink/public/mojom/service_worker/service_worker_provider.mojom
@@ -18,9 +18,8 @@
 const string kNavigation_ServiceWorkerSpec = "navigation:service_worker";
 
 // S13nServiceWorker:
-// Sent from the browser process to the renderer. Contains parameteres for the
-// constructor of ServiceWorkerNetworkProvider used for starting a shared
-// worker.
+// Sent from the browser process to the renderer. Contains parameters for the
+// WebServiceWorkerNetworkProvider used for starting a shared worker.
 struct ServiceWorkerProviderInfoForSharedWorker {
   int32 provider_id;
   associated ServiceWorkerContainerHost host_ptr_info;
@@ -28,8 +27,7 @@
 };
 
 // Sent from the browser process to the renderer. Contains parameters for the
-// constructor of ServiceWorkerNetworkProvider used for starting a service
-// worker.
+// WebServiceWorkerNetworkProvider used for starting a service worker.
 struct ServiceWorkerProviderInfoForStartWorker {
   int32 provider_id;
 
diff --git a/third_party/blink/public/mojom/worker/shared_worker_factory.mojom b/third_party/blink/public/mojom/worker/shared_worker_factory.mojom
index 9725408..6a5a3ec 100644
--- a/third_party/blink/public/mojom/worker/shared_worker_factory.mojom
+++ b/third_party/blink/public/mojom/worker/shared_worker_factory.mojom
@@ -61,7 +61,8 @@
       //
       // TODO(leonhsl): It doesn't really need to be associated. Make it
       // non-associated and update
-      // ServiceWorkerNetworkProvider::script_loader_factory_ as well.
+      // WebServiceWorkerNetworkProviderImplForWorker::script_loader_factory_
+      // as well.
       associated network.mojom.URLLoaderFactory? main_script_loader_factory,
 
       // NetworkService (PlzWorker):
diff --git a/third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h b/third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h
index b1622f2..ba68f8e 100644
--- a/third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h
+++ b/third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h
@@ -46,7 +46,7 @@
 // thread. Currently the embedder has implementations for service worker clients
 // (frames and shared workers), and service workers themselves.
 //
-// An instance of this class is owned by the associated loading context, e.g.
+// Instances of this class are owned by the associated loading context, e.g.
 // DocumentLoader.
 class WebServiceWorkerNetworkProvider {
  public:
diff --git a/third_party/blink/public/web/modules/service_worker/web_service_worker_context_client.h b/third_party/blink/public/web/modules/service_worker/web_service_worker_context_client.h
index 732f423..5e060fd 100644
--- a/third_party/blink/public/web/modules/service_worker/web_service_worker_context_client.h
+++ b/third_party/blink/public/web/modules/service_worker/web_service_worker_context_client.h
@@ -78,22 +78,32 @@
   // failed.
   virtual void FailedToFetchModuleScript() {}
 
-  // The worker script successfully loaded. Called on the main thread when the
-  // script is served from ResourceLoader or on the worker thread when the
-  // script is served via WebServiceWorkerInstalledScriptsManager.
+  // The worker script was successfully loaded by ResourceLoader. Called on the
+  // main thread.
   //
-  // This may be called before or after WorkerContextStarted(). Script
-  // evaluation does not start until WillEvaluateScript().
+  // This is called before WorkerContextStarted(). Script evaluation does not
+  // start until WillEvaluateScript().
   virtual void WorkerScriptLoaded() {}
 
+  // The worker script was successfully read from
+  // WebServiceWorkerInstalledScriptsManager. Called on the worker thread.
+  //
+  // This is called after WorkerContextStarted(). Script evaluation does not
+  // start until WillEvaluateScript().
+  virtual void InstalledWorkerScriptLoaded() {}
+
   // Called when a WorkerGlobalScope was created for the worker thread. This
   // also gives a proxy to the embedder to talk to the newly created
   // WorkerGlobalScope. The proxy is owned by WorkerGlobalScope and should not
   // be destroyed by the caller. No proxy methods should be called after
   // willDestroyWorkerContext() is called.
   //
-  // This may be called before or after WorkerScriptLoaded(). Script evaluation
-  // does not start until WillEvaluateScript().
+  // For new workers (on-main-thread script fetch), this is called after
+  // WorkerScriptLoaded().
+  //
+  // For installed workers, this is called before InstalledWorkerScriptLoaded().
+  //
+  // Script evaluation does not start until WillEvaluateScript().
   virtual void WorkerContextStarted(WebServiceWorkerContextProxy*) {}
 
   // Called immediately before V8 script evaluation starts for the main script.
diff --git a/third_party/blink/public/web/web_shared_worker_client.h b/third_party/blink/public/web/web_shared_worker_client.h
index 88f7e32..ab767347 100644
--- a/third_party/blink/public/web/web_shared_worker_client.h
+++ b/third_party/blink/public/web/web_shared_worker_client.h
@@ -64,9 +64,9 @@
       WebApplicationCacheHostClient*) = 0;
 
   // Called on the main thread during initialization, before requesting the main
-  // script resource. Creates the ServiceWorkerNetworkProvider which is used for
-  // script loading (i.e., the main script and importScripts). Other requests
-  // (e.g., fetch and XHR) go through WebWorkerFetchContext.
+  // script resource. Creates the WebServiceWorkerNetworkProvider which is used
+  // for script loading (i.e., the main script and importScripts). Other
+  // requests (e.g., fetch and XHR) go through WebWorkerFetchContext.
   virtual std::unique_ptr<WebServiceWorkerNetworkProvider>
   CreateServiceWorkerNetworkProvider() = 0;
 
diff --git a/third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.cc b/third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.cc
index bf65386a..e352fa3e 100644
--- a/third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.cc
+++ b/third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.cc
@@ -140,13 +140,12 @@
   script_state_->DissociateContext();
 }
 
-bool WorkerOrWorkletScriptController::InitializeContextIfNeeded(
+bool WorkerOrWorkletScriptController::InitializeContext(
     const String& human_readable_name,
     const KURL& url_for_debugger) {
   v8::HandleScope handle_scope(isolate_);
 
-  if (IsContextInitialized())
-    return true;
+  DCHECK(!IsContextInitialized());
 
   // Create a new v8::Context with the worker/worklet as the global object
   // (aka the inner global).
diff --git a/third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h b/third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h
index 57718e0f2..54f8fab 100644
--- a/third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h
+++ b/third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h
@@ -71,14 +71,13 @@
   // Prevents future JavaScript execution.
   void ForbidExecution();
 
-  // Used by WorkerThread. Returns true if the context is successfully
-  // initialized or already initialized.
   // For WorkerGlobalScope and threaded WorkletGlobalScope, |url_for_debugger|
   // is and should be used only for setting name/origin that appears in
   // DevTools. For other global scopes, |human_readable_name| is used for
   // setting DOMWrapperWorld's human readable name.
-  bool InitializeContextIfNeeded(const String& human_readable_name,
-                                 const KURL& url_for_debugger);
+  // This should be called only once.
+  bool InitializeContext(const String& human_readable_name,
+                         const KURL& url_for_debugger);
 
   // Used by WorkerGlobalScope:
   void RethrowExceptionFromImportedScript(ErrorEvent*, ExceptionState&);
diff --git a/third_party/blink/renderer/core/editing/finder/find_buffer.cc b/third_party/blink/renderer/core/editing/finder/find_buffer.cc
index f01bc0f..6030414 100644
--- a/third_party/blink/renderer/core/editing/finder/find_buffer.cc
+++ b/third_party/blink/renderer/core/editing/finder/find_buffer.cc
@@ -34,16 +34,17 @@
 
 FindBuffer::Results::Results(const Vector<UChar>& buffer,
                              String search_text,
-                             const mojom::blink::FindOptions& options) {
+                             const blink::FindOptions options) {
   // We need to own the |search_text| because |text_searcher_| only has a
   // StringView (doesn't own the search text).
   search_text_ = search_text;
-  text_searcher_.SetPattern(search_text_, options.match_case);
+  text_searcher_.SetPattern(search_text_, !(options & kCaseInsensitive));
   text_searcher_.SetText(buffer.data(), buffer.size());
   text_searcher_.SetOffset(0);
 }
 
-FindBuffer::Results::Iterator::Iterator(TextSearcherICU* text_searcher)
+FindBuffer::Results::Iterator::Iterator(TextSearcherICU* text_searcher,
+                                        String search_text)
     : text_searcher_(text_searcher), has_match_(true) {
   operator++();
 }
@@ -63,7 +64,7 @@
   if (empty_result_)
     return end();
   text_searcher_.SetOffset(0);
-  return Iterator(&text_searcher_);
+  return Iterator(&text_searcher_, search_text_);
 }
 
 FindBuffer::Results::Iterator FindBuffer::Results::end() const {
@@ -186,7 +187,7 @@
 
 std::unique_ptr<FindBuffer::Results> FindBuffer::FindMatches(
     const WebString& search_text,
-    const mojom::blink::FindOptions& options) const {
+    const blink::FindOptions options) const {
   if (buffer_.IsEmpty() || search_text.length() > buffer_.size())
     return std::make_unique<Results>();
   String search_text_16_bit = search_text;
diff --git a/third_party/blink/renderer/core/editing/finder/find_buffer.h b/third_party/blink/renderer/core/editing/finder/find_buffer.h
index a6b8bc3..439627b 100644
--- a/third_party/blink/renderer/core/editing/finder/find_buffer.h
+++ b/third_party/blink/renderer/core/editing/finder/find_buffer.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_FINDER_FIND_BUFFER_H_
 
 #include "third_party/blink/public/mojom/frame/find_in_page.mojom-blink.h"
+#include "third_party/blink/renderer/core/editing/finder/find_options.h"
 #include "third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h"
 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
 
@@ -39,13 +40,13 @@
 
     Results(const Vector<UChar>& buffer,
             String search_text,
-            const mojom::blink::FindOptions& options);
+            const blink::FindOptions options);
 
     class CORE_EXPORT Iterator
         : public std::iterator<std::forward_iterator_tag, BufferMatchResult> {
      public:
       Iterator() = default;
-      Iterator(TextSearcherICU* text_searcher);
+      Iterator(TextSearcherICU* text_searcher, String search_text_);
 
       bool operator==(const Iterator& other) {
         return has_match_ == other.has_match_;
@@ -78,9 +79,8 @@
   };
 
   // Finds all the match for |search_text| in |buffer_|.
-  std::unique_ptr<Results> FindMatches(
-      const WebString& search_text,
-      const mojom::blink::FindOptions& options) const;
+  std::unique_ptr<Results> FindMatches(const WebString& search_text,
+                                       const blink::FindOptions options) const;
 
   // Gets a flat tree range corresponding to text in the [start_index,
   // end_index) of |buffer|.
diff --git a/third_party/blink/renderer/core/editing/finder/find_buffer_test.cc b/third_party/blink/renderer/core/editing/finder/find_buffer_test.cc
index 1eb23e6..32f4c47 100644
--- a/third_party/blink/renderer/core/editing/finder/find_buffer_test.cc
+++ b/third_party/blink/renderer/core/editing/finder/find_buffer_test.cc
@@ -44,7 +44,7 @@
   FindBuffer buffer(WholeDocumentRange());
   EXPECT_TRUE(buffer.PositionAfterBlock().IsNull());
   std::unique_ptr<FindBuffer::Results> results =
-      buffer.FindMatches("abce", *mojom::blink::FindOptions::New());
+      buffer.FindMatches("abce", kCaseInsensitive);
   EXPECT_EQ(1u, results->CountForTesting());
   FindBuffer::BufferMatchResult match = *results->begin();
   EXPECT_EQ(0u, match.start);
@@ -143,27 +143,19 @@
   PositionInFlatTree end_position =
       PositionInFlatTree::LastPositionInNode(*node);
   FindBuffer buffer(EphemeralRangeInFlatTree(start_position, end_position));
-  EXPECT_EQ(1u, buffer.FindMatches("foo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(2u, buffer.FindMatches("oo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(4u, buffer.FindMatches("o", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("f", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(4u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting());
   // |start_position| = fo|ofoo
   // |end_position| = foof|oo
   start_position = PositionInFlatTree(*node, 2u);
   end_position = PositionInFlatTree(*node, 4u);
   buffer = FindBuffer(EphemeralRangeInFlatTree(start_position, end_position));
-  EXPECT_EQ(0u, buffer.FindMatches("foo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("oo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("o", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("f", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting());
 }
 
 TEST_F(FindBufferTest, FindBetweenPositionsDifferentNodes) {
@@ -177,69 +169,45 @@
   FindBuffer buffer(EphemeralRangeInFlatTree(
       PositionInFlatTree::FirstPositionInNode(*div->firstChild()),
       PositionInFlatTree::LastPositionInNode(*span->firstChild())));
-  EXPECT_EQ(2u, buffer.FindMatches("foo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(2u, buffer.FindMatches("fo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(2u, buffer.FindMatches("oof", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(2u, buffer.FindMatches("oo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(4u, buffer.FindMatches("o", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(3u, buffer.FindMatches("f", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("fo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("oof", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(4u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(3u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting());
   // <div>f^oo<span>foof<b>o|o</b></span></div>
   // So buffer = "oofoofo"
   buffer = FindBuffer(
       EphemeralRangeInFlatTree(PositionInFlatTree(*div->firstChild(), 1),
                                PositionInFlatTree(*b->firstChild(), 1)));
-  EXPECT_EQ(1u, buffer.FindMatches("foo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(2u, buffer.FindMatches("oof", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(2u, buffer.FindMatches("fo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(2u, buffer.FindMatches("oo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(5u, buffer.FindMatches("o", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(2u, buffer.FindMatches("f", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("oof", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("fo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(5u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting());
   // <div>foo<span>f^oof|<b>oo</b></span></div>
   // So buffer = "oof"
   buffer = FindBuffer(
       EphemeralRangeInFlatTree(PositionInFlatTree(*span->firstChild(), 1),
                                PositionInFlatTree(*span->firstChild(), 4)));
-  EXPECT_EQ(0u, buffer.FindMatches("foo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("oof", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("fo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("oo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(2u, buffer.FindMatches("o", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("f", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("oof", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("fo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting());
   // <div>foo<span>foof^<b>oo|</b></span></div>
   // So buffer = "oo"
   buffer = FindBuffer(
       EphemeralRangeInFlatTree(PositionInFlatTree(*span->firstChild(), 4),
                                PositionInFlatTree(*b->firstChild(), 2)));
-  EXPECT_EQ(0u, buffer.FindMatches("foo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("oof", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("fo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("oo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(2u, buffer.FindMatches("o", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("f", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("oof", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("fo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting());
 }
 
 TEST_F(FindBufferTest, FindBetweenPositionsSkippedNodes) {
@@ -258,60 +226,42 @@
   FindBuffer buffer(EphemeralRangeInFlatTree(
       PositionInFlatTree::FirstPositionInNode(*div->firstChild()),
       PositionInFlatTree(*span->firstChild(), 3)));
-  EXPECT_EQ(1u, buffer.FindMatches("foo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("oof", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("fo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("oo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(2u, buffer.FindMatches("o", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("f", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("oof", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("fo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting());
   // <div>foo<span style='display:none;'>f^oof</span><b>oo|</b>
   // <script>fo</script><a>o</a></div>
   // So buffer = "oo"
   buffer = FindBuffer(
       EphemeralRangeInFlatTree(PositionInFlatTree(*span->firstChild(), 1),
                                PositionInFlatTree(*b->firstChild(), 2)));
-  EXPECT_EQ(0u, buffer.FindMatches("foo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("oo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(2u, buffer.FindMatches("o", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("f", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting());
   // <div>foo<span style='display:none;'>f^oof</span><b>oo|</b>
   // <script>f|o</script><a>o</a></div>
   // So buffer = "oo"
   buffer = FindBuffer(
       EphemeralRangeInFlatTree(PositionInFlatTree(*span->firstChild(), 1),
                                PositionInFlatTree(*script->firstChild(), 2)));
-  EXPECT_EQ(0u, buffer.FindMatches("foo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("oo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(2u, buffer.FindMatches("o", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("f", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(2u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting());
   // <div>foo<span style='display:none;'>foof</span><b>oo|</b>
   // <script>f^o</script><a>o|</a></div>
   // So buffer = "o"
   buffer = FindBuffer(
       EphemeralRangeInFlatTree(PositionInFlatTree(*script->firstChild(), 1),
                                PositionInFlatTree(*a->firstChild(), 1)));
-  EXPECT_EQ(0u, buffer.FindMatches("foo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("oo", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("o", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("f", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting());
 }
 
 class FindBufferBlockTest : public FindBufferTest,
@@ -332,52 +282,38 @@
   FindBuffer text_buffer(WholeDocumentRange());
   EXPECT_EQ(GetElementById("block"),
             *text_buffer.PositionAfterBlock().ComputeContainerNode());
-  EXPECT_EQ(1u,
-            text_buffer.FindMatches("text", *mojom::blink::FindOptions::New())
-                ->CountForTesting());
-  EXPECT_EQ(0u, text_buffer
-                    .FindMatches("textblock", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
   EXPECT_EQ(
-      0u,
-      text_buffer.FindMatches("text block", *mojom::blink::FindOptions::New())
-          ->CountForTesting());
+      1u, text_buffer.FindMatches("text", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, text_buffer.FindMatches("textblock", kCaseInsensitive)
+                    ->CountForTesting());
+  EXPECT_EQ(0u, text_buffer.FindMatches("text block", kCaseInsensitive)
+                    ->CountForTesting());
 
   FindBuffer block_buffer(EphemeralRangeInFlatTree(
       text_buffer.PositionAfterBlock(), LastPositionInDocument()));
   EXPECT_EQ(GetElementById("span"),
             *block_buffer.PositionAfterBlock().ComputeContainerNode());
   EXPECT_EQ(
-      1u, block_buffer.FindMatches("block", *mojom::blink::FindOptions::New())
-              ->CountForTesting());
-  EXPECT_EQ(0u, block_buffer
-                    .FindMatches("textblock", *mojom::blink::FindOptions::New())
+      1u,
+      block_buffer.FindMatches("block", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, block_buffer.FindMatches("textblock", kCaseInsensitive)
                     ->CountForTesting());
-  EXPECT_EQ(
-      0u,
-      block_buffer.FindMatches("text block", *mojom::blink::FindOptions::New())
-          ->CountForTesting());
-  EXPECT_EQ(0u, block_buffer
-                    .FindMatches("blockspan", *mojom::blink::FindOptions::New())
+  EXPECT_EQ(0u, block_buffer.FindMatches("text block", kCaseInsensitive)
                     ->CountForTesting());
-  EXPECT_EQ(
-      0u,
-      block_buffer.FindMatches("block span", *mojom::blink::FindOptions::New())
-          ->CountForTesting());
+  EXPECT_EQ(0u, block_buffer.FindMatches("blockspan", kCaseInsensitive)
+                    ->CountForTesting());
+  EXPECT_EQ(0u, block_buffer.FindMatches("block span", kCaseInsensitive)
+                    ->CountForTesting());
 
   FindBuffer span_buffer(EphemeralRangeInFlatTree(
       block_buffer.PositionAfterBlock(), LastPositionInDocument()));
   EXPECT_TRUE(span_buffer.PositionAfterBlock().IsNull());
-  EXPECT_EQ(1u,
-            span_buffer.FindMatches("span", *mojom::blink::FindOptions::New())
-                ->CountForTesting());
-  EXPECT_EQ(0u, span_buffer
-                    .FindMatches("blockspan", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
   EXPECT_EQ(
-      0u,
-      span_buffer.FindMatches("block span", *mojom::blink::FindOptions::New())
-          ->CountForTesting());
+      1u, span_buffer.FindMatches("span", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, span_buffer.FindMatches("blockspan", kCaseInsensitive)
+                    ->CountForTesting());
+  EXPECT_EQ(0u, span_buffer.FindMatches("block span", kCaseInsensitive)
+                    ->CountForTesting());
 }
 
 class FindBufferSeparatorTest
@@ -398,8 +334,7 @@
 TEST_P(FindBufferSeparatorTest, FindSeparatedElements) {
   SetBodyContent("a<" + GetParam() + ">a</" + GetParam() + ">a");
   FindBuffer buffer(WholeDocumentRange());
-  EXPECT_EQ(0u, buffer.FindMatches("aa", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("aa", kCaseInsensitive)->CountForTesting());
 }
 
 TEST_F(FindBufferTest, WhiteSpaceCollapsingPreWrap) {
@@ -408,63 +343,116 @@
       "</span>");
   FindBuffer buffer(WholeDocumentRange());
   EXPECT_EQ(
-      1u, buffer.FindMatches("a b c d  e  ", *mojom::blink::FindOptions::New())
-              ->CountForTesting());
+      1u,
+      buffer.FindMatches("a b c d  e  ", kCaseInsensitive)->CountForTesting());
 };
 
 TEST_F(FindBufferTest, WhiteSpaceCollapsingPre) {
   SetBodyContent("<div style='white-space: pre;'>a \n b</div>");
   FindBuffer buffer(WholeDocumentRange());
-  EXPECT_EQ(1u, buffer.FindMatches("a", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("b", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("ab", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("a b", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("a  b", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("a   b", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("a\n b", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("a \nb", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("a \n b", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("a", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("b", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("ab", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("a b", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u,
+            buffer.FindMatches("a  b", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u,
+            buffer.FindMatches("a   b", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u,
+            buffer.FindMatches("a\n b", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u,
+            buffer.FindMatches("a \nb", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u,
+            buffer.FindMatches("a \n b", kCaseInsensitive)->CountForTesting());
 }
 
 TEST_F(FindBufferTest, WhiteSpaceCollapsingPreLine) {
   SetBodyContent("<div style='white-space: pre-line;'>a \n b</div>");
   FindBuffer buffer(WholeDocumentRange());
-  EXPECT_EQ(1u, buffer.FindMatches("a", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(1u, buffer.FindMatches("b", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("ab", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("a b", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("a  b", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("a   b", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("a \n b", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("a\n b", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("a \nb", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
-  EXPECT_EQ(0u, buffer.FindMatches("a\nb", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("a", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("b", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("ab", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("a b", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u,
+            buffer.FindMatches("a  b", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u,
+            buffer.FindMatches("a   b", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u,
+            buffer.FindMatches("a \n b", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u,
+            buffer.FindMatches("a\n b", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u,
+            buffer.FindMatches("a \nb", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u,
+            buffer.FindMatches("a\nb", kCaseInsensitive)->CountForTesting());
 }
 
 TEST_F(FindBufferTest, BidiTest) {
   SetBodyContent("<bdo dir=rtl id=bdo>foo<span>bar</span></bdo>");
   FindBuffer buffer(WholeDocumentRange());
-  EXPECT_EQ(1u, buffer.FindMatches("foobar", *mojom::blink::FindOptions::New())
-                    ->CountForTesting());
+  EXPECT_EQ(1u,
+            buffer.FindMatches("foobar", kCaseInsensitive)->CountForTesting());
+}
+
+TEST_F(FindBufferTest, KanaSmallVsNormal) {
+  SetBodyContent("や");  // Normal-sized や
+  FindBuffer buffer(WholeDocumentRange());
+  // Should find normal-sized や
+  EXPECT_EQ(1u, buffer.FindMatches("や", kCaseInsensitive)->CountForTesting());
+  // Should not find smalll-sized ゃ
+  EXPECT_EQ(0u, buffer.FindMatches("ゃ", kCaseInsensitive)->CountForTesting());
+}
+
+TEST_F(FindBufferTest, KanaDakuten) {
+  SetBodyContent("びゃ");  // Hiragana bya
+  FindBuffer buffer(WholeDocumentRange());
+  // Should find bi
+  EXPECT_EQ(1u, buffer.FindMatches("び", kCaseInsensitive)->CountForTesting());
+  // Should find smalll-sized ゃ
+  EXPECT_EQ(1u, buffer.FindMatches("ゃ", kCaseInsensitive)->CountForTesting());
+  // Should find bya
+  EXPECT_EQ(1u,
+            buffer.FindMatches("びゃ", kCaseInsensitive)->CountForTesting());
+  // Should not find hi
+  EXPECT_EQ(0u, buffer.FindMatches("ひ", kCaseInsensitive)->CountForTesting());
+  // Should not find pi
+  EXPECT_EQ(0u, buffer.FindMatches("ぴ", kCaseInsensitive)->CountForTesting());
+}
+
+TEST_F(FindBufferTest, KanaHalfFull) {
+  // Should treat hiragana, katakana, half width katakana as the same.
+  // hiragana ra, half width katakana ki, full width katakana na
+  SetBodyContent("らキナ");
+  FindBuffer buffer(WholeDocumentRange());
+  // Should find katakana ra
+  EXPECT_EQ(1u, buffer.FindMatches("ラ", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("ラ", kCaseInsensitive)->CountForTesting());
+  // Should find hiragana & katakana ki
+  EXPECT_EQ(1u, buffer.FindMatches("き", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("キ", kCaseInsensitive)->CountForTesting());
+  // Should find hiragana & katakana na
+  EXPECT_EQ(1u, buffer.FindMatches("な", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("ナ", kCaseInsensitive)->CountForTesting());
+  // Should find whole word
+  EXPECT_EQ(1u,
+            buffer.FindMatches("らきな", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("ラキナ", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u,
+            buffer.FindMatches("ラキナ", kCaseInsensitive)->CountForTesting());
+}
+
+TEST_F(FindBufferTest, KanaDecomposed) {
+  SetBodyContent("は ゛");
+  FindBuffer buffer(WholeDocumentRange());
+  EXPECT_EQ(0u, buffer.FindMatches("ば", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u,
+            buffer.FindMatches("は ゛", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(0u, buffer.FindMatches("バ ", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u,
+            buffer.FindMatches("ハ ゛", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u, buffer.FindMatches("ハ ゙", kCaseInsensitive)->CountForTesting());
+  EXPECT_EQ(1u,
+            buffer.FindMatches("ハ ゛", kCaseInsensitive)->CountForTesting());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/editing/finder/find_task_controller.cc b/third_party/blink/renderer/core/editing/finder/find_task_controller.cc
index 509ee934..81e1f95 100644
--- a/third_party/blink/renderer/core/editing/finder/find_task_controller.cc
+++ b/third_party/blink/renderer/core/editing/finder/find_task_controller.cc
@@ -113,11 +113,17 @@
     int match_count = 0;
     bool full_range_searched = false;
     PositionInFlatTree next_task_start_position;
+
+    blink::FindOptions find_options =
+        (options_->forward ? 0 : kBackwards) |
+        (options_->match_case ? 0 : kCaseInsensitive) |
+        (options_->find_next ? 0 : kStartInSelection);
+
     do {
       // Find in the whole block.
       FindBuffer buffer(EphemeralRangeInFlatTree(search_start, search_end));
       std::unique_ptr<FindBuffer::Results> match_results =
-          buffer.FindMatches(search_text_, *options_);
+          buffer.FindMatches(search_text_, find_options);
       for (FindBuffer::BufferMatchResult match : *match_results) {
         const EphemeralRangeInFlatTree ephemeral_match_range =
             buffer.RangeFromBufferIndex(match.start,
diff --git a/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.cc b/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.cc
index 69dc7ade..4d16b13 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.cc
+++ b/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.cc
@@ -30,6 +30,7 @@
 #include <unicode/usearch.h>
 #include "base/macros.h"
 #include "third_party/blink/renderer/platform/text/text_break_iterator_internal_icu.h"
+#include "third_party/blink/renderer/platform/text/unicode_utilities.h"
 #include "third_party/blink/renderer/platform/wtf/text/character_names.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
@@ -114,6 +115,10 @@
                                  bool case_sensitive) {
   SetCaseSensitivity(case_sensitive);
   SetPattern(pattern.Characters16(), pattern.length());
+  if (ContainsKanaLetters(pattern.ToString())) {
+    NormalizeCharactersIntoNFCForm(pattern.Characters16(), pattern.length(),
+                                   normalized_search_text_);
+  }
 }
 
 void TextSearcherICU::SetText(const UChar* text, wtf_size_t length) {
@@ -130,6 +135,14 @@
 }
 
 bool TextSearcherICU::NextMatchResult(MatchResultICU& result) {
+  while (NextMatchResultInternal(result)) {
+    if (!ShouldSkipCurrentMatch(result))
+      return true;
+  }
+  return false;
+}
+
+bool TextSearcherICU::NextMatchResultInternal(MatchResultICU& result) {
   UErrorCode status = U_ZERO_ERROR;
   const int match_start = usearch_next(searcher_, &status);
   DCHECK_EQ(status, U_ZERO_ERROR);
@@ -149,6 +162,22 @@
   return true;
 }
 
+bool TextSearcherICU::ShouldSkipCurrentMatch(MatchResultICU& result) const {
+  if (normalized_search_text_.IsEmpty())
+    return false;
+  Vector<UChar> normalized_match;
+  int32_t text_length;
+  const UChar* text = usearch_getText(searcher_, &text_length);
+  DCHECK_LE((int32_t)(result.start + result.length), text_length);
+
+  NormalizeCharactersIntoNFCForm(text + result.start, result.length,
+                                 normalized_match);
+
+  return !CheckOnlyKanaLettersInStrings(
+      normalized_search_text_.data(), normalized_search_text_.size(),
+      normalized_match.begin(), normalized_match.size());
+}
+
 void TextSearcherICU::SetPattern(const UChar* pattern, wtf_size_t length) {
   UErrorCode status = U_ZERO_ERROR;
   usearch_setPattern(searcher_, pattern, length, &status);
diff --git a/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h b/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h
index a99fae2..917a8ae 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h
+++ b/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h
@@ -32,9 +32,12 @@
  private:
   void SetPattern(const UChar* pattern, wtf_size_t length);
   void SetCaseSensitivity(bool case_sensitive);
+  bool ShouldSkipCurrentMatch(MatchResultICU&) const;
+  bool NextMatchResultInternal(MatchResultICU&);
 
   UStringSearch* searcher_ = nullptr;
   wtf_size_t text_length_ = 0;
+  Vector<UChar> normalized_search_text_;
 
   DISALLOW_COPY_AND_ASSIGN(TextSearcherICU);
 };
diff --git a/third_party/blink/renderer/core/editing/serializers/markup_formatter.cc b/third_party/blink/renderer/core/editing/serializers/markup_formatter.cc
index 789bf35..22362102 100644
--- a/third_party/blink/renderer/core/editing/serializers/markup_formatter.cc
+++ b/third_party/blink/renderer/core/editing/serializers/markup_formatter.cc
@@ -202,35 +202,20 @@
                                         : kEntityMaskInAttributeValue);
 }
 
-void MarkupFormatter::AppendQuotedURLAttributeValue(
-    StringBuilder& result,
-    const Element& element,
-    const Attribute& attribute) {
-  DCHECK(element.IsURLAttribute(attribute)) << element;
-  String resolved_url_string = ResolveURLIfNeeded(element, attribute.Value());
-  UChar quote_char = '"';
-  if (ProtocolIsJavaScript(resolved_url_string)) {
-    // minimal escaping for javascript urls
-    if (resolved_url_string.Contains('&'))
-      resolved_url_string.Replace('&', "&amp;");
-
-    if (resolved_url_string.Contains('"')) {
-      if (resolved_url_string.Contains('\''))
-        resolved_url_string.Replace('"', "&quot;");
-      else
-        quote_char = '\'';
-    }
-    result.Append(quote_char);
-    result.Append(resolved_url_string);
-    result.Append(quote_char);
-    return;
+void MarkupFormatter::AppendAttribute(StringBuilder& result,
+                                      const AtomicString& prefix,
+                                      const AtomicString& local_name,
+                                      const String& value,
+                                      bool document_is_html) {
+  result.Append(' ');
+  if (!prefix.IsEmpty()) {
+    result.Append(prefix);
+    result.Append(':');
   }
-
-  // FIXME: This does not fully match other browsers. Firefox percent-escapes
-  // non-ASCII characters for innerHTML.
-  result.Append(quote_char);
-  AppendAttributeValue(result, resolved_url_string, false);
-  result.Append(quote_char);
+  result.Append(local_name);
+  result.Append("=\"");
+  AppendAttributeValue(result, value, document_is_html);
+  result.Append('"');
 }
 
 void MarkupFormatter::AppendNamespace(StringBuilder& result,
@@ -241,16 +226,10 @@
   AtomicString found_uri = namespaces.at(lookup_key);
   if (!EqualIgnoringNullity(found_uri, namespace_uri)) {
     namespaces.Set(lookup_key, namespace_uri);
-    result.Append(' ');
-    result.Append(g_xmlns_atom.GetString());
-    if (!prefix.IsEmpty()) {
-      result.Append(':');
-      result.Append(prefix);
-    }
-
-    result.Append("=\"");
-    AppendAttributeValue(result, namespace_uri, false);
-    result.Append('"');
+    if (prefix.IsEmpty())
+      AppendAttribute(result, g_null_atom, g_xmlns_atom, namespace_uri, false);
+    else
+      AppendAttribute(result, g_xmlns_atom, prefix, namespace_uri, false);
   }
 }
 
@@ -354,6 +333,12 @@
                                       const Attribute& attribute,
                                       Namespaces* namespaces) {
   bool document_is_html = SerializeAsHTMLDocument(element);
+  String value = attribute.Value();
+  if (element.IsURLAttribute(attribute)) {
+    // FIXME: This does not fully match other browsers. Firefox percent-escapes
+    // non-ASCII characters for innerHTML.
+    value = ResolveURLIfNeeded(element, value);
+  }
 
   if (document_is_html) {
     // https://html.spec.whatwg.org/multipage/parsing.html#attribute's-serialised-name
@@ -366,8 +351,8 @@
     } else if (attribute.NamespaceURI() == xlink_names::kNamespaceURI) {
       prefixed_name.SetPrefix(g_xlink_atom);
     }
-    result.Append(' ');
-    result.Append(prefixed_name.ToString());
+    AppendAttribute(result, prefixed_name.Prefix(), prefixed_name.LocalName(),
+                    value, document_is_html);
   } else {
     // https://w3c.github.io/DOM-Parsing/#serializing-an-element-s-attributes
 
@@ -429,26 +414,8 @@
                         *namespaces);
       }
     }
-    // 3.6. Append a " " (U+0020 SPACE) to result.
-    result.Append(' ');
-    // 3.7. If candidate prefix is not null, then append to result the
-    // concatenation of candidate prefix with ":" (U+003A COLON).
-    if (candidate_prefix) {
-      result.Append(candidate_prefix);
-      result.Append(':');
-    }
-    // 3.9.1. The value of attr's localName;
-    result.Append(attribute.LocalName());
-  }
-
-  result.Append('=');
-
-  if (element.IsURLAttribute(attribute)) {
-    AppendQuotedURLAttributeValue(result, element, attribute);
-  } else {
-    result.Append('"');
-    AppendAttributeValue(result, attribute.Value(), document_is_html);
-    result.Append('"');
+    AppendAttribute(result, candidate_prefix, attribute.LocalName(), value,
+                    document_is_html);
   }
 }
 
diff --git a/third_party/blink/renderer/core/editing/serializers/markup_formatter.h b/third_party/blink/renderer/core/editing/serializers/markup_formatter.h
index 31693ee..78b1ca2 100644
--- a/third_party/blink/renderer/core/editing/serializers/markup_formatter.h
+++ b/third_party/blink/renderer/core/editing/serializers/markup_formatter.h
@@ -73,6 +73,11 @@
 
  public:
   static void AppendAttributeValue(StringBuilder&, const String&, bool);
+  static void AppendAttribute(StringBuilder& result,
+                              const AtomicString& prefix,
+                              const AtomicString& local_name,
+                              const String& value,
+                              bool document_is_html);
   static void AppendCDATASection(StringBuilder&, const String&);
   static void AppendCharactersReplacingEntities(StringBuilder&,
                                                 const String&,
@@ -114,9 +119,6 @@
 
  private:
   String ResolveURLIfNeeded(const Element&, const String&) const;
-  void AppendQuotedURLAttributeValue(StringBuilder&,
-                                     const Element&,
-                                     const Attribute&);
 
   const EAbsoluteURLs resolve_urls_method_;
   SerializationType serialization_type_;
diff --git a/third_party/blink/renderer/core/layout/custom/layout_worklet_global_scope.cc b/third_party/blink/renderer/core/layout/custom/layout_worklet_global_scope.cc
index 8121738..207c5aa 100644
--- a/third_party/blink/renderer/core/layout/custom/layout_worklet_global_scope.cc
+++ b/third_party/blink/renderer/core/layout/custom/layout_worklet_global_scope.cc
@@ -31,8 +31,8 @@
       pending_layout_registry);
   String context_name("LayoutWorklet #");
   context_name.append(String::Number(global_scope_number));
-  global_scope->ScriptController()->InitializeContextIfNeeded(context_name,
-                                                              NullURL());
+  // TODO(bashi): Handle a case where the script controller fails to initialize.
+  global_scope->ScriptController()->InitializeContext(context_name, NullURL());
   MainThreadDebugger::Instance()->ContextCreated(
       global_scope->ScriptController()->GetScriptState(),
       global_scope->GetFrame(), global_scope->DocumentSecurityOrigin());
diff --git a/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc b/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc
index 16a0872..12d70a4 100644
--- a/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc
+++ b/third_party/blink/renderer/core/loader/modulescript/module_script_loader_test.cc
@@ -194,8 +194,8 @@
       MakeGarbageCollected<WorkletModuleResponsesMap>());
   global_scope_ = MakeGarbageCollected<WorkletGlobalScope>(
       std::move(creation_params), *reporting_proxy_, &GetFrame());
-  global_scope_->ScriptController()->InitializeContextIfNeeded("Dummy Context",
-                                                               NullURL());
+  ASSERT_TRUE(global_scope_->ScriptController()->InitializeContext(
+      "Dummy Context", NullURL()));
   modulator_ = MakeGarbageCollected<ModuleScriptLoaderTestModulator>(
       global_scope_->ScriptController()->GetScriptState());
 }
diff --git a/third_party/blink/renderer/core/streams/miscellaneous_operations.cc b/third_party/blink/renderer/core/streams/miscellaneous_operations.cc
index 6b8065d..3db17ae 100644
--- a/third_party/blink/renderer/core/streams/miscellaneous_operations.cc
+++ b/third_party/blink/renderer/core/streams/miscellaneous_operations.cc
@@ -225,6 +225,48 @@
   TraceWrapperV8Reference<v8::Value> extra_arg_;
 };
 
+class JavaScriptStreamStartAlgorithm : public StreamStartAlgorithm {
+ public:
+  JavaScriptStreamStartAlgorithm(v8::Isolate* isolate,
+                                 v8::Local<v8::Object> recv,
+                                 const char* method_name_for_error,
+                                 v8::Local<v8::Value> controller)
+      : recv_(isolate, recv),
+        method_name_for_error_(method_name_for_error),
+        controller_(isolate, controller) {}
+
+  v8::MaybeLocal<v8::Promise> Run(ScriptState* script_state,
+                                  ExceptionState& exception_state) override {
+    auto* isolate = script_state->GetIsolate();
+    // https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink
+    // 3. Let startAlgorithm be the following steps:
+    //    a. Return ? InvokeOrNoop(underlyingSink, "start", « controller »).
+    auto value_maybe = CallOrNoop1(
+        script_state, recv_.NewLocal(isolate), "start", method_name_for_error_,
+        controller_.NewLocal(isolate), exception_state);
+    if (exception_state.HadException()) {
+      return v8::MaybeLocal<v8::Promise>();
+    }
+    v8::Local<v8::Value> value;
+    if (!value_maybe.ToLocal(&value)) {
+      exception_state.ThrowTypeError("internal error");
+      return v8::MaybeLocal<v8::Promise>();
+    }
+    return PromiseResolve(script_state, value);
+  }
+
+  void Trace(Visitor* visitor) override {
+    visitor->Trace(recv_);
+    visitor->Trace(controller_);
+    StreamStartAlgorithm::Trace(visitor);
+  }
+
+ private:
+  TraceWrapperV8Reference<v8::Object> recv_;
+  const char* const method_name_for_error_;
+  TraceWrapperV8Reference<v8::Value> controller_;
+};
+
 }  // namespace
 
 // TODO(ricea): For optimal performance, method_name should be cached as an
@@ -271,6 +313,16 @@
       isolate, method.As<v8::Function>(), extra_arg_local, underlying_object);
 }
 
+CORE_EXPORT StreamStartAlgorithm* CreateStartAlgorithm(
+    ScriptState* script_state,
+    v8::Local<v8::Object> underlying_object,
+    const char* method_name_for_error,
+    v8::Local<v8::Value> controller) {
+  return MakeGarbageCollected<JavaScriptStreamStartAlgorithm>(
+      script_state->GetIsolate(), underlying_object, method_name_for_error,
+      controller);
+}
+
 CORE_EXPORT v8::MaybeLocal<v8::Value> CallOrNoop1(
     ScriptState* script_state,
     v8::Local<v8::Object> object,
diff --git a/third_party/blink/renderer/core/streams/miscellaneous_operations.h b/third_party/blink/renderer/core/streams/miscellaneous_operations.h
index 3c1458b..3e0c42d 100644
--- a/third_party/blink/renderer/core/streams/miscellaneous_operations.h
+++ b/third_party/blink/renderer/core/streams/miscellaneous_operations.h
@@ -17,6 +17,7 @@
 class ScriptState;
 class StrategySizeAlgorithm;
 class StreamAlgorithm;
+class StreamStartAlgorithm;
 
 // This is slightly different than the version in the standard
 // https://streams.spec.whatwg.org/#create-algorithm-from-underlying-method as
@@ -34,6 +35,17 @@
     v8::MaybeLocal<v8::Value> extra_arg,
     ExceptionState&);
 
+// Create a StreamStartAlgorithm from the "start" method on |underlying_object|.
+// Unlike other algorithms, the lookup of the method on the object is done at
+// execution time rather than algorithm creation time. |method_name_for_error|
+// is used in exception messages. It is not copied so must remain valid until
+// the algorithm is run.
+CORE_EXPORT StreamStartAlgorithm* CreateStartAlgorithm(
+    ScriptState*,
+    v8::Local<v8::Object> underlying_object,
+    const char* method_name_for_error,
+    v8::Local<v8::Value> controller);
+
 // Used in place of InvokeOrNoop in spec. Always takes 1 argument.
 // https://streams.spec.whatwg.org/#invoke-or-noop
 CORE_EXPORT v8::MaybeLocal<v8::Value> CallOrNoop1(ScriptState*,
diff --git a/third_party/blink/renderer/core/streams/miscellaneous_operations_test.cc b/third_party/blink/renderer/core/streams/miscellaneous_operations_test.cc
index 85c688e..aaf5101 100644
--- a/third_party/blink/renderer/core/streams/miscellaneous_operations_test.cc
+++ b/third_party/blink/renderer/core/streams/miscellaneous_operations_test.cc
@@ -169,6 +169,86 @@
       extra_arg, 1, argv));
 }
 
+TEST(MiscellaneousOperationsTest, CreateStartAlgorithmNoMethod) {
+  V8TestingScope scope;
+  auto underlying_object = v8::Object::New(scope.GetIsolate());
+  v8::Local<v8::Value> controller = v8::Undefined(scope.GetIsolate());
+  auto* algo = CreateStartAlgorithm(scope.GetScriptState(), underlying_object,
+                                    "underlyingSink.start", controller);
+  ASSERT_TRUE(algo);
+  auto maybe_result = algo->Run(scope.GetScriptState(), ASSERT_NO_EXCEPTION);
+  ASSERT_FALSE(maybe_result.IsEmpty());
+  auto result = maybe_result.ToLocalChecked();
+  ASSERT_EQ(result->State(), v8::Promise::kFulfilled);
+  EXPECT_TRUE(result->Result()->IsUndefined());
+}
+
+TEST(MiscellaneousOperationsTest, CreateStartAlgorithmNullMethod) {
+  V8TestingScope scope;
+  auto underlying_object = v8::Object::New(scope.GetIsolate());
+  underlying_object
+      ->Set(scope.GetContext(), V8String(scope.GetIsolate(), "start"),
+            v8::Null(scope.GetIsolate()))
+      .Check();
+  v8::Local<v8::Value> controller = v8::Undefined(scope.GetIsolate());
+  auto* algo = CreateStartAlgorithm(scope.GetScriptState(), underlying_object,
+                                    "underlyingSink.start", controller);
+  ASSERT_TRUE(algo);
+  ExceptionState exception_state(scope.GetIsolate(),
+                                 ExceptionState::kExecutionContext, "", "");
+  auto maybe_result = algo->Run(scope.GetScriptState(), exception_state);
+  EXPECT_TRUE(exception_state.HadException());
+  EXPECT_TRUE(maybe_result.IsEmpty());
+}
+
+TEST(MiscellaneousOperationsTest, CreateStartAlgorithmThrowingMethod) {
+  V8TestingScope scope;
+  ScriptValue underlying_value = EvalWithPrintingError(&scope,
+                                                       R"(({
+  start() {
+    throw new Error();
+  }
+}))");
+  ASSERT_TRUE(underlying_value.IsObject());
+  auto underlying_object = underlying_value.V8Value().As<v8::Object>();
+  v8::Local<v8::Value> controller = v8::Undefined(scope.GetIsolate());
+  auto* algo = CreateStartAlgorithm(scope.GetScriptState(), underlying_object,
+                                    "underlyingSink.start", controller);
+  ASSERT_TRUE(algo);
+  ExceptionState exception_state(scope.GetIsolate(),
+                                 ExceptionState::kExecutionContext, "", "");
+  auto maybe_result = algo->Run(scope.GetScriptState(), exception_state);
+  EXPECT_TRUE(exception_state.HadException());
+  EXPECT_TRUE(maybe_result.IsEmpty());
+}
+
+TEST(MiscellaneousOperationsTest, CreateStartAlgorithmReturningController) {
+  V8TestingScope scope;
+  ScriptValue underlying_value = EvalWithPrintingError(&scope,
+                                                       R"(({
+  start(controller) {
+    return controller;
+  }
+}))");
+  ASSERT_TRUE(underlying_value.IsObject());
+  auto underlying_object = underlying_value.V8Value().As<v8::Object>();
+  // In a real stream, |controller| is never a promise, but nothing in
+  // CreateStartAlgorithm() requires this. By making it a promise, we can verify
+  // that a promise returned from start is passed through as-is.
+  v8::Local<v8::Value> controller =
+      v8::Promise::Resolver::New(scope.GetContext())
+          .ToLocalChecked()
+          ->GetPromise();
+  auto* algo = CreateStartAlgorithm(scope.GetScriptState(), underlying_object,
+                                    "underlyingSink.start", controller);
+  ASSERT_TRUE(algo);
+  auto maybe_result = algo->Run(scope.GetScriptState(), ASSERT_NO_EXCEPTION);
+  EXPECT_FALSE(maybe_result.IsEmpty());
+  v8::Local<v8::Value> result = maybe_result.ToLocalChecked();
+  ASSERT_TRUE(result->IsPromise());
+  ASSERT_EQ(result, controller);
+}
+
 TEST(MiscellaneousOperationsTest, CallOrNoop1NoMethod) {
   V8TestingScope scope;
   auto underlying_object = v8::Object::New(scope.GetIsolate());
diff --git a/third_party/blink/renderer/core/streams/stream_algorithms.h b/third_party/blink/renderer/core/streams/stream_algorithms.h
index c4f2dd78..a0217a9 100644
--- a/third_party/blink/renderer/core/streams/stream_algorithms.h
+++ b/third_party/blink/renderer/core/streams/stream_algorithms.h
@@ -34,10 +34,23 @@
   virtual void Trace(Visitor*) {}
 };
 
+// Base class for start algorithms, ie. those that are derived from the start()
+// method of the underlying object. These differ from other underlying
+// algorithms in that they can throw synchronously. Objects of this
+// type must always be reachable by V8's garbage collector.
+class StreamStartAlgorithm : public GarbageCollectedFinalized<StreamAlgorithm> {
+ public:
+  virtual ~StreamStartAlgorithm() = default;
+
+  virtual v8::MaybeLocal<v8::Promise> Run(ScriptState*, ExceptionState&) = 0;
+
+  virtual void Trace(Visitor*) {}
+};
+
 // Base class for algorithms which take one or more arguments and return a
 // Promise. This is used as the type for all the algorithms in the standard that
-// do not use StrategySizeAlgorithm. Objects of this type must always be
-// reachable by V8's garbage collector.
+// do not use StrategySizeAlgorithm or StreamStartAlgorithm. Objects of this
+// type must always be reachable by V8's garbage collector.
 class StreamAlgorithm : public GarbageCollectedFinalized<StreamAlgorithm> {
  public:
   virtual ~StreamAlgorithm() = default;
diff --git a/third_party/blink/renderer/core/workers/worker_thread.cc b/third_party/blink/renderer/core/workers/worker_thread.cc
index cf062fa..f6683ab 100644
--- a/third_party/blink/renderer/core/workers/worker_thread.cc
+++ b/third_party/blink/renderer/core/workers/worker_thread.cc
@@ -465,7 +465,7 @@
             WorkerThreadDebugger::From(GetIsolate()))
       debugger->WorkerThreadCreated(this);
 
-    if (GlobalScope()->ScriptController()->InitializeContextIfNeeded(
+    if (GlobalScope()->ScriptController()->InitializeContext(
             String(), url_for_debugger)) {
       worker_reporting_proxy_.DidInitializeWorkerContext();
       v8::HandleScope handle_scope(GetIsolate());
diff --git a/third_party/blink/renderer/modules/DEPS b/third_party/blink/renderer/modules/DEPS
index b0697f0..3122bc0 100644
--- a/third_party/blink/renderer/modules/DEPS
+++ b/third_party/blink/renderer/modules/DEPS
@@ -5,6 +5,7 @@
     "+mojo/public/cpp/bindings",
     "+services/network/public/cpp/shared_url_loader_factory.h",
     "+services/service_manager/public/mojom/interface_provider.mojom-blink.h",
+    "+services/viz/public/interfaces/hit_test/hit_test_region_list.mojom-blink.h",
     "+third_party/blink/public/common",
     "+third_party/blink/public/web",
     "+third_party/blink/renderer/bindings",
diff --git a/third_party/blink/renderer/modules/battery/DEPS b/third_party/blink/renderer/modules/battery/DEPS
index 87cb07eb..cdfc1ab 100644
--- a/third_party/blink/renderer/modules/battery/DEPS
+++ b/third_party/blink/renderer/modules/battery/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
     "+services/device/public/mojom/battery_monitor.mojom-blink.h",
+    "+services/device/public/mojom/battery_status.mojom-blink.h",
     "+services/device/public/mojom/constants.mojom-blink.h",
     "-third_party/blink/renderer/modules",
     "+third_party/blink/renderer/modules/event_target_modules.h",
diff --git a/third_party/blink/renderer/modules/battery/battery_dispatcher.cc b/third_party/blink/renderer/modules/battery/battery_dispatcher.cc
index aa084b6..0ee3b1a 100644
--- a/third_party/blink/renderer/modules/battery/battery_dispatcher.cc
+++ b/third_party/blink/renderer/modules/battery/battery_dispatcher.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/modules/battery/battery_dispatcher.h"
 
+#include "services/device/public/mojom/battery_status.mojom-blink.h"
 #include "services/device/public/mojom/constants.mojom-blink.h"
 #include "third_party/blink/public/platform/interface_provider.h"
 #include "third_party/blink/public/platform/platform.h"
diff --git a/third_party/blink/renderer/modules/canvas/htmlcanvas/html_canvas_element_module_test.cc b/third_party/blink/renderer/modules/canvas/htmlcanvas/html_canvas_element_module_test.cc
index 6be47fe..bff719a 100644
--- a/third_party/blink/renderer/modules/canvas/htmlcanvas/html_canvas_element_module_test.cc
+++ b/third_party/blink/renderer/modules/canvas/htmlcanvas/html_canvas_element_module_test.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "build/build_config.h"
+#include "services/viz/public/interfaces/hit_test/hit_test_region_list.mojom-blink.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/modules/frame_sinks/embedded_frame_sink.mojom-blink.h"
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas/offscreen_canvas_test.cc b/third_party/blink/renderer/modules/canvas/offscreencanvas/offscreen_canvas_test.cc
index 93955ac..21c5f15 100644
--- a/third_party/blink/renderer/modules/canvas/offscreencanvas/offscreen_canvas_test.cc
+++ b/third_party/blink/renderer/modules/canvas/offscreencanvas/offscreen_canvas_test.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h"
 
+#include "services/viz/public/interfaces/hit_test/hit_test_region_list.mojom-blink.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/core/dom/document.h"
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.cc
index fd6e0ba..bf74acd 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.cc
@@ -101,8 +101,8 @@
       pending_generator_registry);
   String context_name("PaintWorklet #");
   context_name.append(String::Number(global_scope_number));
-  global_scope->ScriptController()->InitializeContextIfNeeded(context_name,
-                                                              NullURL());
+  // TODO(bashi): Handle a case where the script controller fails to initialize.
+  global_scope->ScriptController()->InitializeContext(context_name, NullURL());
   MainThreadDebugger::Instance()->ContextCreated(
       global_scope->ScriptController()->GetScriptState(),
       global_scope->GetFrame(), global_scope->DocumentSecurityOrigin());
diff --git a/third_party/blink/renderer/modules/exported/web_embedded_worker_impl.cc b/third_party/blink/renderer/modules/exported/web_embedded_worker_impl.cc
index 519d1b46..0f71a86 100644
--- a/third_party/blink/renderer/modules/exported/web_embedded_worker_impl.cc
+++ b/third_party/blink/renderer/modules/exported/web_embedded_worker_impl.cc
@@ -180,9 +180,10 @@
 
   devtools_worker_token_ = data.devtools_worker_token;
   // S13nServiceWorker: |loader_factory| is null since all loads for new scripts
-  // go through ServiceWorkerNetworkProvider::script_loader_factory() rather
-  // than the shadow page's loader. This is different to shared workers, which
-  // use script_loader_factory() for the main script only, and the shadow page
+  // go through (internal WebServiceWorkerNetworkProvider class in
+  // service_worker_context_client.cc)::script_loader_factory() rather than the
+  // shadow page's loader. This is different to shared workers, which use
+  // script_loader_factory() for the main script only, and the shadow page
   // loader for importScripts().
   //
   // Non-S13nServiceWorker: |loader_factory| is null since the main script load
diff --git a/third_party/blink/renderer/modules/payments/can_make_payment_test.cc b/third_party/blink/renderer/modules/payments/can_make_payment_test.cc
index ac712f7..21860ed 100644
--- a/third_party/blink/renderer/modules/payments/can_make_payment_test.cc
+++ b/third_party/blink/renderer/modules/payments/can_make_payment_test.cc
@@ -17,12 +17,111 @@
 using payments::mojom::blink::PaymentErrorReason;
 using payments::mojom::blink::PaymentRequestClient;
 
-// HasEnrolledInstrumentTest is parameterized on this enum to test that
-// canMakePayment when PaymentRequestHasEnrolledInstrumentEnabled is false
-// behaves identically to hasEnrolledInstrument.
+class HasEnrolledInstrumentTest : public testing::Test {
+  void SetUp() override {
+    testing::Test::SetUp();
+    RuntimeEnabledFeatures::SetPaymentRequestHasEnrolledInstrumentEnabled(true);
+  }
+};
+
+TEST_F(HasEnrolledInstrumentTest, RejectPromiseOnUserCancel) {
+  V8TestingScope scope;
+  PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
+  MakePaymentRequestOriginSecure(scope.GetDocument());
+  PaymentRequest* request = PaymentRequest::Create(
+      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(),
+      BuildPaymentDetailsInitForTest(), scope.GetExceptionState());
+
+  request->hasEnrolledInstrument(scope.GetScriptState())
+      .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
+
+  static_cast<PaymentRequestClient*>(request)->OnError(
+      PaymentErrorReason::USER_CANCEL);
+}
+
+TEST_F(HasEnrolledInstrumentTest, RejectPromiseOnUnknownError) {
+  V8TestingScope scope;
+  PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
+  MakePaymentRequestOriginSecure(scope.GetDocument());
+  PaymentRequest* request = PaymentRequest::Create(
+      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(),
+      BuildPaymentDetailsInitForTest(), scope.GetExceptionState());
+
+  request->hasEnrolledInstrument(scope.GetScriptState())
+      .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
+
+  static_cast<PaymentRequestClient*>(request)->OnError(
+      PaymentErrorReason::UNKNOWN);
+}
+
+TEST_F(HasEnrolledInstrumentTest, RejectDuplicateRequest) {
+  V8TestingScope scope;
+  PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
+  MakePaymentRequestOriginSecure(scope.GetDocument());
+  PaymentRequest* request = PaymentRequest::Create(
+      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(),
+      BuildPaymentDetailsInitForTest(), scope.GetExceptionState());
+  request->hasEnrolledInstrument(scope.GetScriptState());
+  request->hasEnrolledInstrument(scope.GetScriptState())
+      .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
+}
+
+TEST_F(HasEnrolledInstrumentTest, RejectQueryQuotaExceeded) {
+  V8TestingScope scope;
+  PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
+  MakePaymentRequestOriginSecure(scope.GetDocument());
+  PaymentRequest* request = PaymentRequest::Create(
+      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(),
+      BuildPaymentDetailsInitForTest(), scope.GetExceptionState());
+
+  request->hasEnrolledInstrument(scope.GetScriptState())
+      .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
+
+  static_cast<PaymentRequestClient*>(request)->OnHasEnrolledInstrument(
+      HasEnrolledInstrumentQueryResult::QUERY_QUOTA_EXCEEDED);
+}
+
+TEST_F(HasEnrolledInstrumentTest, ReturnHasNoEnrolledInstrument) {
+  V8TestingScope scope;
+  PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
+  MakePaymentRequestOriginSecure(scope.GetDocument());
+  PaymentRequest* request = PaymentRequest::Create(
+      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(),
+      BuildPaymentDetailsInitForTest(), scope.GetExceptionState());
+  String captor;
+  request->hasEnrolledInstrument(scope.GetScriptState())
+      .Then(funcs.ExpectCall(&captor), funcs.ExpectNoCall());
+
+  static_cast<PaymentRequestClient*>(request)->OnHasEnrolledInstrument(
+      HasEnrolledInstrumentQueryResult::HAS_NO_ENROLLED_INSTRUMENT);
+
+  v8::MicrotasksScope::PerformCheckpoint(scope.GetScriptState()->GetIsolate());
+  EXPECT_EQ("false", captor);
+}
+
+TEST_F(HasEnrolledInstrumentTest, ReturnHasEnrolledInstrument) {
+  V8TestingScope scope;
+  PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
+  MakePaymentRequestOriginSecure(scope.GetDocument());
+  PaymentRequest* request = PaymentRequest::Create(
+      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(),
+      BuildPaymentDetailsInitForTest(), scope.GetExceptionState());
+  String captor;
+  request->hasEnrolledInstrument(scope.GetScriptState())
+      .Then(funcs.ExpectCall(&captor), funcs.ExpectNoCall());
+
+  static_cast<PaymentRequestClient*>(request)->OnHasEnrolledInstrument(
+      HasEnrolledInstrumentQueryResult::HAS_ENROLLED_INSTRUMENT);
+
+  v8::MicrotasksScope::PerformCheckpoint(scope.GetScriptState()->GetIsolate());
+  EXPECT_EQ("true", captor);
+}
+
+// CanMakePaymentTest is parameterized on this enum to test both legacy and new
+// behaviors.
 enum class HasEnrolledInstrumentEnabled { kYes, kNo };
 
-class HasEnrolledInstrumentTest
+class CanMakePaymentTest
     : public testing::Test,
       public testing::WithParamInterface<HasEnrolledInstrumentEnabled> {
   void SetUp() override {
@@ -32,146 +131,7 @@
   }
 };
 
-TEST_P(HasEnrolledInstrumentTest, RejectPromiseOnUserCancel) {
-  V8TestingScope scope;
-  PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
-  MakePaymentRequestOriginSecure(scope.GetDocument());
-  PaymentRequest* request = PaymentRequest::Create(
-      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(),
-      BuildPaymentDetailsInitForTest(), scope.GetExceptionState());
-
-  if (GetParam() == HasEnrolledInstrumentEnabled::kYes) {
-    request->hasEnrolledInstrument(scope.GetScriptState())
-        .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
-  } else {
-    request->canMakePayment(scope.GetScriptState())
-        .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
-  }
-
-  static_cast<PaymentRequestClient*>(request)->OnError(
-      PaymentErrorReason::USER_CANCEL);
-}
-
-TEST_P(HasEnrolledInstrumentTest, RejectPromiseOnUnknownError) {
-  V8TestingScope scope;
-  PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
-  MakePaymentRequestOriginSecure(scope.GetDocument());
-  PaymentRequest* request = PaymentRequest::Create(
-      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(),
-      BuildPaymentDetailsInitForTest(), scope.GetExceptionState());
-
-  if (GetParam() == HasEnrolledInstrumentEnabled::kYes) {
-    request->hasEnrolledInstrument(scope.GetScriptState())
-        .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
-  } else {
-    request->canMakePayment(scope.GetScriptState())
-        .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
-  }
-
-  static_cast<PaymentRequestClient*>(request)->OnError(
-      PaymentErrorReason::UNKNOWN);
-}
-
-TEST_P(HasEnrolledInstrumentTest, RejectDuplicateRequest) {
-  V8TestingScope scope;
-  PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
-  MakePaymentRequestOriginSecure(scope.GetDocument());
-  PaymentRequest* request = PaymentRequest::Create(
-      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(),
-      BuildPaymentDetailsInitForTest(), scope.GetExceptionState());
-  if (GetParam() == HasEnrolledInstrumentEnabled::kYes) {
-    request->hasEnrolledInstrument(scope.GetScriptState());
-    request->hasEnrolledInstrument(scope.GetScriptState())
-        .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
-  } else {
-    request->canMakePayment(scope.GetScriptState());
-    request->canMakePayment(scope.GetScriptState())
-        .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
-  }
-}
-
-TEST_P(HasEnrolledInstrumentTest, RejectQueryQuotaExceeded) {
-  RuntimeEnabledFeatures::SetPaymentRequestHasEnrolledInstrumentEnabled(false);
-  V8TestingScope scope;
-  PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
-  MakePaymentRequestOriginSecure(scope.GetDocument());
-  PaymentRequest* request = PaymentRequest::Create(
-      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(),
-      BuildPaymentDetailsInitForTest(), scope.GetExceptionState());
-
-  if (GetParam() == HasEnrolledInstrumentEnabled::kYes) {
-    request->hasEnrolledInstrument(scope.GetScriptState())
-        .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
-  } else {
-    request->canMakePayment(scope.GetScriptState())
-        .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
-  }
-
-  static_cast<PaymentRequestClient*>(request)->OnHasEnrolledInstrument(
-      HasEnrolledInstrumentQueryResult::QUERY_QUOTA_EXCEEDED);
-}
-
-TEST_P(HasEnrolledInstrumentTest, ReturnHasNoEnrolledInstrument) {
-  V8TestingScope scope;
-  PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
-  MakePaymentRequestOriginSecure(scope.GetDocument());
-  PaymentRequest* request = PaymentRequest::Create(
-      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(),
-      BuildPaymentDetailsInitForTest(), scope.GetExceptionState());
-  String captor;
-  if (GetParam() == HasEnrolledInstrumentEnabled::kYes) {
-    request->hasEnrolledInstrument(scope.GetScriptState())
-        .Then(funcs.ExpectCall(&captor), funcs.ExpectNoCall());
-  } else {
-    request->canMakePayment(scope.GetScriptState())
-        .Then(funcs.ExpectCall(&captor), funcs.ExpectNoCall());
-  }
-
-  static_cast<PaymentRequestClient*>(request)->OnHasEnrolledInstrument(
-      HasEnrolledInstrumentQueryResult::HAS_NO_ENROLLED_INSTRUMENT);
-
-  v8::MicrotasksScope::PerformCheckpoint(scope.GetScriptState()->GetIsolate());
-  EXPECT_EQ("false", captor);
-}
-
-TEST_P(HasEnrolledInstrumentTest, ReturnHasEnrolledInstrument) {
-  V8TestingScope scope;
-  PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
-  MakePaymentRequestOriginSecure(scope.GetDocument());
-  PaymentRequest* request = PaymentRequest::Create(
-      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(),
-      BuildPaymentDetailsInitForTest(), scope.GetExceptionState());
-  String captor;
-  if (GetParam() == HasEnrolledInstrumentEnabled::kYes) {
-    request->hasEnrolledInstrument(scope.GetScriptState())
-        .Then(funcs.ExpectCall(&captor), funcs.ExpectNoCall());
-  } else {
-    request->canMakePayment(scope.GetScriptState())
-        .Then(funcs.ExpectCall(&captor), funcs.ExpectNoCall());
-  }
-
-  static_cast<PaymentRequestClient*>(request)->OnHasEnrolledInstrument(
-      HasEnrolledInstrumentQueryResult::HAS_ENROLLED_INSTRUMENT);
-
-  v8::MicrotasksScope::PerformCheckpoint(scope.GetScriptState()->GetIsolate());
-  EXPECT_EQ("true", captor);
-}
-
-INSTANTIATE_TEST_CASE_P(ProgrammaticHasEnrolledInstrumentTest,
-                        HasEnrolledInstrumentTest,
-                        ::testing::Values(HasEnrolledInstrumentEnabled::kYes,
-                                          HasEnrolledInstrumentEnabled::kNo));
-
-// Test fixture for canMakePayment when
-// PaymentRequestHasEnrolledInstrumentEnabled is true.
-class CanMakePaymentTest : public testing::Test {
-  void SetUp() override {
-    testing::Test::SetUp();
-    RuntimeEnabledFeatures::SetPaymentRequestHasEnrolledInstrumentEnabled(true);
-  }
-};
-
-TEST_F(CanMakePaymentTest, RejectPromiseOnUserCancel) {
+TEST_P(CanMakePaymentTest, RejectPromiseOnUserCancel) {
   V8TestingScope scope;
   PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
   MakePaymentRequestOriginSecure(scope.GetDocument());
@@ -186,7 +146,7 @@
       PaymentErrorReason::USER_CANCEL);
 }
 
-TEST_F(CanMakePaymentTest, RejectPromiseOnUnknownError) {
+TEST_P(CanMakePaymentTest, RejectPromiseOnUnknownError) {
   V8TestingScope scope;
   PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
   MakePaymentRequestOriginSecure(scope.GetDocument());
@@ -201,7 +161,7 @@
       PaymentErrorReason::UNKNOWN);
 }
 
-TEST_F(CanMakePaymentTest, RejectDuplicateRequest) {
+TEST_P(CanMakePaymentTest, RejectDuplicateRequest) {
   V8TestingScope scope;
   PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
   MakePaymentRequestOriginSecure(scope.GetDocument());
@@ -214,7 +174,22 @@
       .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
 }
 
-TEST_F(CanMakePaymentTest, ReturnCannotMakePayment) {
+TEST_P(CanMakePaymentTest, RejectQueryQuotaExceeded) {
+  V8TestingScope scope;
+  PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
+  MakePaymentRequestOriginSecure(scope.GetDocument());
+  PaymentRequest* request = PaymentRequest::Create(
+      scope.GetExecutionContext(), BuildPaymentMethodDataForTest(),
+      BuildPaymentDetailsInitForTest(), scope.GetExceptionState());
+
+  request->canMakePayment(scope.GetScriptState())
+      .Then(funcs.ExpectNoCall(), funcs.ExpectCall());
+
+  static_cast<PaymentRequestClient*>(request)->OnCanMakePayment(
+      CanMakePaymentQueryResult::QUERY_QUOTA_EXCEEDED);
+}
+
+TEST_P(CanMakePaymentTest, ReturnCannotMakePayment) {
   V8TestingScope scope;
   PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
   MakePaymentRequestOriginSecure(scope.GetDocument());
@@ -232,7 +207,7 @@
   EXPECT_EQ("false", captor);
 }
 
-TEST_F(CanMakePaymentTest, ReturnCanMakePayment) {
+TEST_P(CanMakePaymentTest, ReturnCanMakePayment) {
   V8TestingScope scope;
   PaymentRequestMockFunctionScope funcs(scope.GetScriptState());
   MakePaymentRequestOriginSecure(scope.GetDocument());
@@ -250,5 +225,10 @@
   EXPECT_EQ("true", captor);
 }
 
+INSTANTIATE_TEST_CASE_P(ProgrammaticCanMakePaymentTest,
+                        CanMakePaymentTest,
+                        ::testing::Values(HasEnrolledInstrumentEnabled::kYes,
+                                          HasEnrolledInstrumentEnabled::kNo));
+
 }  // namespace
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/payments/payment_request.cc b/third_party/blink/renderer/modules/payments/payment_request.cc
index 62ea7cdc8..c25412e7 100644
--- a/third_party/blink/renderer/modules/payments/payment_request.cc
+++ b/third_party/blink/renderer/modules/payments/payment_request.cc
@@ -88,6 +88,9 @@
 using ::payments::mojom::blink::PaymentValidationErrors;
 using ::payments::mojom::blink::PaymentValidationErrorsPtr;
 
+const char kCanMakePaymentDebugName[] = "canMakePayment";
+const char kHasEnrolledInstrumentDebugName[] = "hasEnrolledInstrument";
+
 }  // namespace
 
 namespace mojo {
@@ -767,12 +770,15 @@
 }
 
 void WarnIgnoringQueryQuotaForCanMakePayment(
-    ExecutionContext& execution_context) {
-  execution_context.AddConsoleMessage(ConsoleMessage::Create(
-      kJSMessageSource, kWarningMessageLevel,
-      "Quota reached for PaymentRequest.canMakePayment(). This would normally "
+    ExecutionContext& execution_context,
+    const char* method_name) {
+  const String& error = String::Format(
+      "Quota reached for PaymentRequest.%s(). This would normally "
       "reject the promise, but allowing continued usage on localhost and "
-      "file:// scheme origins."));
+      "file:// scheme origins.",
+      method_name);
+  execution_context.AddConsoleMessage(
+      ConsoleMessage::Create(kJSMessageSource, kWarningMessageLevel, error));
 }
 
 }  // namespace
@@ -864,12 +870,6 @@
 }
 
 ScriptPromise PaymentRequest::canMakePayment(ScriptState* script_state) {
-  if (!RuntimeEnabledFeatures::PaymentRequestHasEnrolledInstrumentEnabled()) {
-    // Fallback to backward-compatible definition of canMakePayment, which is
-    // now implemented as hasEnrolledInstrument.
-    return hasEnrolledInstrument(script_state);
-  }
-
   if (!payment_provider_.is_bound() || GetPendingAcceptPromiseResolver() ||
       can_make_payment_resolver_ || !script_state->ContextIsValid()) {
     return ScriptPromise::RejectWithDOMException(
@@ -877,7 +877,9 @@
                                            "Cannot query payment request"));
   }
 
-  payment_provider_->CanMakePayment();
+  bool legacy_mode =
+      !RuntimeEnabledFeatures::PaymentRequestHasEnrolledInstrumentEnabled();
+  payment_provider_->CanMakePayment(legacy_mode);
 
   can_make_payment_resolver_ = ScriptPromiseResolver::Create(script_state);
   return can_make_payment_resolver_->Promise();
@@ -1444,16 +1446,31 @@
 }
 
 void PaymentRequest::OnCanMakePayment(CanMakePaymentQueryResult result) {
+  // TODO(https://crbug.com/891371): Understand how the resolver could be null
+  // here and prevent it.
   if (!can_make_payment_resolver_)
     return;
 
   switch (result) {
+    case CanMakePaymentQueryResult::WARNING_CAN_MAKE_PAYMENT:
+      WarnIgnoringQueryQuotaForCanMakePayment(*GetExecutionContext(),
+                                              kCanMakePaymentDebugName);
+      FALLTHROUGH;
     case CanMakePaymentQueryResult::CAN_MAKE_PAYMENT:
       can_make_payment_resolver_->Resolve(true);
       break;
+    case CanMakePaymentQueryResult::WARNING_CANNOT_MAKE_PAYMENT:
+      WarnIgnoringQueryQuotaForCanMakePayment(*GetExecutionContext(),
+                                              kCanMakePaymentDebugName);
+      FALLTHROUGH;
     case CanMakePaymentQueryResult::CANNOT_MAKE_PAYMENT:
       can_make_payment_resolver_->Resolve(false);
       break;
+    case CanMakePaymentQueryResult::QUERY_QUOTA_EXCEEDED:
+      can_make_payment_resolver_->Reject(DOMException::Create(
+          DOMExceptionCode::kNotAllowedError,
+          "Not allowed to check whether can make payment"));
+      break;
   }
 
   can_make_payment_resolver_.Clear();
@@ -1468,13 +1485,15 @@
 
   switch (result) {
     case HasEnrolledInstrumentQueryResult::WARNING_HAS_ENROLLED_INSTRUMENT:
-      WarnIgnoringQueryQuotaForCanMakePayment(*GetExecutionContext());
+      WarnIgnoringQueryQuotaForCanMakePayment(*GetExecutionContext(),
+                                              kHasEnrolledInstrumentDebugName);
       FALLTHROUGH;
     case HasEnrolledInstrumentQueryResult::HAS_ENROLLED_INSTRUMENT:
       has_enrolled_instrument_resolver_->Resolve(true);
       break;
     case HasEnrolledInstrumentQueryResult::WARNING_HAS_NO_ENROLLED_INSTRUMENT:
-      WarnIgnoringQueryQuotaForCanMakePayment(*GetExecutionContext());
+      WarnIgnoringQueryQuotaForCanMakePayment(*GetExecutionContext(),
+                                              kHasEnrolledInstrumentDebugName);
       FALLTHROUGH;
     case HasEnrolledInstrumentQueryResult::HAS_NO_ENROLLED_INSTRUMENT:
       has_enrolled_instrument_resolver_->Resolve(false);
@@ -1482,7 +1501,7 @@
     case HasEnrolledInstrumentQueryResult::QUERY_QUOTA_EXCEEDED:
       has_enrolled_instrument_resolver_->Reject(DOMException::Create(
           DOMExceptionCode::kNotAllowedError,
-          "Not allowed to check whether can make payment"));
+          "Exceeded query quota for hasEnrolledInstrument"));
       break;
   }
 
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream.h b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream.h
index a6d923ed..c3ce1dfe 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream.h
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream.h
@@ -77,7 +77,8 @@
   // for reading and writing and deleted by the quic::QuicSession.
   virtual void WriteData(Vector<uint8_t> data, bool fin) = 0;
 
-  // Sets the delegate object, which must outlive the P2PQuicStream.
+  // Sets the delegate object. In the case that the Delegate does not outlive
+  // the P2PQuicStream object, it must unset itself before destructing.
   virtual void SetDelegate(Delegate* delegate) = 0;
 };
 
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.cc b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.cc
index 567214b7..b53479f 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.cc
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.h"
 
+#include <utility>
 #include "net/third_party/quic/core/quic_error_codes.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 
@@ -34,13 +35,14 @@
 P2PQuicStreamImpl::~P2PQuicStreamImpl() {}
 
 void P2PQuicStreamImpl::OnDataAvailable() {
-  DCHECK(delegate_);
   if (!sequencer()->HasBytesToRead() && sequencer()->IsClosed()) {
     // We have consumed all data from the sequencer up to the FIN bit. This can
     // only occur by receiving an empty STREAM frame with the FIN bit set.
     quic::QuicStream::OnFinRead();
-    delegate_->OnDataReceived({}, /*fin=*/true);
     consumed_fin_ = true;
+    if (delegate_) {
+      delegate_->OnDataReceived({}, /*fin=*/true);
+    }
   }
 
   uint32_t delegate_read_buffer_available =
@@ -87,17 +89,20 @@
     quic::QuicStream::OnFinRead();
     consumed_fin_ = fin;
   }
-  delegate_->OnDataReceived(std::move(data), fin);
+  if (delegate_) {
+    delegate_->OnDataReceived(std::move(data), fin);
+  }
 }
 
 void P2PQuicStreamImpl::OnStreamDataConsumed(size_t bytes_consumed) {
-  DCHECK(delegate_);
   // We should never consume more than has been written.
   DCHECK_GE(write_buffered_amount_, bytes_consumed);
   QuicStream::OnStreamDataConsumed(bytes_consumed);
   if (bytes_consumed > 0) {
     write_buffered_amount_ -= bytes_consumed;
-    delegate_->OnWriteDataConsumed(SafeCast<uint32_t>(bytes_consumed));
+    if (delegate_) {
+      delegate_->OnWriteDataConsumed(SafeCast<uint32_t>(bytes_consumed));
+    }
   }
 }
 
@@ -134,12 +139,13 @@
 }
 
 void P2PQuicStreamImpl::OnStreamReset(const quic::QuicRstStreamFrame& frame) {
-  DCHECK(delegate_);
   // Calling this on the QuicStream will ensure that the stream is closed
   // for reading and writing and we send a RST frame to the remote side if
   // we have not already.
   quic::QuicStream::OnStreamReset(frame);
-  delegate_->OnRemoteReset();
+  if (delegate_) {
+    delegate_->OnRemoteReset();
+  }
 }
 
 void P2PQuicStreamImpl::OnClose() {
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.h b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.h
index 733ff5a..2af3a5f 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.h
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_impl.h
@@ -67,7 +67,7 @@
  private:
   using quic::QuicStream::Reset;
 
-  // Outlives the P2PQuicStreamImpl.
+  // Must either outlive the P2PQuicStream or unset itself upon destruction.
   Delegate* delegate_;
 
   // The read buffer size of the delegate. The |delegate_read_buffered_amount_|
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_unittest.cc b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_unittest.cc
index 0a36ce34..d3c03c7 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_unittest.cc
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/p2p_quic_stream_unittest.cc
@@ -439,4 +439,64 @@
   // Just the last data received ("ta") is held in the delegate's read buffer.
   EXPECT_EQ(2u, stream_->DelegateReadBufferedAmountForTesting());
 }
+
+// Tests that after a delegate unsets itself, it will no longer receive the
+// OnWriteDataConsumed callback.
+TEST_F(P2PQuicStreamTest, UnsetDelegateDoesNotFireOnWriteDataConsumed) {
+  InitializeStream();
+  stream_->SetDelegate(nullptr);
+  // Mock out the QuicSession to get the QuicStream::OnStreamDataConsumed
+  // callback to fire.
+  EXPECT_CALL(session_,
+              WritevData(stream_, kStreamId,
+                         /*write_length=*/base::size(kSomeData), _, _))
+      .WillOnce(Invoke([](quic::QuicStream* stream, quic::QuicStreamId id,
+                          size_t write_length, quic::QuicStreamOffset offset,
+                          quic::StreamSendingState state) {
+        return quic::QuicConsumedData(
+            write_length, state != quic::StreamSendingState::NO_FIN);
+      }));
+
+  EXPECT_CALL(delegate_, OnWriteDataConsumed(_)).Times(0);
+
+  stream_->WriteData(VectorFromArray(kSomeData), /*fin=*/false);
+}
+
+// Tests that after a delegate unsets itself, it will no longer receive the
+// OnRemoteReset callback.
+TEST_F(P2PQuicStreamTest, UnsetDelegateDoesNotFireOnRemoteReset) {
+  InitializeStream();
+  stream_->SetDelegate(nullptr);
+  EXPECT_CALL(delegate_, OnRemoteReset()).Times(0);
+
+  quic::QuicRstStreamFrame rst_frame(quic::kInvalidControlFrameId, kStreamId,
+                                     quic::QUIC_STREAM_CANCELLED, 0);
+  stream_->OnStreamReset(rst_frame);
+}
+
+// Tests that after a delegate unsets itself, it will no longer receive the
+// OnDataReceived callback when receiving a stream frame with data and no FIN
+// bit.
+TEST_F(P2PQuicStreamTest, UnsetDelegateDoesNotFireOnDataReceivedWithData) {
+  InitializeStream();
+  stream_->SetDelegate(nullptr);
+
+  EXPECT_CALL(delegate_, OnDataReceived(_, _)).Times(0);
+
+  quic::QuicStreamFrame stream_frame(stream_->id(), /*fin=*/false, 0,
+                                     StringPieceFromArray(kSomeData));
+  stream_->OnStreamFrame(stream_frame);
+}
+
+// Tests that after a delegate unsets itself, it will no longer receive the
+// OnDataReceived callback when receiving a stream frame with the FIN bit.
+TEST_F(P2PQuicStreamTest, UnsetDelegateDoesNotFireOnDataReceivedWithFin) {
+  InitializeStream();
+  stream_->SetDelegate(nullptr);
+
+  EXPECT_CALL(delegate_, OnDataReceived(_, _)).Times(0);
+
+  quic::QuicStreamFrame stream_frame(stream_->id(), /*fin=*/true, 0, {});
+  stream_->OnStreamFrame(stream_frame);
+}
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/quic_stream_host.cc b/third_party/blink/renderer/modules/peerconnection/adapters/quic_stream_host.cc
index a0937ef..70debe1 100644
--- a/third_party/blink/renderer/modules/peerconnection/adapters/quic_stream_host.cc
+++ b/third_party/blink/renderer/modules/peerconnection/adapters/quic_stream_host.cc
@@ -99,6 +99,7 @@
 void QuicStreamHost::Delete() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(transport_host_);
+  p2p_stream_->SetDelegate(nullptr);
   // OnRemoveStream will delete |this|.
   transport_host_->OnRemoveStream(this);
 }
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.cc b/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.cc
index 6acfef6..ac4de7c5 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.cc
@@ -193,9 +193,6 @@
                               data_vector.size(), 1, 24000000, 50);
   proxy_->WriteData(std::move(data_vector), finish);
   if (finish) {
-    // TODO(shampson): This can cause a crash. If the P2PQuicStream
-    // has buffered data with a FIN, it will try calling
-    // host->OnWriteDataConsumed, after the QuicStreamHost has been deleted.
     wrote_fin_ = true;
     if (!read_fin_) {
       DCHECK_EQ(state_, RTCQuicStreamState::kOpen);
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream_test.cc
index 3ab9b0e..8ae23b7 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream_test.cc
@@ -171,6 +171,8 @@
   RunUntilIdle();
 
   ASSERT_TRUE(stream_delegate);
+  EXPECT_CALL(*p2p_quic_stream.get(), SetDelegate(nullptr));
+
   stream_delegate->OnRemoteReset();
 
   RunUntilIdle();
@@ -205,6 +207,8 @@
   RunUntilIdle();
 
   ASSERT_TRUE(stream_delegate);
+  EXPECT_CALL(*p2p_quic_stream.get(), SetDelegate(nullptr));
+
   stream_delegate->OnRemoteReset();
   quic_stream->reset();
   EXPECT_EQ("closed", quic_stream->state());
@@ -328,6 +332,8 @@
   RunUntilIdle();
 
   ASSERT_TRUE(stream_delegate);
+  EXPECT_CALL(*p2p_quic_stream.get(), SetDelegate(nullptr));
+
   stream_delegate->OnRemoteReset();
 
   RunUntilIdle();
@@ -358,6 +364,8 @@
   RunUntilIdle();
 
   ASSERT_TRUE(stream_delegate);
+  EXPECT_CALL(*p2p_quic_stream.get(), SetDelegate(nullptr));
+
   stream_delegate->OnDataReceived({}, /*fin=*/true);
 
   RunUntilIdle();
@@ -384,6 +392,8 @@
   RunUntilIdle();
 
   ASSERT_TRUE(stream_delegate);
+  EXPECT_CALL(*p2p_quic_stream.get(), SetDelegate(nullptr));
+
   stream_delegate->OnRemoteReset();
 
   RunUntilIdle();
@@ -575,6 +585,8 @@
   RunUntilIdle();
 
   ASSERT_TRUE(stream_delegate);
+  EXPECT_CALL(*p2p_quic_stream.get(), SetDelegate(nullptr));
+
   stream_delegate->OnRemoteReset();
 
   RunUntilIdle();
@@ -878,6 +890,8 @@
 
   Persistent<RTCQuicStream> stream =
       CreateQuicStream(scope, p2p_quic_stream.get());
+  EXPECT_CALL(*p2p_quic_stream.get(), SetDelegate(nullptr));
+
   stream->reset();
 
   NotShared<DOMUint8Array> read_buffer(DOMUint8Array::Create(2));
@@ -1136,6 +1150,8 @@
 
   ASSERT_TRUE(stream_delegate);
   EXPECT_EQ("open", stream->state());
+  EXPECT_CALL(*p2p_quic_stream.get(), SetDelegate(nullptr));
+
   stream_delegate->OnRemoteReset();
 
   RunUntilIdle();
@@ -1167,6 +1183,7 @@
   EXPECT_TRUE(stream->readInto(read_buffer, ASSERT_NO_EXCEPTION)->finished());
 
   EXPECT_EQ("closing", stream->state());
+  EXPECT_CALL(*p2p_quic_stream.get(), SetDelegate(nullptr));
 
   stream->write(CreateWriteParametersWithoutData(/*finish=*/true),
                 ASSERT_NO_EXCEPTION);
@@ -1195,6 +1212,8 @@
   RunUntilIdle();
 
   ASSERT_TRUE(stream_delegate);
+  EXPECT_CALL(*p2p_quic_stream.get(), SetDelegate(nullptr));
+
   stream_delegate->OnDataReceived({}, /*fin=*/true);
 
   RunUntilIdle();
diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope_proxy.cc b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope_proxy.cc
index cd30cfd..356ed7ca 100644
--- a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope_proxy.cc
+++ b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope_proxy.cc
@@ -657,7 +657,7 @@
 
 void ServiceWorkerGlobalScopeProxy::DidLoadInstalledScript() {
   DCHECK_CALLED_ON_VALID_THREAD(worker_thread_checker_);
-  Client().WorkerScriptLoaded();
+  Client().InstalledWorkerScriptLoaded();
 }
 
 void ServiceWorkerGlobalScopeProxy::DidFailToLoadInstalledClassicScript() {
diff --git a/third_party/blink/renderer/platform/graphics/begin_frame_provider.cc b/third_party/blink/renderer/platform/graphics/begin_frame_provider.cc
index 8d3a6f6..e071c92 100644
--- a/third_party/blink/renderer/platform/graphics/begin_frame_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/begin_frame_provider.cc
@@ -9,6 +9,7 @@
 #include "third_party/blink/public/platform/interface_provider.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
+#include "ui/gfx/mojo/presentation_feedback.mojom-blink.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
index 75731e6..73643de7 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
@@ -11,6 +11,7 @@
 #include "components/viz/common/quads/texture_draw_quad.h"
 #include "components/viz/common/resources/resource_format.h"
 #include "components/viz/common/resources/single_release_callback.h"
+#include "services/viz/public/interfaces/hit_test/hit_test_region_list.mojom-blink.h"
 #include "third_party/blink/public/platform/interface_provider.h"
 #include "third_party/blink/public/platform/modules/frame_sinks/embedded_frame_sink.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -22,6 +23,7 @@
 #include "third_party/blink/renderer/platform/histogram.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
+#include "ui/gfx/mojo/presentation_feedback.mojom-blink.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher_test.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher_test.cc
index 2603317..21cba56 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher_test.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher_test.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "components/viz/common/quads/texture_draw_quad.h"
+#include "services/viz/public/interfaces/hit_test/hit_test_region_list.mojom-blink.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/modules/frame_sinks/embedded_frame_sink.mojom-blink.h"
@@ -17,6 +18,7 @@
 #include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/skia/include/core/SkSurface.h"
+#include "ui/gfx/mojo/presentation_feedback.mojom-blink.h"
 
 using testing::_;
 using testing::Mock;
diff --git a/third_party/blink/renderer/platform/graphics/test/mock_compositor_frame_sink.h b/third_party/blink/renderer/platform/graphics/test/mock_compositor_frame_sink.h
index 685e85d..ce7379f 100644
--- a/third_party/blink/renderer/platform/graphics/test/mock_compositor_frame_sink.h
+++ b/third_party/blink/renderer/platform/graphics/test/mock_compositor_frame_sink.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_TEST_MOCK_COMPOSITOR_FRAME_SINK_H_
 
 #include "components/viz/common/quads/compositor_frame.h"
+#include "gpu/ipc/common/mailbox.mojom-blink.h"
 #include "services/viz/public/interfaces/compositing/compositor_frame_sink.mojom-blink.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/blink/public/platform/modules/frame_sinks/embedded_frame_sink.mojom-blink.h"
diff --git a/third_party/blink/renderer/platform/mojo/geometry_struct_traits_test.cc b/third_party/blink/renderer/platform/mojo/geometry_struct_traits_test.cc
index 46a82b7..44dca7c0 100644
--- a/third_party/blink/renderer/platform/mojo/geometry_struct_traits_test.cc
+++ b/third_party/blink/renderer/platform/mojo/geometry_struct_traits_test.cc
@@ -7,6 +7,7 @@
 #include "base/message_loop/message_loop.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/mojo/geometry.mojom-blink.h"
 #include "ui/gfx/geometry/mojo/geometry_traits_test_service.mojom-blink.h"
 
 namespace blink {
diff --git a/third_party/blink/web_tests/SmokeTests b/third_party/blink/web_tests/SmokeTests
index 5554052..3f3390c2 100644
--- a/third_party/blink/web_tests/SmokeTests
+++ b/third_party/blink/web_tests/SmokeTests
@@ -619,7 +619,6 @@
 fast/innerHTML/innerHTML-iframe.html
 fast/innerHTML/innerHTML-nbsp.xhtml
 fast/innerHTML/innerHTML-script-tag-crash.xhtml
-fast/innerHTML/javascript-url.html
 fast/input/input-device-constructor.html
 fast/inspector-support/cssURLQuotes.html
 fast/inspector-support/uncaught-dom1-exception.html
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index f2782fd2..a5c7f574d 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -5759,7 +5759,7 @@
 
 # Sheriff 2018-11-26
 crbug.com/908276 [ Mac ] external/wpt/webstorage/storage_setitem.html [ Pass Timeout ]
-crbug.com/908347 [ Mac ] media/autoplay/webaudio-audio-context-resume.html [ Failure Pass ]
+crbug.com/908347 media/autoplay/webaudio-audio-context-resume.html [ Failure Pass ]
 
 # Sheriff 2018-11-29
 crbug.com/910139 custom-elements/form-submission-file.html [ Crash Pass ]
@@ -5892,11 +5892,17 @@
 crbug.com/922951 animations/direction-and-fill/fill-mode-transform.html [ Skip ]
 crbug.com/922951 animations/direction-and-fill/fill-mode.html [ Skip ]
 crbug.com/922951 compositing/overflow/overflow-scroll-with-local-background.html [ Skip ]
+crbug.com/922951 fast/dom/shadow/move-marquee-crossing-treescope-crash.html [ Skip ]
 crbug.com/922951 fast/loader/detach-while-printing.html [ Skip ]
 crbug.com/922951 fast/loader/document-open-iframe-then-detach.html [ Skip ]
 crbug.com/922951 fast/scroll-snap/snaps-after-keyboard-scrolling.html [ Skip ]
 crbug.com/922951 fast/scroll-snap/snaps-for-different-key-granularity.html [ Skip ]
 crbug.com/922951 fast/text/international/inline-plaintext-relayout-with-leading-neutrals.html [ Skip ]
+crbug.com/922951 http/tests/devtools/audits2/audits2-successful-run.js [ Skip ]
+crbug.com/922951 http/tests/devtools/tracing/console-timeline.js [ Skip ]
+crbug.com/922951 http/tests/devtools/tracing/timeline-network-received-data.js [ Skip ]
+crbug.com/922951 http/tests/devtools/tracing-session-id.js [ Skip ]
+crbug.com/922951 http/tests/images/feature-policy-unoptimized-images-cached-image.html [ Skip ]
 crbug.com/922951 http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html [ Skip ]
 crbug.com/922951 http/tests/webaudio/autoplay-crossorigin.html [ Skip ]
 crbug.com/922951 media/controls/overflow-menu-hide-on-click-outside-stoppropagation.html [ Skip ]
@@ -5904,6 +5910,9 @@
 crbug.com/922951 media/controls/overflow-menu-toggle-class-for-animation.html [ Skip ]
 crbug.com/922951 svg/animations/dynamic-modify-transform-without-baseval.html [ Skip ]
 crbug.com/922951 svg/animations/target-condition-crash.html [ Skip ]
+crbug.com/922951 svg/as-object/embedded-svg-immediate-offsetWidth-query.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-fixed-percentage-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/custom/object-sizing-zero-intrinsic-width-height.html [ Skip ]
 crbug.com/922951 virtual/gpu/fast/canvas/color-space/canvas-drawImage-offscreenCanvas.html [ Skip ]
 crbug.com/922951 virtual/gpu/fast/canvas/OffscreenCanvas-placeholder-createImageBitmap.html [ Skip ]
 crbug.com/922951 virtual/mouseevent_fractional/fast/events/synthetic-events/tap-on-scaled-screen.html [ Skip ]
@@ -5919,6 +5928,12 @@
 crbug.com/922951 virtual/threaded/animations/direction-and-fill/fill-mode-iteration-count-non-integer.html [ Skip ]
 crbug.com/922951 virtual/threaded/animations/direction-and-fill/fill-mode-missing-from-to-keyframes.html [ Skip ]
 crbug.com/922951 virtual/threaded/animations/direction-and-fill/fill-mode.html [ Skip ]
+crbug.com/922951 virtual/threaded/http/tests/devtools/tracing/console-timeline.js [ Skip ]
+crbug.com/922951 virtual/threaded/http/tests/devtools/tracing/frame-model-instrumentation.js [ Skip ]
+crbug.com/922951 virtual/threaded/http/tests/devtools/tracing/timeline-layout/timeline-layout-with-invalidations.js [ Skip ]
+crbug.com/922951 virtual/threaded/http/tests/devtools/tracing/timeline-layout/timeline-layout-reason.js [ Skip ]
+crbug.com/922951 virtual/threaded/http/tests/devtools/tracing/timeline-misc/timeline-record-reload.js [ Skip ]
+crbug.com/922951 virtual/threaded/http/tests/devtools/tracing/timeline-paint/timeline-paint-image.js [ Skip ]
 crbug.com/922951 virtual/user-activation-v2/fast/events/synthetic-events/tap-on-scaled-screen.html [ Skip ]
 crbug.com/922951 virtual/video-surface-layer/media/controls/overflow-menu-hide-on-click-outside-stoppropagation.html [ Skip ]
 crbug.com/922951 virtual/video-surface-layer/media/controls/overflow-menu-hide-on-click-outside.html [ Skip ]
diff --git a/third_party/blink/web_tests/external/wpt/html/syntax/serializing-html-fragments/serializing.html b/third_party/blink/web_tests/external/wpt/html/syntax/serializing-html-fragments/serializing.html
index 6645b5aa..1bccbf5 100644
--- a/third_party/blink/web_tests/external/wpt/html/syntax/serializing-html-fragments/serializing.html
+++ b/third_party/blink/web_tests/external/wpt/html/syntax/serializing-html-fragments/serializing.html
@@ -15,6 +15,7 @@
 <span><a b='"'></a></span>
 <span><a b="<"></a></span>
 <span><a b=">"></a></span>
+<span><a href="javascript:&quot;&lt;>&quot;"></a></span>
 <span><svg xlink:href="a"></svg></span>
 <span><svg xmlns:svg="test"></svg></span>
 <span>a</span>
@@ -49,6 +50,7 @@
 ["<a b=\"&quot;\"></a>", "<span><a b=\"&quot;\"></a></span>"],
 ["<a b=\"<\"></a>", "<span><a b=\"<\"></a></span>"],
 ["<a b=\">\"></a>", "<span><a b=\">\"></a></span>"],
+["<a href=\"javascript:&quot;<>&quot;\"></a>", "<span><a href=\"javascript:&quot;<>&quot;\"></a></span>"],
 ["<svg xlink:href=\"a\"></svg>", "<span><svg xlink:href=\"a\"></svg></span>"],
 ["<svg xmlns:svg=\"test\"></svg>", "<span><svg xmlns:svg=\"test\"></svg></span>"],
 ["a", "<span>a</span>"],
diff --git a/third_party/blink/web_tests/fast/innerHTML/javascript-url-expected.txt b/third_party/blink/web_tests/fast/innerHTML/javascript-url-expected.txt
deleted file mode 100644
index ee8a57e..0000000
--- a/third_party/blink/web_tests/fast/innerHTML/javascript-url-expected.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-Test that innerHTML/outerHTML does not mangle javascript: urls.
-PASS r.innerHTML is "<a href=\"javascript:test(123)\">link</a>"
-PASS r.innerHTML is "<a href='javascript:test(\"text<\")'>link</a>"
-PASS r.innerHTML is "<a href=\"javascript:test('text>')\">link</a>"
-PASS r.innerHTML is "<a href=\"javascript:test('text&amp;',&quot;test2&amp;&quot;)\">link</a>"
-PASS r.firstChild.outerHTML is "<a href=\"javascript:window.location='?x&amp;y'\">link</a>"
-PASS r.firstChild.outerHTML is "<a href=\"javascript:window.location='?x&amp;amp;y'\">link</a>"
-PASS r.firstChild.outerHTML is "<a href=\"javascript:window.location='?x&amp;y'\">link</a>"
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
-link
diff --git a/third_party/blink/web_tests/fast/innerHTML/javascript-url.html b/third_party/blink/web_tests/fast/innerHTML/javascript-url.html
deleted file mode 100644
index ed1e30a..0000000
--- a/third_party/blink/web_tests/fast/innerHTML/javascript-url.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE html>
-<script src="../../resources/js-test.js"></script>
-<body>
-Test that innerHTML/outerHTML does not mangle javascript: urls.
-<div id=console></div>
-<div id=jsurltest><a href='javascript:test(&37;3C!--D--&37;3E)'>link</a></div>
-<script>
-var r = document.getElementById('jsurltest');
-
-r.innerHTML = r.innerHTML.replace('&amp;37;3C!--D--&amp;37;3E', 123);
-shouldBeEqualToString('r.innerHTML', '<a href="javascript:test(123)">link</a>');
-
-r.firstChild.setAttribute('href', 'javascript:test(\"text<\")');
-shouldBeEqualToString('r.innerHTML', '<a href=\'javascript:test("text<")\'>link</a>');
-
-r.firstChild.setAttribute('href', 'javascript:test(\'text>\')');
-shouldBeEqualToString("r.innerHTML", '<a href="javascript:test(\'text>\')">link</a>');
-
-testString = 'javascript:test(\'text&\',"test2&")';
-r.firstChild.setAttribute('href', testString);
-shouldBeEqualToString('r.innerHTML', '<a href="javascript:test(\'text&amp;\',&quot;test2&amp;&quot;)">link</a>');
-
-r.firstChild.href = 'javascript:window.location=\'?x&y\'';
-shouldBeEqualToString("r.firstChild.outerHTML", '<a href="javascript:window.location=\'?x&amp;y\'">link</a>');
-
-// Behavior is same as FF
-r.firstChild.href = 'javascript:window.location=\'?x&amp;y\'';
-shouldBeEqualToString('r.firstChild.outerHTML', '<a href="javascript:window.location=\'?x&amp;amp;y\'">link</a>');
-r.innerHTML = '<a href="javascript:window.location=\'?x&amp;y\'">link</a>';
-shouldBeEqualToString('r.firstChild.outerHTML', '<a href="javascript:window.location=\'?x&amp;y\'">link</a>');
-</script>
diff --git a/third_party/emoji-segmenter/BUILD.gn b/third_party/emoji-segmenter/BUILD.gn
new file mode 100644
index 0000000..a4ba9f4f
--- /dev/null
+++ b/third_party/emoji-segmenter/BUILD.gn
@@ -0,0 +1,11 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+config("emoji_segmenter_config") {
+  include_dirs = [ "src" ]
+}
+
+source_set("emoji-segmenter") {
+  public_configs = [ ":emoji_segmenter_config" ]
+}
diff --git a/third_party/emoji-segmenter/LICENSE b/third_party/emoji-segmenter/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/third_party/emoji-segmenter/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/third_party/emoji-segmenter/OWNERS b/third_party/emoji-segmenter/OWNERS
new file mode 100644
index 0000000..a0d6c05
--- /dev/null
+++ b/third_party/emoji-segmenter/OWNERS
@@ -0,0 +1,3 @@
+behdad@chromium.org
+drott@chromium.org
+eae@chromium.org
diff --git a/third_party/emoji-segmenter/README.chromium b/third_party/emoji-segmenter/README.chromium
new file mode 100644
index 0000000..904a9d85
--- /dev/null
+++ b/third_party/emoji-segmenter/README.chromium
@@ -0,0 +1,16 @@
+Name: Emoji Segmenter
+Short Name: emoji-segmenter
+URL: https://github.com/googlei18n/emoji-segmenter
+Version: 0.1.0
+Date: 20190129
+Revision: 242460e9cbee7453880725be5b9bf352d1882a9f
+Security Critical: yes
+License: Apache 2.0
+License File: LICENSE
+
+Description:
+This is an import of the Emoji Segmenter maintained at the source URL above. The
+Emoji Segmenter is a Ragel grammar and generated C code for segmenting runs of
+text into text-presentation and emoji-presentation runs. It is currently used
+for deciding which preferred presentation, color or text, a run of text should
+have.
diff --git a/third_party/feed/README.chromium b/third_party/feed/README.chromium
index af78a9aa..bde85a2c 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: ed7c2ae304814efdb0e85cfbfe5205897b0d8d03
+Revision: 25362a7395755d79755a83ebb5eb19ec6ad0af20
 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 ce77d21..acc16da3 100644
--- a/third_party/feed/java_sources.gni
+++ b/third_party/feed/java_sources.gni
@@ -231,6 +231,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/EventLogger.java",
   "src/src/main/java/com/google/android/libraries/feed/piet/host/HostBindingProvider.java",
+  "src/src/main/java/com/google/android/libraries/feed/piet/host/ThrowingCustomElementProvider.java",
   "src/src/main/java/com/google/android/libraries/feed/piet/ui/AspectRatioScalingImageView.java",
   "src/src/main/java/com/google/android/libraries/feed/piet/ui/BorderDrawable.java",
   "src/src/main/java/com/google/android/libraries/feed/piet/ui/GradientDrawable.java",
@@ -263,8 +264,8 @@
 feed_conformance_test_lib_sources = [
   "src/src/main/java/com/google/android/libraries/feed/api/common/testing/ContentIdGenerators.java",
   "src/src/main/java/com/google/android/libraries/feed/api/common/testing/InternalProtocolBuilder.java",
+  "src/src/main/java/com/google/android/libraries/feed/common/concurrent/testing/ClockBackedFakeMainThreadRunner.java",
   "src/src/main/java/com/google/android/libraries/feed/common/concurrent/testing/FakeMainThreadRunner.java",
-  "src/src/main/java/com/google/android/libraries/feed/common/testing/FakeClock.java",
   "src/src/main/java/com/google/android/libraries/feed/common/testing/FakeRequestManager.java",
   "src/src/main/java/com/google/android/libraries/feed/common/testing/InfrastructureIntegrationScope.java",
   "src/src/main/java/com/google/android/libraries/feed/common/testing/ModelProviderValidator.java",
@@ -273,6 +274,7 @@
   "src/src/main/java/com/google/android/libraries/feed/common/testing/RunnableSubject.java",
   "src/src/main/java/com/google/android/libraries/feed/common/testing/Suppliers.java",
   "src/src/main/java/com/google/android/libraries/feed/common/testing/WireProtocolResponseBuilder.java",
+  "src/src/main/java/com/google/android/libraries/feed/common/time/testing/FakeClock.java",
   "src/src/main/java/com/google/android/libraries/feed/testing/conformance/network/NetworkClientConformanceTest.java",
   "src/src/main/java/com/google/android/libraries/feed/testing/conformance/scheduler/SchedulerConformanceTest.java",
   "src/src/main/java/com/google/android/libraries/feed/testing/conformance/storage/ContentStorageConformanceTest.java",
diff --git a/tools/idl_parser/idl_lexer.py b/tools/idl_parser/idl_lexer.py
index a4a6f76..e3965017 100755
--- a/tools/idl_parser/idl_lexer.py
+++ b/tools/idl_parser/idl_lexer.py
@@ -161,7 +161,7 @@
 
   # A symbol or keyword.
   def t_KEYWORD_OR_SYMBOL(self, t):
-    r'_?[A-Za-z][A-Za-z_0-9-]*'
+    r'[_-]?[A-Za-z][A-Za-z_0-9-]*'
 
     # All non-keywords are assumed to be symbols
     t.type = self.keywords.get(t.value, 'identifier')
diff --git a/tools/idl_parser/test_lexer/values.in b/tools/idl_parser/test_lexer/values.in
index 88dd8cd0..ad285895c 100644
--- a/tools/idl_parser/test_lexer/values.in
+++ b/tools/idl_parser/test_lexer/values.in
@@ -54,3 +54,5 @@
 
 identifier blah
 identifier include-hyphen
+identifier _leading_underscore
+identifier -leading_hyphen
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 99f7c8b..609aed3 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -7320,6 +7320,18 @@
   <int value="14" label="Viber"/>
 </enum>
 
+<enum name="ClientCertSelectionResult">
+  <summary>
+    This metric enumerates how Chrome choses a client certificate when needed as
+    a result of user action or content settings.
+  </summary>
+  <int value="0" label="User selected certificate"/>
+  <int value="1" label="User cancelled selection"/>
+  <int value="2" label="User closed tab"/>
+  <int value="3" label="Selected by content settings"/>
+  <int value="4" label="User selection not permitted"/>
+</enum>
+
 <enum name="ClipboardAction">
   <int value="0" label="Write from non-Incognito"/>
   <int value="1" label="Write from Incognito"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 18ceb69..ca0e5bf 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -102001,6 +102001,22 @@
   </summary>
 </histogram>
 
+<histogram name="Security.ClientAuth.CertificateSelectionSource"
+    enum="ClientCertSelectionResult" expires_after="2019-07-30">
+  <owner>jdeblasio@chromium.org</owner>
+  <summary>
+    When TLS client authentication is requested by the server, Chrome must
+    choose whether to return a certificate or to abort the handshake. This
+    histogram records how this determination was made, either as a result of
+    user action, or as chosen automatically due to content settings. It only
+    records when a fresh certificate selection would be attempted (and thus
+    ignores cache hits). This histogram is recorded in SSLClientAuthObserver for
+    user-selected outcomes and in ChromeContentBrowserClient for policy
+    outcomes. This histogram does not cover mobile, as certificate handling is
+    not handled by Chrome on Android nor iOS.
+  </summary>
+</histogram>
+
 <histogram
     name="Security.HTTPBad.NavigationStartedAfterUserWarnedAboutSensitiveInput"
     units="ms">
diff --git a/ui/aura/test/DEPS b/ui/aura/test/DEPS
index 2de1840..a1c39352 100644
--- a/ui/aura/test/DEPS
+++ b/ui/aura/test/DEPS
@@ -2,6 +2,7 @@
   "+cc/test",
   "+components/viz/test",
   "+mojo/core/embedder/embedder.h",
+  "+services/viz/public/interfaces/compositing/compositor_frame_sink.mojom.h",
   "+services/ws/public/cpp/input_devices",
   "+ui/gl",
   "+ui/wm/core/wm_state.h",
diff --git a/ui/aura/test/mus/test_window_tree.cc b/ui/aura/test/mus/test_window_tree.cc
index 2357a53..98bf5a3 100644
--- a/ui/aura/test/mus/test_window_tree.cc
+++ b/ui/aura/test/mus/test_window_tree.cc
@@ -7,6 +7,8 @@
 #include "base/bind.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
+#include "services/viz/public/interfaces/compositing/compositor_frame_sink.mojom.h"
+#include "services/ws/public/mojom/window_tree_constants.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/test/mus/test_window_tree_delegate.h"
 
diff --git a/ui/aura/test/mus/test_window_tree.h b/ui/aura/test/mus/test_window_tree.h
index e491a352..3d15e2be 100644
--- a/ui/aura/test/mus/test_window_tree.h
+++ b/ui/aura/test/mus/test_window_tree.h
@@ -14,6 +14,7 @@
 #include "services/ws/public/mojom/window_tree.mojom.h"
 #include "ui/aura/mus/mus_types.h"
 #include "ui/base/hit_test.h"
+#include "ui/platform_window/mojo/text_input_state.mojom.h"
 
 namespace aura {
 
diff --git a/ui/file_manager/file_manager/background/js/test_util.js b/ui/file_manager/file_manager/background/js/test_util.js
index 974d5f9..92ba7d1 100644
--- a/ui/file_manager/file_manager/background/js/test_util.js
+++ b/ui/file_manager/file_manager/background/js/test_util.js
@@ -17,26 +17,6 @@
 };
 
 /**
- * Returns an array with the files currently selected in the file manager.
- * TODO(hirono): Integrate the method into getFileList method.
- *
- * @param {Window} contentWindow Window to be tested.
- * @return {Array<string>} Array of selected files.
- */
-test.util.sync.getSelectedFiles = function(contentWindow) {
-  var table = contentWindow.document.querySelector('#detail-table');
-  var rows = table.querySelectorAll('li');
-  var selected = [];
-  for (var i = 0; i < rows.length; ++i) {
-    if (rows[i].hasAttribute('selected')) {
-      selected.push(
-          rows[i].querySelector('.filename-label').textContent);
-    }
-  }
-  return selected;
-};
-
-/**
  * Returns the name of the item currently selected in the directory tree.
  * Returns null if no entry is selected.
  *
@@ -56,15 +36,16 @@
   return null;
 };
 
-
 /**
- * Returns an array with the files on the file manager's file list.
+ * Returns details about each file shown in the file list: name, size, type and
+ * modification time.
  *
- * TODO(sashab): Since we recycle DOM elements, this only returns the first ~11
- * visible elements. crbug.com/850834.
+ * Since FilesApp normally has a fixed display size in test, and also since the
+ * #detail-table recycles its file row elements, this call only returns details
+ * about the visible file rows (11 rows normally, see crbug.com/850834).
  *
  * @param {Window} contentWindow Window to be tested.
- * @return {Array<Array<string>>} Array of rows.
+ * @return {Array<Array<string>>} Details for each visible file row.
  */
 test.util.sync.getFileList = function(contentWindow) {
   var table = contentWindow.document.querySelector('#detail-table');
@@ -83,6 +64,25 @@
 };
 
 /**
+ * Returns the name of the files currently selected in the file list. Note the
+ * routine has the same 'visible files' limitation as getFileList() above.
+ *
+ * @param {Window} contentWindow Window to be tested.
+ * @return {Array<string>} Selected file names.
+ */
+test.util.sync.getSelectedFiles = function(contentWindow) {
+  var table = contentWindow.document.querySelector('#detail-table');
+  var rows = table.querySelectorAll('li');
+  var selected = [];
+  for (var i = 0; i < rows.length; ++i) {
+    if (rows[i].hasAttribute('selected')) {
+      selected.push(rows[i].querySelector('.filename-label').textContent);
+    }
+  }
+  return selected;
+};
+
+/**
  * Fakes pressing the down arrow until the given |filename| is selected.
  *
  * @param {Window} contentWindow Window to be tested.
diff --git a/ui/file_manager/file_manager/foreground/css/file_types.css b/ui/file_manager/file_manager/foreground/css/file_types.css
index 1f9609f..9a8da83 100644
--- a/ui/file_manager/file_manager/foreground/css/file_types.css
+++ b/ui/file_manager/file_manager/foreground/css/file_types.css
@@ -363,7 +363,8 @@
 
 .external-media-root[file-type-icon='folder'],
 [volume-type-icon='external_media'],
-[volume-type-icon='removable'] {
+[volume-type-icon='removable'],
+[root-type-icon='removable'] {
   background-image: -webkit-image-set(
       url(../images/volumes/usb.png) 1x,
       url(../images/volumes/2x/usb.png) 2x);
@@ -372,7 +373,8 @@
 tree .tree-item[selected] > .tree-row >
 .external-media-root[file-type-icon='folder'],
 .tree-row[selected] [volume-type-icon='external_media'],
-.tree-row[selected] [volume-type-icon='removable'] {
+.tree-row[selected] [volume-type-icon='removable'],
+.tree-row[selected] [root-type-icon='removable'] {
   background-image: -webkit-image-set(
       url(../images/volumes/usb_active.png) 1x,
       url(../images/volumes/2x/usb_active.png) 2x);
@@ -453,13 +455,17 @@
       url(../images/volumes/2x/phone_active.png) 2x);
 }
 
-[volume-type-icon='removable'][volume-subtype='unknown'] {
+[volume-type-icon='removable'][volume-subtype='partition'],
+[volume-type-icon='removable'][volume-subtype='unknown'],
+[file-type-icon='removable'] {
   background-image: -webkit-image-set(
       url(../images/volumes/hard_drive.png) 1x,
       url(../images/volumes/2x/hard_drive.png) 2x);
 }
 
-.tree-row[selected] [volume-type-icon='removable'][volume-subtype='unknown'] {
+tree .tree-item[selected] > .tree-row >
+[volume-type-icon='removable'][volume-subtype='partition'],
+.tree-row[selected] [volume-type-icon='removable'][volume-subtype='unknown']  {
   background-image: -webkit-image-set(
       url(../images/volumes/hard_drive_active.png) 1x,
       url(../images/volumes/2x/hard_drive_active.png) 2x);
diff --git a/ui/ozone/platform/wayland/wayland_xkb_keyboard_layout_engine.cc b/ui/ozone/platform/wayland/wayland_xkb_keyboard_layout_engine.cc
index b2600ad..d1865c62 100644
--- a/ui/ozone/platform/wayland/wayland_xkb_keyboard_layout_engine.cc
+++ b/ui/ozone/platform/wayland/wayland_xkb_keyboard_layout_engine.cc
@@ -17,6 +17,7 @@
   xkb_mod_indexes_.alt = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_ALT);
   xkb_mod_indexes_.shift = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_SHIFT);
   xkb_mod_indexes_.caps = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS);
+  xkb_mod_indexes_.altgr = xkb_keymap_mod_get_index(keymap, "Mod5");
 }
 
 void WaylandXkbKeyboardLayoutEngine::UpdateModifiers(uint32_t depressed_mods,
@@ -48,6 +49,10 @@
     event_modifiers_->SetModifierLock(MODIFIER_CAPS_LOCK, true);
   else
     event_modifiers_->SetModifierLock(MODIFIER_CAPS_LOCK, false);
+
+  if (xkb_state_mod_index_is_active(xkb_state_.get(), xkb_mod_indexes_.altgr,
+                                    component))
+    event_modifiers_->UpdateModifier(MODIFIER_ALTGR, true);
 }
 
 void WaylandXkbKeyboardLayoutEngine::SetEventModifiers(
diff --git a/ui/ozone/platform/wayland/wayland_xkb_keyboard_layout_engine.h b/ui/ozone/platform/wayland/wayland_xkb_keyboard_layout_engine.h
index 18c8f8c..70bf303 100644
--- a/ui/ozone/platform/wayland/wayland_xkb_keyboard_layout_engine.h
+++ b/ui/ozone/platform/wayland/wayland_xkb_keyboard_layout_engine.h
@@ -36,6 +36,7 @@
     xkb_mod_index_t alt = 0;
     xkb_mod_index_t shift = 0;
     xkb_mod_index_t caps = 0;
+    xkb_mod_index_t altgr = 0;
   } xkb_mod_indexes_;
 
   EventModifiers* event_modifiers_ = nullptr;  // Owned by WaylandKeyboard.